Install Nginx with ModSecurity 3 on Ubuntu 22.04 LTS
You’ll get a working Nginx that talks to the latest ModSecurity 3 WAF, ready for custom rule sets and basic protection. The guide assumes a fresh Ubuntu 22.04 install but works just as well on an existing server.
Prerequisites
First make sure your system is up‑to‑date and you have the build tools Nginx needs.
sudo apt update && sudo apt upgrade -y
sudo apt install -y git gcc make libpcre3-dev zlib1g-dev \
libssl-dev wget curl gnupg2 ca-certificates lsb-release
Updating packages prevents weird compile errors later, and the dev libraries are required to build Nginx modules.
Grab ModSecurity 3 (Libmodsecurity)
Ubuntu’s repositories still ship an old 2.x branch, so we’ll pull the current source.
cd /usr/local/src
sudo git clone --depth 1 -b v3/master https://github.com/SpiderLabs/ModSecurity
cd ModSecurity
git submodule init && git submodule update
sudo ./build.sh
sudo ./configure
sudo make
sudo make install
make install puts the library files in /usr/local/modsecurity. I’ve seen people skip submodule update and end up with a broken build; don’t do that.
Compile Nginx with the ModSecurity module
We need a version of Nginx that includes the dynamic module. Using the official Ubuntu package won’t work because it’s compiled without ModSecurity support.
cd /usr/local/src
sudo wget http://nginx.org/download/nginx-1.24.0.tar.gz
sudo tar xf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# Pull the connector that bridges Nginx and ModSecurity
sudo git clone https://github.com/SpiderLabs/ModSecurity-nginx.git
# Build Nginx with the dynamic module enabled
sudo ./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--modules-path=/usr/lib/nginx/modules \
--with-http_ssl_module \
--add-dynamic-module=./ModSecurity-nginx
sudo make
sudo make install
The --add-dynamic-module flag tells the compiler to create a separate .so file you can enable or disable without recompiling Nginx later. If you skip this and try to load the module statically, you’ll get “module not found” errors.
Enable the ModSecurity module in Nginx
Create a small config snippet that loads the module and points it at the library we built earlier.
sudo mkdir -p /etc/nginx/modules-enabled
echo "load_module modules/ngx_http_modsecurity_module.so;" | sudo tee /etc/nginx/modules-enabled/modsecurity.conf
Now tell ModSecurity where its configuration lives.
sudo mkdir -p /etc/nginx/modsec
sudo cp /usr/local/modsecurity/include/modsecurity.h /etc/nginx/modsec/
Create the core ModSecurity config file:
sudo tee /etc/nginx/modsec/modsecurity.conf > /dev/null <<'EOF'
SecRuleEngine On
SecDefaultAction "phase:1,log,auditlog,pass"
Include /usr/local/modsecurity/etc/modsecurity/*.conf
EOF
I usually copy the example rules that come with ModSecurity into /usr/local/modsecurity/etc/modsecurity/. The default rule set is decent for getting started, but you’ll want to trim it down later – the out‑of‑the‑box list can cause false positives on a busy site.
Hook ModSecurity into your server block
Edit any virtual host you want protected and add the directive right after listen.
server {
listen 80;
server_name example.com;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
The modsecurity on; line turns the WAF on for that host only, which is handy when you’re testing changes.
Test the installation
Restart Nginx and watch the logs.
sudo systemctl restart nginx
sudo tail -f /var/log/nginx/error.log /var/log/modsec_audit.log
Try a simple attack string:
curl "http://example.com/?id=1' or '1'='1"
You should see an entry in the audit log and a 403 response if the rule set caught it. If Nginx refuses to start, double‑check that the load_module line points at the correct .so file; I’ve seen mismatched paths cause cryptic “invalid module” errors.
Optional tweaks
- Performance: Turn on caching for ModSecurity by adding SecCacheTransformations On in the config.
- Rule management: The free Core Rule Set (CRS) is a good baseline, but you’ll probably want to disable rules that trip on legitimate traffic. Use SecRuleRemoveById to silence noisy IDs.
- Automatic updates: Set up a cron job that pulls the latest CRS from GitHub and reloads Nginx.
That’s it – Nginx now talks to ModSecurity 3, and you have a basic WAF in place. Feel free to experiment with custom rules; the more you tinker, the better your site will defend itself.