Guides 11792 Published by

The guide shows how to add the ModSecurity web‑application firewall to an existing Nginx installation on Ubuntu 20.04 by first installing development packages, cloning and building the ModSecurity source, and then compiling a compatible dynamic module for the exact Nginx version in use. After copying the compiled module into /etc/nginx/modules and loading it via load_module, it walks through creating a minimal ModSecurity configuration, enabling the firewall inside a server block, and testing that malicious requests are blocked with a 403 response. Finally, it advises on log rotation and incremental rule updates to keep the WAF effective without overloading the server.



Install ModSecurity with Nginx on Ubuntu 20.04

If you’ve ever watched a malicious request slip past a default Nginx config, you know why a WAF is worth the extra coffee‑break. This guide walks through adding ModSecurity to an existing Nginx install on Ubuntu 20.04, shows where things can go sideways, and leaves you with a lean rule set that actually blocks something.

Prepare the system

sudo apt update
sudo apt install -y nginx git build-essential libpcre3-dev libssl-dev zlib1g-dev uuid-dev

Why? Nginx is already on the box, but ModSecurity needs development headers for PCRE (regular expressions) and OpenSSL. Skipping these libraries will cause the compile step to explode.

Grab ModSecurity source

cd /usr/local/src
sudo git clone --depth 1 -b v3/master https://github.com/SpiderLabs/ModSecurity.git

I once tried to download a zip from an unofficial mirror and ended up with a broken Makefile. The official repo is the safest bet.

Build ModSecurity

cd ModSecurity
sudo git submodule init
sudo git submodule update
sudo ./build.sh
sudo ./configure
sudo make
sudo make install

The ./build.sh script pulls in the libmodsecurity‑common files; without it the later make will complain about missing symbols.

Compile Nginx with the ModSecurity module

First, fetch the exact source version that matches your installed Nginx:

nginx -v # note the version number, e.g. 1.18.0
cd /usr/local/src
sudo wget http://nginx.org/download/nginx-1.18.0.tar.gz
sudo tar xf nginx-1.18.0.tar.gz

Now pull the connector:

sudo git clone https://github.com/SpiderLabs/ModSecurity-nginx.git

Configure and compile:

cd nginx-1.18.0
sudo ./configure \
--with-compat \
--add-dynamic-module=../ModSecurity-nginx
sudo make modules
sudo cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/

Why a dynamic module? It lets you enable or disable ModSecurity without rebuilding Nginx later. If you try to embed it statically and then upgrade Nginx, you’ll be stuck with version mismatches.

Load the module in Nginx

Edit /etc/nginx/nginx.conf (or a file under conf.d/) and add near the top:

load_module modules/ngx_http_modsecurity_module.so;

If you forget this line, Nginx will start but ignore all ModSecurity directives—waste of time.

Create a basic ModSecurity config

Copy the example file and trim it down:

sudo mkdir -p /etc/nginx/modsec
sudo cp /usr/local/modsecurity/etc/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf

The default config ships in “DetectionOnly” mode, which logs but never blocks. Turning it on is the whole point of a WAF.

Hook ModSecurity into a server block

Inside your site’s server {} block add:

modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;

Create main.conf with a few sensible rules; you don’t need the massive OWASP CRS unless you love false positives:

sudo tee /etc/nginx/modsec/main.conf > /dev/null <<'EOF'
Include /usr/local/modsecurity/etc/base_rules/*.conf
# Simple SQLi check
SecRule ARGS "@detectSQLi" "id:1001,phase:2,deny,status:403,msg:'SQL Injection Detected'"
EOF

I’ve seen the CRS grind a busy site to a crawl because it throws away legitimate traffic. Start small, then add rules as you discover actual attacks.

Test and reload

sudo nginx -t # syntax check
sudo systemctl restart nginx

A quick curl test with a known attack string should return 403:

curl "http://yourdomain.com/?id=1' OR '1'='1"

If you get 200, double‑check that modsecurity on; is inside the correct server block and that the rules file path matches.

Keep it tidy

  • Log rotation: ModSecurity writes to /var/log/modsec_audit.log. Add a logrotate entry so the file doesn’t fill your disk.
  • Rule updates: When you finally decide to pull in more rules, do it incrementally and watch the error log for “[error] ModSecurity: Access denied with code 403”.

That’s it. You now have a functional WAF sitting in front of Nginx, and you avoided the common pitfall of loading a bloated rule set that chokes your site.