Enabling a web application firewall (WAF) in front of Nginx reduces exposure to common web attacks by inspecting HTTP traffic before it reaches the application. A WAF is most valuable at reverse-proxy edges where many applications share the same ingress, or where legacy code cannot be hardened quickly.
A common open-source WAF stack for Nginx uses ModSecurity v3 (libmodsecurity) plus the ModSecurity-nginx connector module. Requests are evaluated against a rule set, most commonly the OWASP Core Rule Set (CRS), and matching rules can be logged, blocked, or both.
Rule-based inspection can introduce false positives plus measurable overhead, so a safe rollout starts in DetectionOnly mode with audit logging enabled. Commands below target Ubuntu or Debian with systemd plus the distro nginx package; dynamic modules must match the installed Nginx version, so module rebuilds are expected after nginx upgrades.
Related: How to secure Nginx web server
Related: How to configure log rotation for Nginx
Steps to enable a web application firewall for Nginx:
- Install build packages required for libmodsecurity plus Nginx module compilation.
$ sudo apt update && sudo apt install --assume-yes \ ca-certificates curl git build-essential pkgconf \ autoconf automake libtool flex bison \ libpcre2-dev zlib1g-dev libssl-dev \ libxml2 libxml2-dev libyajl-dev \ libcurl4-openssl-dev libgeoip-dev liblmdb-dev libmaxminddb-dev Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease ##### snipped ##### Setting up libyajl-dev:amd64 (2.1.0-3build2) ... Processing triggers for libc-bin (2.35-0ubuntu3.6) ...
Older Debian or Ubuntu releases may use libpcre3-dev instead of libpcre2-dev.
- Create a workspace directory for the build.
$ mkdir -p ~/src/modsecurity-nginx
- Switch into the workspace directory.
$ cd ~/src/modsecurity-nginx
- Populate the workspace with the required source repositories.
$ git clone --depth 1 https://github.com/SpiderLabs/ModSecurity.git Cloning into 'ModSecurity'... remote: Enumerating objects: 100, done. remote: Counting objects: 100% (100/100), done. ##### snipped ##### $ git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git Cloning into 'ModSecurity-nginx'... remote: Enumerating objects: 100, done. ##### snipped #####
- Install libmodsecurity from source.
$ cd ~/src/modsecurity-nginx/ModSecurity $ git submodule update --init --recursive Submodule 'others/libinjection' registered for path 'others/libinjection' ##### snipped ##### $ ./build.sh ##### snipped ##### $ ./configure checking for gcc... gcc checking whether the C compiler works... yes ##### snipped ##### $ make -j"$(nproc)" ##### snipped ##### $ sudo make install ##### snipped ##### $ sudo ldconfig
- Create the ModSecurity configuration directory for Nginx.
$ sudo mkdir -p /etc/nginx/modsec
- Copy unicode.mapping into the ModSecurity configuration directory.
$ sudo cp ~/src/modsecurity-nginx/ModSecurity/unicode.mapping /etc/nginx/modsec/unicode.mapping
- Create /etc/nginx/modsec/modsecurity.conf in DetectionOnly mode.
SecRuleEngine DetectionOnly SecRequestBodyAccess On SecRequestBodyLimit 13107200 SecRequestBodyNoFilesLimit 131072 SecRequestBodyInMemoryLimit 131072 SecResponseBodyAccess Off SecAuditEngine RelevantOnly SecAuditLogRelevantStatus "^(?:5|4(?!04))" SecAuditLogParts ABIJDEFHZ SecAuditLogType Serial SecAuditLog /var/log/nginx/modsec_audit.log SecTmpDir /tmp SecDataDir /tmp SecUnicodeMapFile /etc/nginx/modsec/unicode.mapping 20127
DetectionOnly logs rule matches without blocking responses, which keeps the first rollout safer while false positives are tuned.
- Install the OWASP Core Rule Set under /etc/nginx/modsec/.
$ sudo git clone --depth 1 https://github.com/coreruleset/coreruleset.git /etc/nginx/modsec/coreruleset Cloning into '/etc/nginx/modsec/coreruleset'... ##### snipped ##### $ sudo cp /etc/nginx/modsec/coreruleset/crs-setup.conf.example /etc/nginx/modsec/coreruleset/crs-setup.conf
- Create /etc/nginx/modsec/main.conf to load ModSecurity plus CRS rules.
Include /etc/nginx/modsec/modsecurity.conf Include /etc/nginx/modsec/coreruleset/crs-setup.conf Include /etc/nginx/modsec/coreruleset/rules/*.conf
- Create the ModSecurity audit log file for Nginx.
$ 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
- Build the ModSecurity dynamic module for the installed Nginx version.
$ cd ~/src/modsecurity-nginx $ NGINX_VERSION="$(nginx -v 2>&1 | sed 's/^nginx version: nginx\\///')" $ echo "$NGINX_VERSION" 1.22.1 $ curl -fsSLO "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" $ tar -xzf "nginx-${NGINX_VERSION}.tar.gz" $ cd "nginx-${NGINX_VERSION}" $ ./configure --with-compat --add-dynamic-module=../ModSecurity-nginx ##### snipped ##### $ make modules ##### snipped ##### $ ls -1 objs/ngx_http_modsecurity_module.so objs/ngx_http_modsecurity_module.so
The module must match the installed Nginx version; repeat the module build after nginx package upgrades.
- Install the compiled module into the Nginx modules directory.
$ sudo cp ~/src/modsecurity-nginx/nginx-${NGINX_VERSION}/objs/ngx_http_modsecurity_module.so /usr/lib/nginx/modules/ $ sudo ls -l /usr/lib/nginx/modules/ngx_http_modsecurity_module.so -rw-r--r-- 1 root root 123456 Dec 14 12:34 /usr/lib/nginx/modules/ngx_http_modsecurity_module.so
On some builds the modules directory is different; confirm with nginx -V output showing the prefix, then adjust the destination accordingly.
- Load the ModSecurity module via /etc/nginx/modules-enabled/50-modsecurity.conf.
$ sudo tee /etc/nginx/modules-enabled/50-modsecurity.conf >/dev/null <<'EOF' load_module modules/ngx_http_modsecurity_module.so; EOF
If /etc/nginx/nginx.conf does not include /etc/nginx/modules-enabled/*.conf, add the load_module line near the top of /etc/nginx/nginx.conf instead.
- Enable ModSecurity in the Nginx HTTP context.
modsecurity on; modsecurity_rules_file /etc/nginx/modsec/main.conf;
Place the two directives into a dedicated file such as /etc/nginx/conf.d/modsecurity.conf for global enablement, or move them into a single server block for a narrower rollout.
- Test the Nginx configuration for syntax errors.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
- Reload nginx to apply the WAF.
$ sudo systemctl reload nginx
- Send a CRS-triggering request to generate an audit log entry.
$ curl -i "http://127.0.0.1/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E" HTTP/1.1 200 OK Server: nginx Date: Sun, 14 Dec 2025 12:35:12 GMT Content-Type: text/html Content-Length: 615 Connection: keep-alive ##### snipped #####
- Confirm the request appears in /var/log/nginx/modsec_audit.log.
$ sudo tail -n 60 /var/log/nginx/modsec_audit.log ##### snipped ##### Message: Warning. Pattern match "<script" at ARGS:q. [file "/etc/nginx/modsec/coreruleset/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "#####"] [id "941100"] [msg "XSS Attack Detected"] [severity "CRITICAL"] ##### snipped #####
- Enable blocking mode by setting SecRuleEngine to On.
$ sudo sed -i 's/^SecRuleEngine DetectionOnly$/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf
SecRuleEngine On can return 403 for legitimate traffic until rule exclusions are tuned; deploy gradually plus monitor the audit log for false positives.
- Reload nginx to apply the ModSecurity engine mode change.
$ sudo systemctl reload nginx
- Verify the test payload returns 403 in blocking mode.
$ curl -i "http://127.0.0.1/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E" HTTP/1.1 403 Forbidden Server: nginx Date: Sun, 14 Dec 2025 12:36:03 GMT Content-Type: text/html Content-Length: 153 Connection: keep-alive ##### snipped #####
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
Comment anonymously. Login not required.
