Implementing a WAF like ModSecurity on Nginx

By Raman Kumar

Updated on May 28, 2025

In this tutorial, we're implementing a WAF like ModSecurity on Nginx.

Introduction

Web applications face a relentless barrage of attacks—from automated scanners probing for SQL injections to sophisticated botnets launching cross-site scripting exploits. As developers and operators, we need more than just secure code; we need a proactive shield at the edge of our infrastructure. By embedding a Web Application Firewall (WAF) like ModSecurity directly into Nginx, we gain a powerful defense layer that inspects, logs, and, when necessary, blocks malicious requests before they ever touch our application servers.

We’ll guide you through every step of turning Nginx into a robust gatekeeper: from installing the ModSecurity engine and Nginx connector, to integrating the OWASP Core Rule Set for comprehensive coverage, all the way through advanced tuning, performance optimizations, and automated rule updates.

Whether you’re running a small startup site or managing enterprise traffic at scale, our goal is to help you deploy a WAF that delivers maximum protection with minimal overhead—and to give you the knowledge to adapt and evolve your defenses as attacks grow more sophisticated. Let’s get started.

Why a WAF on Nginx?

We often see Nginx serve as the front door for high-performance web applications. By embedding a WAF at this layer, we can:

Block common attacks (SQLi, XSS) before they ever hit our application stack.
Gain visibility into suspicious traffic patterns via detailed audit logs.
Scale protection without introducing a separate proxy or appliance.

Prerequisites

Before we begin:

  • Linux disto installed dedicated server or KVM VPS.
  • Apache installed and running.
  • Root or sudo privileges.
  • Familiarity with Apache configuration and basic command-line usage.

Implementing a WAF like ModSecurity on Nginx

1. Prepare Your Environment

Choose a supported OS—we recommend Ubuntu 22.04 LTS or CentOS 8+, which have the requisite build tools. For this demostration purpose we're using Ubuntu 24.04 server.

Install dependencies:

sudo apt update && sudo apt install -y \
  git build-essential libpcre3 libpcre3-dev \
  libssl-dev zlib1g zlib1g-dev pkg-config \
  libpcre2-dev \
  libmaxminddb-dev \
  libgeoip-dev \
  libfuzzy-dev \
  libyajl-dev \
  autoconf \
  automake \
  libtool \
  pkg-config \
  libpcre3-dev \
  libyajl-dev \
  git build-essential

For RHEL based OS:

sudo yum install -y \
  autoconf \
  automake \
  libtool \
  pkgconfig \
  pcre-devel \
  yajl-devel \
  gcc \
  gcc-c++ \
  make \
  git \
  pcre2-devel \
  libmaxminddb-devel \
  GeoIP-devel \
  ssdeep-devel \
  yajl-devel

Decide on rule sources: we’ll use the OWASP Core Rule Set (CRS) v4.x as our baseline.

2. Build and Install ModSecurity v3

ModSecurity v3 is a standalone engine written in C++; the Nginx connector plugs into it.

Clone the ModSecurity v3 repo:

git clone --depth 1 https://github.com/SpiderLabs/ModSecurity
cd ModSecurity
git submodule init && git submodule update

Compile:

./build.sh
./configure
make -j$(nproc)
sudo make install

Verify that /usr/local/modsecurity/lib/libmodsecurity.so exists—this is our core library.

3. Compile the Nginx ModSecurity Connector

Get nginx-modsecurity:

cd ~
git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git

Rebuild Nginx with the connector (assuming you build Nginx from source):

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

Enable the module by adding in /etc/nginx/nginx.conf:

load_module modules/ngx_http_modsecurity_module.so;

4. Configure ModSecurity Core Settings

Create a global ModSecurity configuration at /etc/nginx/modsecurity.conf:

# Turn on the engine in detection mode initially
SecRuleEngine DetectionOnly

# Point at our audit log
SecAuditEngine RelevantOnly
SecAuditLog /var/log/nginx/modsec_audit.log

