Enabling a web application firewall in front of Nginx adds request inspection before traffic reaches the application. That extra inspection helps catch obvious cross-site scripting, protocol abuse, and other hostile patterns at the reverse proxy instead of leaving every request decision to upstream code.

On current Ubuntu and Debian packaging, the libnginx-mod-http-modsecurity package ships the ModSecurity v3 dynamic module together with /etc/nginx/modsecurity.conf and /etc/nginx/modsecurity_includes.conf. The modsecurity-crs package provides the OWASP Core Rule Set under /etc/modsecurity/crs and /usr/share/modsecurity-crs/rules, so the supported package-based path does not require compiling Nginx or the rule set from source.

A safe rollout still starts in DetectionOnly mode so rule hits are written to the audit log before live traffic is blocked. One current packaging wrinkle matters on Nginx: /usr/share/modsecurity-crs/owasp-crs.load contains IncludeOptional directives for Apache loading, so /etc/nginx/modsecurity_includes.conf should use explicit Include lines for the CRS files instead of loading /owasp-crs.load directly through modsecurity_rules_file.

Steps to enable a web application firewall for Nginx:

  1. Open a terminal session with an account that can use sudo.
  2. Install the Nginx ModSecurity module and the packaged CRS rules.
    $ sudo apt update
    Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease
    Hit:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease
    Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease
    Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease
    Reading package lists...
    
    $ sudo apt install --assume-yes libnginx-mod-http-modsecurity modsecurity-crs
    Reading package lists...
    Building dependency tree...
    Reading state information...
    libnginx-mod-http-modsecurity is already the newest version (1.0.3-1build3).
    modsecurity-crs is already the newest version (3.3.5-2).
    0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
  3. Confirm the package installed the module include plus the default ModSecurity configuration files.
    $ sudo ls -l /etc/nginx/modules-enabled/50-mod-http-modsecurity.conf /etc/nginx/modsecurity.conf /etc/nginx/modsecurity_includes.conf
    lrwxrwxrwx 1 root root 60 Apr  9 13:08 /etc/nginx/modules-enabled/50-mod-http-modsecurity.conf -> /usr/share/nginx/modules-available/mod-http-modsecurity.conf
    -rw-r--r-- 1 root root 9100 Mar 31  2024 /etc/nginx/modsecurity.conf
    -rw-r--r-- 1 root root   62 Mar 31  2024 /etc/nginx/modsecurity_includes.conf
  4. Verify the packaged rule engine still starts in DetectionOnly mode.
    $ sudo grep -n '^SecRuleEngine' /etc/nginx/modsecurity.conf
    7:SecRuleEngine DetectionOnly

    The packaged /etc/nginx/modsecurity.conf already points the audit log to /var/log/nginx/modsec_audit.log, so the main setup work is loading the rule set and enabling the module in the Nginx configuration.

  5. Replace /etc/nginx/modsecurity_includes.conf with explicit CRS include lines that Nginx can parse.
    Include /etc/nginx/modsecurity.conf
    Include /etc/modsecurity/crs/crs-setup.conf
    Include /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
    Include /usr/share/modsecurity-crs/rules/*.conf
    Include /etc/modsecurity/crs/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf

    Do not point modsecurity_rules_file straight at /usr/share/modsecurity-crs/owasp-crs.load on current Ubuntu or Debian packages. That file contains IncludeOptional directives for Apache loading and causes nginx -t to fail when ModSecurity-nginx parses it.

  6. Enable ModSecurity in the http context so every server block inherits the WAF.
    # /etc/nginx/conf.d/modsecurity.conf
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;

    For a narrower rollout, place the same two directives inside one specific server block instead of /etc/nginx/conf.d/modsecurity.conf.

  7. Create the audit log path with permissions Nginx can use.
    $ sudo touch /var/log/nginx/modsec_audit.log
    $ sudo chown www-data:adm /var/log/nginx/modsec_audit.log
    $ sudo chmod 640 /var/log/nginx/modsec_audit.log
  8. Test the Nginx configuration before reloading it.
    $ sudo nginx -t
    2026/04/09 13:08:45 [notice] 3449#3449: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/921/0)
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  9. Reload nginx to apply the WAF configuration.
    $ sudo systemctl reload nginx
  10. Send a request that should trigger the CRS while the rule engine is still in DetectionOnly mode.
    $ curl --silent --show-error -D - -o /dev/null 'http://127.0.0.1/?q=%3Cscript%3Ealert(4)%3C%2Fscript%3E'
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Date: Thu, 09 Apr 2026 13:08:57 GMT
    Content-Type: text/html
    Content-Length: 615
    Last-Modified: Thu, 09 Apr 2026 13:08:18 GMT
    Connection: keep-alive
    ETag: "69d7a4c2-267"
    Accept-Ranges: bytes

    Use the real site hostname, or add --resolve example.com:80:127.0.0.1, when 127.0.0.1 does not map to the site that should be protected. A 200 response here is expected because DetectionOnly logs the match without blocking it.

  11. Confirm the request hit the audit log.
    $ sudo grep -n 'XSS Attack Detected via libinjection' /var/log/nginx/modsec_audit.log | tail --lines=1
    26:ModSecurity: Warning. detected XSS using libinjection. [file "/usr/share/modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "38"] [id "941100"] [msg "XSS Attack Detected via libinjection"] [data "Matched Data: XSS data found within ARGS:q: <script>alert(4)</script>"] [severity "2"] [ver "OWASP_CRS/3.3.5"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-xss"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/242"] [hostname "127.0.0.1"] [uri "/"] [unique_id "177574013781.138244"] [ref "v8,25t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls"]

    That confirms the module is active, the CRS rules are loaded, and the request is reaching the audit log before enforcement is enabled.

  12. Switch the rule engine from DetectionOnly to enforcement mode.
    $ sudo sed --in-place 's/^SecRuleEngine DetectionOnly$/SecRuleEngine On/' /etc/nginx/modsecurity.conf

    Blocking mode can return 403 for legitimate traffic until exclusions are tuned. Keep a rollback path ready and watch the audit log during the first live rollout.

  13. Verify the rule engine now shows On.
    $ sudo grep -n '^SecRuleEngine' /etc/nginx/modsecurity.conf
    7:SecRuleEngine On
  14. Test the configuration and reload nginx after turning blocking on.
    $ sudo nginx -t
    2026/04/09 13:09:07 [notice] 3494#3494: ModSecurity-nginx v1.0.3 (rules loaded inline/local/remote: 0/921/0)
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
    
    $ sudo systemctl reload nginx
  15. Verify the same test payload is now blocked with HTTP 403.
    $ curl --silent --show-error -D - -o /dev/null 'http://127.0.0.1/?q=%3Cscript%3Ealert(6)%3C%2Fscript%3E'
    HTTP/1.1 403 Forbidden
    Server: nginx/1.24.0 (Ubuntu)
    Date: Thu, 09 Apr 2026 13:10:10 GMT
    Content-Type: text/html
    Content-Length: 162
    Connection: keep-alive

    At this point the WAF is enforcing the packaged CRS. The next production step is tuning exclusions in /etc/modsecurity/crs/crs-setup.conf or separate local rule files so legitimate application traffic is not blocked.