Configure a production-grade web application firewall using NGINX with ModSecurity 3 and OWASP Core Rule Set. Includes logging, monitoring, and fail2ban integration for comprehensive threat protection.
Prerequisites
- Root or sudo access
- Basic NGINX knowledge
- 4GB+ RAM recommended
What this solves
A web application firewall (WAF) protects your applications from common attacks like SQL injection, XSS, and CSRF by filtering HTTP traffic before it reaches your backend services. This tutorial sets up NGINX with ModSecurity 3 and the OWASP Core Rule Set (CRS) to provide enterprise-grade protection for your web applications. You'll also configure logging, monitoring, and fail2ban integration for automated threat response.
Step-by-step installation
Update system packages
Update your package manager to ensure you get the latest security patches and package versions.
sudo apt update && sudo apt upgrade -yInstall dependencies and build tools
Install the required packages for compiling ModSecurity and the NGINX connector module.
sudo apt install -y build-essential git autoconf automake libtool pkg-config
sudo apt install -y libcurl4-openssl-dev liblmdb-dev libpcre3-dev libyajl-dev
sudo apt install -y nginx-core nginx-dev libmaxminddb-devDownload and compile ModSecurity 3
Clone the ModSecurity repository and compile the library from source for optimal performance and latest features.
cd /opt
sudo git clone --depth 1 -b v3.0.12 --single-branch https://github.com/owasp-modsecurity/ModSecurity
sudo chown -R $USER:$USER ModSecurity
cd ModSecurity
git submodule init
git submodule updateConfigure and compile ModSecurity with optimized settings.
./build.sh
./configure --with-lmdb
make -j$(nproc)
sudo make installBuild the NGINX ModSecurity connector
Download and compile the ModSecurity-nginx connector to integrate ModSecurity with NGINX.
cd /opt
sudo git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx.git
sudo chown -R $USER:$USER ModSecurity-nginxGet NGINX source code to build the dynamic module.
nginx -v
NGINX_VERSION=$(nginx -v 2>&1 | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
tar -xzf nginx-${NGINX_VERSION}.tar.gz
cd nginx-${NGINX_VERSION}Configure NGINX with the ModSecurity module and compile.
./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
make modules
sudo cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/Configure NGINX to load ModSecurity
Enable the ModSecurity module in NGINX configuration.
load_module modules/ngx_http_modsecurity_module.so;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Logging format with security info
log_format security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log security;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}Download and configure OWASP Core Rule Set
Download the latest OWASP CRS rules for comprehensive protection against web application attacks.
sudo mkdir -p /etc/nginx/modsec
cd /etc/nginx/modsec
sudo wget https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.0.0.tar.gz
sudo tar -xzf v4.0.0.tar.gz
sudo mv coreruleset-4.0.0 crs
sudo chown -R www-data:www-data /etc/nginx/modsecConfigure the main ModSecurity configuration file.
# ModSecurity configuration
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
SecRequestBodyJsonDepthLimit 512
SecRequestBodyInMemoryLimit 131072
SecDataDir /tmp/
SecTmpDir /tmp/
Upload directory
SecUploadDir /tmp/
SecUploadKeepFiles RelevantOnly
SecUploadFileMode 0600
Debug and audit logging
SecDebugLog /var/log/nginx/modsec_debug.log
SecDebugLogLevel 0
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABDEFHIJZ
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log
Request/Response handling
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile unicode.mapping 20127
Rule compatibility
SecCollectionTimeout 600
SecHttpBlKey whdkfieyhtnf
Include OWASP CRS
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.confConfigure CRS setup and exclusions
Create a custom CRS configuration with exclusions for common false positives.
# Custom CRS configuration and exclusions
Set paranoia level (1-4, higher = more strict)
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=2"
Set anomaly scoring thresholds
SecAction "id:900110,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold=5"
SecAction "id:900111,phase:1,nolog,pass,t:none,setvar:tx.outbound_anomaly_score_threshold=4"
Allowed request methods
SecAction "id:900200,phase:1,nolog,pass,t:none,setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE'"
Allowed file extensions
SecAction "id:900220,phase:1,nolog,pass,t:none,setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json|'"
Common exclusions for legitimate traffic
WordPress admin exclusions
SecRule REQUEST_FILENAME "@beginsWith /wp-admin/" "id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
SecRule REQUEST_FILENAME "@beginsWith /wp-admin/" "id:1002,phase:1,pass,nolog,ctl:ruleRemoveById=941160"
API endpoint exclusions
SecRule REQUEST_FILENAME "@beginsWith /api/" "id:1003,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
SecRule REQUEST_FILENAME "@beginsWith /api/" "id:1004,phase:1,pass,nolog,ctl:ruleRemoveById=920230"
Upload form exclusions
SecRule REQUEST_FILENAME "@contains upload" "id:1005,phase:1,pass,nolog,ctl:ruleRemoveById=200003"
Rate limiting rules
SecRule IP:BF_COUNTER "@gt 20" "id:1100,phase:1,deny,status:429,msg:'Rate limit exceeded',logdata:'Rate limit exceeded for IP %{REMOTE_ADDR}'"
SecAction "id:1101,phase:5,pass,nolog,setvar:IP.BF_COUNTER=+1,expirevar:IP.BF_COUNTER=300"Create virtual host with ModSecurity
Configure an NGINX virtual host with ModSecurity protection enabled.
server {
listen 80;
server_name example.com www.example.com;
# Enable ModSecurity
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
# Additional security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Document root
root /var/www/html;
index index.html index.php;
# Main location
location / {
try_files $uri $uri/ =404;
# Apply rate limiting
limit_req zone=api burst=20 nodelay;
}
# API endpoints with stricter limiting
location /api/ {
limit_req zone=api burst=5 nodelay;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Login endpoints
location /login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://backend;
}
# Block common attack patterns
location ~* \.(sql|bak|old|backup)$ {
deny all;
return 404;
}
# Security logging
access_log /var/log/nginx/waf_access.log security;
error_log /var/log/nginx/waf_error.log;
}
Backend upstream
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}Enable the site and create necessary directories.
sudo ln -s /etc/nginx/sites-available/waf-protected /etc/nginx/sites-enabled/
sudo mkdir -p /var/www/html
sudo chown -R www-data:www-data /var/www/html
sudo chmod 755 /var/www/htmlConfigure log rotation
Set up log rotation for ModSecurity and NGINX logs to prevent disk space issues.
/var/log/nginx/modsec_*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi
endscript
postrotate
invoke-rc.d nginx reload >/dev/null 2>&1
endscript
}
/var/log/nginx/waf_*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
invoke-rc.d nginx reload >/dev/null 2>&1
endscript
}Install and configure fail2ban integration
Install fail2ban to automatically block IPs that trigger ModSecurity rules repeatedly.
sudo apt install -y fail2banCreate a custom fail2ban filter for ModSecurity.
[Definition]
failregex = ^.\[.\] ModSecurity: Access denied with code 403.\[client \]. $
^.\[.\] ModSecurity: Warning.\[client \]. $
^."(GET|POST)." 403.*$
ignoreregex = Configure fail2ban jail for ModSecurity violations.
[nginx-modsecurity]
enabled = true
port = http,https
filter = nginx-modsecurity
logpath = /var/log/nginx/modsec_audit.log
/var/log/nginx/error.log
maxretry = 3
bantime = 3600
findtime = 600
action = iptables-multiport[name=nginx-modsecurity, port="http,https", protocol=tcp]
sendmail-whois[name=nginx-modsecurity, dest=admin@example.com]Start and enable services
Enable and start NGINX and fail2ban services with proper startup configuration.
sudo nginx -t
sudo systemctl enable nginx fail2ban
sudo systemctl restart nginx fail2ban
sudo systemctl status nginx fail2banConfigure monitoring and alerting
Set up log monitoring script
Create a monitoring script to track ModSecurity activity and send alerts.
#!/bin/bash
ModSecurity monitoring script
LOGFILE="/var/log/nginx/modsec_audit.log"
ALERT_EMAIL="admin@example.com"
LAST_CHECK="/tmp/modsec_last_check"
Create last check file if it doesn't exist
if [ ! -f "$LAST_CHECK" ]; then
echo $(date +%s) > "$LAST_CHECK"
fi
LAST_TIME=$(cat "$LAST_CHECK")
CURRENT_TIME=$(date +%s)
Find new attacks since last check
NEW_ATTACKS=$(awk -v start="$LAST_TIME" '
/ModSecurity: Access denied/ {
cmd="date -d \"" $4 " " $5 "\" +%s"
cmd | getline timestamp
close(cmd)
if (timestamp > start) {
attacks++
print $0
}
}
END { print "Total new attacks:" attacks+0 }
' "$LOGFILE")
ATTACK_COUNT=$(echo "$NEW_ATTACKS" | tail -n1 | grep -o '[0-9]\+' || echo "0")
Send alert if attacks detected
if [ "$ATTACK_COUNT" -gt 0 ]; then
echo "ModSecurity detected $ATTACK_COUNT new attacks" | \
mail -s "WAF Alert: $ATTACK_COUNT attacks blocked" "$ALERT_EMAIL"
fi
Update last check time
echo "$CURRENT_TIME" > "$LAST_CHECK"
Make the script executable and set up a cron job.
sudo chmod +x /opt/modsec-monitor.sh
sudo chown root:root /opt/modsec-monitor.sh
Add to crontab for monitoring every 15 minutes
echo "/15 * /opt/modsec-monitor.sh" | sudo crontab -Configure Prometheus metrics (optional)
If you use Prometheus monitoring, create a metrics exporter for ModSecurity.
#!/usr/bin/env python3
import re
import time
from prometheus_client import start_http_server, Counter, Histogram
from prometheus_client.core import CollectorRegistry
Metrics
registry = CollectorRegistry()
attacks_total = Counter('modsecurity_attacks_total',
'Total number of blocked attacks',
['rule_id', 'severity'], registry=registry)
response_time = Histogram('modsecurity_response_time_seconds',
'Response time for requests', registry=registry)
def parse_modsec_log():
"""Parse ModSecurity audit log for metrics"""
try:
with open('/var/log/nginx/modsec_audit.log', 'r') as f:
# Read new lines since last position
for line in f:
if 'Access denied' in line:
# Extract rule ID and severity
rule_match = re.search(r'\[id "(\d+)"\]', line)
severity_match = re.search(r'\[severity "(\w+)"\]', line)
rule_id = rule_match.group(1) if rule_match else 'unknown'
severity = severity_match.group(1) if severity_match else 'unknown'
attacks_total.labels(rule_id=rule_id, severity=severity).inc()
except FileNotFoundError:
pass
if __name__ == '__main__':
start_http_server(9113, registry=registry)
while True:
parse_modsec_log()
time.sleep(30)
Install Python dependencies and create a systemd service.
sudo apt install -y python3-pip
sudo pip3 install prometheus_client[Unit]
Description=ModSecurity Prometheus Exporter
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
ExecStart=/usr/bin/python3 /opt/modsec-prometheus.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now modsec-exporterVerify your setup
Test that ModSecurity is working correctly and blocking malicious requests.
# Check NGINX configuration
sudo nginx -t
Verify ModSecurity module is loaded
sudo nginx -V 2>&1 | grep -o with-http_modsecurity_module
Check service status
sudo systemctl status nginx fail2ban
Test basic attack pattern (should be blocked)
curl -X GET "http://example.com/?id=1' OR '1'='1"
Check ModSecurity logs
sudo tail -f /var/log/nginx/modsec_audit.log
Verify fail2ban is monitoring
sudo fail2ban-client status nginx-modsecurityYou can also use NGINX reverse proxy configuration to protect backend applications while maintaining performance.
Performance tuning
Optimize ModSecurity performance
Adjust ModSecurity settings for better performance in high-traffic environments.
# Performance optimizations
SecRequestBodyInMemoryLimit 262144
SecRequestBodyLimit 52428800
SecRequestBodyNoFilesLimit 262144
Reduce rule processing overhead
SecRuleEngine DetectionOnly
SecAuditEngine Off
SecDebugLogLevel 0
Optimize rule matching
SecPcreMatchLimit 100000
SecPcreMatchLimitRecursion 100000
Collection timeout optimization
SecCollectionTimeout 3600Configure response caching
Enable response caching for non-sensitive content to reduce server load.
# Cache configuration
proxy_cache_path /var/cache/nginx/waf levels=1:2 keys_zone=waf_cache:100m
inactive=1h max_size=1g use_temp_path=off;
map $request_method $cache_bypass {
POST 1;
PUT 1;
DELETE 1;
PATCH 1;
default 0;
}
Add to server block
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
# Cache responses
proxy_cache waf_cache;
proxy_cache_bypass $cache_bypass;
proxy_cache_valid 200 1h;
proxy_cache_valid 404 1m;
}Create cache directory with proper permissions.
sudo mkdir -p /var/cache/nginx/waf
sudo chown -R www-data:www-data /var/cache/nginx
sudo chmod 755 /var/cache/nginxCommon issues
| Symptom | Cause | Fix |
|---|---|---|
| NGINX fails to start | ModSecurity module not found | Check module path: ls -la /etc/nginx/modules/ |
| False positive blocks | CRS rules too strict | Add exclusions to /etc/nginx/modsec/crs-custom.conf |
| High memory usage | Large request body limits | Reduce SecRequestBodyLimit in main.conf |
| Slow response times | Debug logging enabled | Set SecDebugLogLevel 0 in production |
| Log files growing too large | No log rotation configured | Check logrotate: sudo logrotate -f /etc/logrotate.d/nginx-modsecurity |
| fail2ban not blocking IPs | Wrong log path or regex | Test filter: sudo fail2ban-regex /var/log/nginx/modsec_audit.log /etc/fail2ban/filter.d/nginx-modsecurity.conf |
SecRuleEngine DetectionOnly to log without blocking, then analyze the logs before re-enabling protection.Next steps
- Implement Apache web application firewall with ModSecurity 3 for Apache-based deployments
- Set up intrusion detection with OSSEC HIDS for comprehensive security monitoring
- Configure NGINX load balancing with health checks for high availability WAF deployments
- Set up centralized log aggregation with ELK Stack for advanced WAF analytics
- Implement HAProxy rate limiting and DDoS protection for additional security layers
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# NGINX ModSecurity WAF Installation Script
# Supports Ubuntu/Debian and RHEL-based distributions
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
DOMAIN="${1:-example.com}"
MODSEC_VERSION="v3.0.12"
CRS_VERSION="v4.0.0"
# Usage function
usage() {
echo "Usage: $0 [domain]"
echo "Example: $0 mywebsite.com"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup on error
cleanup() {
log_error "Installation failed. Cleaning up..."
cd /
rm -rf /opt/ModSecurity /opt/ModSecurity-nginx /opt/nginx-* 2>/dev/null || true
exit 1
}
trap cleanup ERR
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_CONF_DIR="/etc/nginx"
NGINX_MODULES_DIR="/etc/nginx/modules"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
NGINX_USER="www-data"
BUILD_DEPS="build-essential git autoconf automake libtool pkg-config libcurl4-openssl-dev liblmdb-dev libpcre3-dev libyajl-dev nginx-core nginx-dev libmaxminddb-dev"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_CONF_DIR="/etc/nginx"
NGINX_MODULES_DIR="/etc/nginx/modules"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
NGINX_USER="nginx"
BUILD_DEPS="gcc gcc-c++ make git autoconf automake libtool pkgconfig libcurl-devel lmdb-devel pcre-devel yajl-devel nginx nginx-devel libmaxminddb-devel"
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
BUILD_DEPS="$BUILD_DEPS epel-release"
fi
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_CONF_DIR="/etc/nginx"
NGINX_MODULES_DIR="/etc/nginx/modules"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
NGINX_USER="nginx"
BUILD_DEPS="gcc gcc-c++ make git autoconf automake libtool pkgconfig libcurl-devel lmdb-devel pcre-devel yajl-devel nginx nginx-devel"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "[1/12] Updating system packages..."
$PKG_UPDATE
log_info "[2/12] Installing dependencies and build tools..."
if [[ "$PKG_MGR" == "dnf" || "$PKG_MGR" == "yum" ]]; then
$PKG_INSTALL epel-release 2>/dev/null || true
if [[ "$ID" == "centos" || "$ID" == "rhel" ]]; then
dnf groupinstall -y "Development Tools" 2>/dev/null || yum groupinstall -y "Development Tools"
fi
fi
$PKG_INSTALL $BUILD_DEPS
log_info "[3/12] Creating directories and setting up environment..."
mkdir -p /opt /var/log/nginx $NGINX_MODULES_DIR
cd /opt
log_info "[4/12] Downloading ModSecurity 3..."
if [ -d "ModSecurity" ]; then
rm -rf ModSecurity
fi
git clone --depth 1 -b $MODSEC_VERSION --single-branch https://github.com/owasp-modsecurity/ModSecurity
cd ModSecurity
git submodule init
git submodule update
log_info "[5/12] Compiling ModSecurity 3..."
./build.sh
./configure --with-lmdb
make -j$(nproc)
make install
ldconfig
log_info "[6/12] Downloading ModSecurity-NGINX connector..."
cd /opt
if [ -d "ModSecurity-nginx" ]; then
rm -rf ModSecurity-nginx
fi
git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx.git
log_info "[7/12] Building NGINX ModSecurity module..."
NGINX_VERSION=$(nginx -v 2>&1 | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
wget -q http://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
make modules
cp objs/ngx_http_modsecurity_module.so $NGINX_MODULES_DIR/
log_info "[8/12] Configuring NGINX to load ModSecurity..."
cat > $NGINX_CONF_DIR/nginx.conf << 'EOF'
load_module modules/ngx_http_modsecurity_module.so;
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Logging
log_format security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time';
access_log /var/log/nginx/access.log security;
error_log /var/log/nginx/error.log warn;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/* 2>/dev/null;
}
EOF
# Update user for RHEL-based systems
if [[ "$PKG_MGR" == "dnf" || "$PKG_MGR" == "yum" ]]; then
sed -i "s/user www-data;/user $NGINX_USER;/" $NGINX_CONF_DIR/nginx.conf
fi
log_info "[9/12] Setting up OWASP Core Rule Set..."
mkdir -p $NGINX_CONF_DIR/modsec
cd $NGINX_CONF_DIR/modsec
wget -q https://github.com/coreruleset/coreruleset/archive/refs/tags/$CRS_VERSION.tar.gz
tar -xzf $CRS_VERSION.tar.gz
mv coreruleset-* crs
cp crs/crs-setup.conf.example crs/crs-setup.conf
log_info "[10/12] Creating ModSecurity configuration..."
cat > $NGINX_CONF_DIR/modsec/modsecurity.conf << 'EOF'
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyInMemoryLimit 131072
SecRequestBodyLimitAction Reject
SecPcreMatchLimit 1000
SecPcreMatchLimitRecursion 1000
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
SecTmpDir /tmp/
SecDataDir /tmp/
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABDEFHIJZ
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log
SecArgumentSeparator &
SecCookieFormat 0
SecUnicodeMapFile unicode.mapping 20127
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.conf
EOF
log_info "[11/12] Creating site configuration..."
SITE_CONF="$NGINX_SITES_DIR/default"
if [[ "$NGINX_SITES_DIR" == "/etc/nginx/conf.d" ]]; then
SITE_CONF="$NGINX_SITES_DIR/default.conf"
fi
cat > $SITE_CONF << EOF
server {
listen 80;
server_name $DOMAIN;
modsecurity on;
modsecurity_rules_file $NGINX_CONF_DIR/modsec/modsecurity.conf;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
# Enable site for Debian-based systems
if [[ "$PKG_MGR" == "apt" ]]; then
ln -sf $SITE_CONF $NGINX_ENABLED_DIR/default
rm -f $NGINX_ENABLED_DIR/default.dpkg-dist 2>/dev/null || true
fi
log_info "[12/12] Setting permissions and starting services..."
chown -R $NGINX_USER:$NGINX_USER $NGINX_CONF_DIR/modsec
chmod -R 755 $NGINX_CONF_DIR/modsec
chmod 644 $NGINX_CONF_DIR/modsec/modsecurity.conf
# Test NGINX configuration
nginx -t
# Start and enable NGINX
systemctl enable nginx
systemctl restart nginx
# Configure firewall if available
if command -v ufw &> /dev/null; then
ufw --force enable
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
elif command -v firewall-cmd &> /dev/null; then
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
# Cleanup build files
cd /
rm -rf /opt/nginx-* /opt/ModSecurity /opt/ModSecurity-nginx /opt/*.tar.gz
log_info "ModSecurity WAF installation completed successfully!"
log_info "NGINX is now running with ModSecurity and OWASP CRS protection"
log_info "Configuration files:"
log_info " - NGINX config: $NGINX_CONF_DIR/nginx.conf"
log_info " - ModSecurity config: $NGINX_CONF_DIR/modsec/modsecurity.conf"
log_info " - Site config: $SITE_CONF"
log_info " - Audit log: /var/log/nginx/modsec_audit.log"
# Verification
if systemctl is-active --quiet nginx && nginx -t &>/dev/null; then
log_info "✓ NGINX is running and configuration is valid"
else
log_error "✗ NGINX configuration test failed"
exit 1
fi
Review the script before running. Execute with: bash install.sh