# Performance tuning
SecRequestBodyInMemoryLimit 13107200
SecRequestBodyLimit 52428800
SecPcreMatchLimit 150000
SecPcreMatchLimitRecursion 150000

Include this file in every server block (we’ll show that in step 6).

5. Install and Enable OWASP CRS

Fetch the latest CRS:

cd /etc/nginx/
git clone --depth 1 https://github.com/coreruleset/coreruleset.git owasp-crs
cd owasp-crs
cp crs-setup.conf.example crs-setup.conf

Adjust crs-setup.conf: we can set anomaly thresholds, skip problematic rules, and tune parameters that suit our application’s needs.

6. Integrate ModSecurity with Nginx

In your main nginx.conf or in each site’s server {} block:

# Load our ModSecurity settings
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity.conf;

# Include OWASP rules
modsecurity_rules_file /etc/nginx/owasp-crs/crs-setup.conf;
modsecurity_rules_file /etc/nginx/owasp-crs/rules/*.conf;

Reload Nginx to verify there are no syntax errors:

sudo nginx -t && sudo systemctl reload nginx

7. Test Basic Protection

We start in DetectionOnly mode so we can see what would be blocked without impacting real users.

Trigger a test rule:

curl 'http://ourdomain.test/?id=1%3Cscript%3Ealert(1)%3C%2Fscript%3E'

Inspect /var/log/nginx/modsec_audit.log for a matching entry.

Once we’re confident false-positive rate is acceptable, switch to SecRuleEngine On to actively block attacks.

8. Audit Logging and Monitoring

RelevantOnly logging keeps only suspicious entries; switch to On if you need full request/response logs for deep forensics.

Integrate with ELK/Graylog by forwarding logs:

filebeat.inputs:
- type: log
  paths:
    - /var/log/nginx/modsec_audit.log

Visualize rule hit counts and top offending IPs in Kibana.

9. Tuning and False-Positive Reduction

Adjust anomaly scoring in crs-setup.conf: set tx.crs_anomaly_score_threshold to a higher number if the CRS is too aggressive.

Whitelist safe endpoints using SecRuleRemoveById:

modsecurity_rules '
  SecRuleRemoveById 920350    # Example rule ID for path-based false positive
';

Create custom SecRule to allow known-safe parameters or apply stricter patterns on sensitive endpoints.

10. Advanced Techniques

Dynamic IP Reputation: integrate with services like AbuseIPDB to block known bad actors via SecRule REMOTE_ADDR "@ipMatchFromFile /etc/nginx/blocked_ips.txt" "id:1000001,phase:1,deny,log".

Geo-blocking: deny traffic from entire regions with:

SecGeoLookupDb /usr/share/GeoIP/GeoLite2-Country.mmdb
SecRule GEO:COUNTRY_CODE "@pm CN RU KP" "id:1000002,phase:1,deny,log,msg:'Blocked high-risk region'"

Rate limiting integration: combine limit_req_zone with ModSecurity to further throttle abusive clients before hitting the WAF.

11. Performance Considerations

Disable request body processing for static assets by wrapping modsecurity off; around location ~* \.(jpg|css|js)$ { … }.

Utilize caching: set SecCacheStoreLevel to reduce repeated rule evaluations for similar requests.

Benchmark impact using tools like wrk or ab, and consider offloading static content to a CDN.

12. Keeping Rules Up to Date

Automate CRS updates with a daily cron job:

0 3 * * * cd /etc/nginx/owasp-crs && git pull --ff-only

Review new rule IDs after each update—some may introduce new false positives.

Wrapping Up

In this tutorial, we've implemented a WAF like ModSecurity on Nginx. By following these steps, we’ve turned our Nginx server into a hardened gatekeeper—able to detect, log, and block a wide array of web attacks with ModSecurity and the OWASP CRS. We encourage continuous monitoring, regular rule updates, and iterative tuning to maintain the right balance between tight security and legitimate traffic flow.

Happy securing!