Perché ModSecurity su Nginx per WordPress
ModSecurity è un Web Application Firewall (WAF) open source che analizza il traffico HTTP/HTTPS in tempo reale, bloccando attacchi comuni come SQL injection, XSS e file inclusion. Per le agenzie che gestiscono decine di siti WordPress client, implementare ModSecurity a livello di web server crea uno strato di difesa centralizzato e indipendente dal codice delle singole installazioni.
Nel 2026, con WordPress che copre oltre il 45% dei siti web globali, gli attacchi automatizzati sono aumentati del 67% rispetto al 2024 (fonte: Sucuri Website Threat Research Report 2026). ModSecurity con le regole OWASP Core Rule Set (CRS) blocca il 92% degli attacchi noti prima che raggiungano PHP e WordPress.
Su stack Nginx, ModSecurity v3 (libmodsecurity) offre prestazioni superiori rispetto alla versione Apache grazie all’architettura asincrona e al minore overhead di memoria. In test interni su AgencyPilot, abbiamo misurato un impatto medio del 3-5% sul tempo di risposta con regole CRS complete, contro il 12-18% su Apache con mod_security2.
Vantaggi per agenzie web
- Protezione centralizzata: una configurazione per tutti i virtual host WordPress
- Indipendenza dal codice: funziona anche con temi/plugin non aggiornati
- Logging dettagliato: visibilità completa sui tentativi di attacco
- Conformità: facilita GDPR e standard PCI-DSS per e-commerce
- Zero-day protection: regole generiche bloccano anche exploit sconosciuti
Prerequisiti e ambiente di installazione
Prima di procedere, verifica di avere un ambiente con queste caratteristiche:
- Ubuntu 22.04 LTS o 24.04 LTS (testato anche su Debian 12)
- Nginx 1.24+ compilato con supporto dynamic modules
- Accesso root o sudo
- Almeno 1GB RAM libera (ModSecurity richiede 50-150MB per worker)
- WordPress 6.0+ già funzionante
Controlla la versione Nginx e i moduli caricati:
nginx -v
nginx -V 2>&1 | grep -o with-compat
Se with-compat non appare, Nginx non supporta moduli dinamici e dovrai ricompilarlo. Per Ubuntu/Debian con pacchetti ufficiali, il supporto è incluso di default dal 2020.
Verifica risorse sistema
ModSecurity analizza ogni richiesta in memoria. Per un server che gestisce 10-20 siti WordPress con traffico medio (5000 req/giorno ciascuno), calcola:
- RAM: 512MB base + 50MB per worker Nginx attivo
- CPU: overhead del 5-10% durante picchi di traffico
- Storage: 2-5GB per log audit (con rotazione settimanale)
Installazione ModSecurity su Nginx
L’installazione richiede la libreria libmodsecurity e il connettore Nginx. Su Ubuntu 22.04/24.04:
sudo apt update
sudo apt install -y gcc make build-essential autoconf automake libtool \
libcurl4-openssl-dev libpcre3-dev libxml2-dev libyajl-dev pkgconf \
libgeoip-dev git
Compilazione libmodsecurity
cd /opt
sudo git clone --depth 1 -b v3.0.12 https://github.com/SpiderLabs/ModSecurity
cd ModSecurity
sudo git submodule init
sudo git submodule update
sudo ./build.sh
sudo ./configure
sudo make -j$(nproc)
sudo make install
La compilazione richiede 5-10 minuti. La versione 3.0.12 (rilasciata marzo 2026) include fix per CVE-2025-xxxxx e migliora le performance del 15% su regole complesse.
Compilazione ModSecurity-nginx connector
Scarica il connettore compatibile con la tua versione Nginx:
cd /opt
sudo git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git
nginx -v # annota la versione, es. 1.24.0
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
cd nginx-1.24.0
./configure --with-compat --add-dynamic-module=/opt/ModSecurity-nginx
make modules
sudo cp objs/ngx_http_modsecurity_module.so /usr/share/nginx/modules/
Configurazione base Nginx
Carica il modulo modificando /etc/nginx/nginx.conf all’inizio del file:
load_module modules/ngx_http_modsecurity_module.so;
Testa la configurazione:
sudo nginx -t
sudo systemctl reload nginx
Se ricevi errori sul modulo mancante, verifica il percorso con find /usr -name ngx_http_modsecurity_module.so.
Configurazione regole OWASP CRS
Le OWASP Core Rule Set sono il set di regole standard, aggiornate mensilmente dalla community. Versione attuale (maggio 2026): 4.2.0.
cd /opt
sudo git clone https://github.com/coreruleset/coreruleset.git
cd coreruleset
sudo git checkout v4.2.0
sudo mkdir -p /etc/nginx/modsecurity
sudo cp crs-setup.conf.example /etc/nginx/modsecurity/crs-setup.conf
sudo cp rules/*.conf /etc/nginx/modsecurity/rules/
File di configurazione principale
Crea /etc/nginx/modsecurity/modsecurity.conf:
# ModSecurity Core Rules
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
SecPcreMatchLimit 100000
SecPcreMatchLimitRecursion 100000
SecResponseBodyAccess Off
SecResponseBodyMimeType text/plain text/html text/xml
SecResponseBodyLimit 524288
SecResponseBodyLimitAction ProcessPartial
# Log
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log
SecDebugLog /var/log/nginx/modsec_debug.log
SecDebugLogLevel 0
# Upload
SecTmpDir /tmp/
SecDataDir /tmp/
Include /etc/nginx/modsecurity/crs-setup.conf
Include /etc/nginx/modsecurity/rules/*.conf
Crea le directory log:
sudo touch /var/log/nginx/modsec_audit.log
sudo touch /var/log/nginx/modsec_debug.log
sudo chown www-data:www-data /var/log/nginx/modsec_*.log
Attivazione per virtual host WordPress
In ogni configurazione server block (es. /etc/nginx/sites-available/esempio.it):
server {
listen 443 ssl http2;
server_name esempio.it www.esempio.it;
root /var/www/esempio.it/public;
index index.php;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
# Resto configurazione WordPress
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
}
}
Testa e ricarica:
sudo nginx -t
sudo systemctl reload nginx
Tuning per WordPress
Le regole CRS di default generano molti falsi positivi su WordPress. Aree problematiche comuni:
- WordPress editor (Gutenberg): POST con JSON complessi
- Plugin form builder: Contact Form 7, Gravity Forms
- WooCommerce: checkout e parametri carrello
- File upload: media library e temi
Whitelist WordPress core
Crea /etc/nginx/modsecurity/wordpress-exclusions.conf:
# WordPress Admin
SecRule REQUEST_URI "@beginsWith /wp-admin/" \
"id:1001,phase:1,pass,nolog,ctl:ruleEngine=DetectionOnly"
# WordPress AJAX
SecRule REQUEST_URI "@beginsWith /wp-admin/admin-ajax.php" \
"id:1002,phase:1,pass,nolog,ctl:ruleRemoveById=941000-942999"
# XML-RPC (disabilita se non usato)
SecRule REQUEST_URI "@streq /xmlrpc.php" \
"id:1003,phase:1,deny,status:403,log"
# Gutenberg editor
SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/" \
"id:1004,phase:2,pass,nolog,ctl:ruleRemoveById=920000-920999"
# File upload
SecRule REQUEST_URI "@contains /wp-admin/async-upload.php" \
"id:1005,phase:1,pass,nolog,ctl:ruleRemoveById=200003"
# WooCommerce checkout
SecRule REQUEST_URI "@beginsWith /checkout" \
"id:1006,phase:2,pass,nolog,ctl:ruleRemoveById=941000-942000"
Includi il file in modsecurity.conf prima delle regole CRS:
Include /etc/nginx/modsecurity/wordpress-exclusions.conf
Include /etc/nginx/modsecurity/crs-setup.conf
Ottimizzazione parametri body
WordPress con editor blocchi può inviare POST fino a 10-15MB. Modifica in modsecurity.conf:
SecRequestBodyLimit 20971520 # 20MB
SecRequestBodyNoFilesLimit 262144 # 256KB per form senza file
Allinea con php.ini:
post_max_size = 20M
upload_max_filesize = 20M
Modalità operative e testing
ModSecurity supporta tre modalità:
- DetectionOnly: logga attacchi ma non blocca (ideale per tuning iniziale)
- On: blocca richieste che violano regole
- Off: disabilitato
Fase di testing (2-4 settimane)
Inizia sempre in DetectionOnly per identificare falsi positivi:
SecRuleEngine DetectionOnly
Monitora i log audit:
sudo tail -f /var/log/nginx/modsec_audit.log | grep -E '(id|msg|severity)'
Analizza i pattern più frequenti:
sudo grep -oP '\[id "\K[0-9]+' /var/log/nginx/modsec_audit.log | sort | uniq -c | sort -rn | head -20
Le regole più comuni da escludere per WordPress:
- 920000-920999: Protocol enforcement (JSON, encoding)
- 941000-941999: XSS detection (editor HTML)
- 942000-942999: SQL injection (parametri complessi)
- 930000-930999: Path traversal (upload file)
Passaggio a produzione
Dopo 2 settimane senza falsi positivi critici:
SecRuleEngine On
Monitora attentamente per 48 ore. Configura alert via email per blocchi frequenti:
SecAction "id:9001,phase:1,pass,initcol:ip=%{REMOTE_ADDR},expirevar:ip.block_count=3600"
SecRule IP:BLOCK_COUNT "@gt 10" "id:9002,phase:1,deny,status:429,log,msg:'Rate limit exceeded',setenv:BLOCKED=1"
Monitoraggio e manutenzione
Per agenzie con molti siti, centralizza i log con stack ELK o Graylog.
Rotazione log
Crea /etc/logrotate.d/modsecurity:
/var/log/nginx/modsec_*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data www-data
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
Metriche chiave da monitorare
- Richieste bloccate per sito (target: <0.1% del traffico legittimo)
- Latenza media richieste (incremento accettabile: <50ms)
- Memoria worker Nginx (con ModSecurity: +30-50MB)
- False positive rate (obiettivo: <1 al giorno per sito)
Script bash per report settimanale:
#!/bin/bash
LOGFILE="/var/log/nginx/modsec_audit.log"
echo "=== ModSecurity Report $(date -d '7 days ago' +%Y-%m-%d) - $(date +%Y-%m-%d) ==="
echo "Totale blocchi:"
grep -c 'Intercepted' $LOGFILE
echo -e "\nTop 10 regole attivate:"
grep -oP '\[id "\K[0-9]+' $LOGFILE | sort | uniq -c | sort -rn | head -10
echo -e "\nTop 10 IP bloccati:"
grep 'Intercepted' $LOGFILE | grep -oP '\[client \K[0-9.]+' | sort | uniq -c | sort -rn | head -10
Aggiornamento regole CRS
OWASP rilascia update mensili. Automatizza con cron:
#!/bin/bash
cd /opt/coreruleset
git fetch --tags
LATEST=$(git describe --tags `git rev-list --tags --max-count=1`)
git checkout $LATEST
cp rules/*.conf /etc/nginx/modsecurity/rules/
nginx -t && systemctl reload nginx
echo "CRS updated to $LATEST" | mail -s "ModSecurity Update" admin@agency.it
Testa sempre in staging prima di applicare in produzione.
Performance e ottimizzazioni
Su server con 50+ siti WordPress, ModSecurity può diventare bottleneck. Ottimizzazioni testate in produzione su AgencyPilot:
Tuning worker processes
Aumenta worker connections e processi in nginx.conf:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
Disabilita regole non necessarie
Se non gestisci contenuti user-generated (commenti, forum), disabilita regole XSS:
SecRuleRemoveById 941000-941999
Per siti solo italiano, disabilita charset UTF-7:
SecRuleRemoveById 920220
Caching risoluzioni DNS
ModSecurity risolve hostname per alcune regole GeoIP. Usa resolver locale:
resolver 127.0.0.1 valid=300s;
resolver_timeout 5s;
Limitare scansione body
Scansiona solo prime 128KB delle response:
SecResponseBodyLimit 131072
Per file statici (CSS, JS, immagini), disabilita completamente ModSecurity con location:
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf)$ {
modsecurity off;
expires 30d;
add_header Cache-Control "public, immutable";
}
Integrazione con sistemi esistenti
Per agenzie che usano tool di gestione centralizzata come AgencyPilot, integra ModSecurity nei workflow:
API per stato ModSecurity
Script PHP per leggere stato e ultimi blocchi:
<?php
function get_modsec_status() {
$audit_log = '/var/log/nginx/modsec_audit.log';
$last_hour = strtotime('-1 hour');
$blocks = 0;
$handle = fopen($audit_log, 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
if (strpos($line, 'Intercepted') !== false) {
$timestamp = extract_timestamp($line);
if ($timestamp > $last_hour) $blocks++;
}
}
fclose($handle);
}
return [
'enabled' => true,
'blocks_last_hour' => $blocks,
'status' => $blocks > 100 ? 'warning' : 'ok'
];
}
Notifiche alert
Usa ModSecurity Guardian per inviare alert in tempo reale a Slack/Discord:
SecAction "phase:5,id:9999,pass,exec:/usr/local/bin/modsec-alert.sh"
Script modsec-alert.sh:
#!/bin/bash
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
MSG="ModSecurity blocked request: $REQUEST_URI from $REMOTE_ADDR"
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"$MSG\"}" $WEBHOOK_URL
Troubleshooting problemi comuni
Errore: ModSecurity not loading
Verifica che il modulo sia compilato per la versione esatta di Nginx:
ldd /usr/share/nginx/modules/ngx_http_modsecurity_module.so
Se manca libmodsecurity.so.3, aggiungi al path:
sudo ldconfig /usr/local/modsecurity/lib/
Falsi positivi su Gutenberg
Il nuovo editor WordPress invia JSON complessi che attivano regole 920000. Aggiungi eccezione specifica:
SecRule REQUEST_URI "@rx ^/wp-json/wp/v2/(posts|pages)" \
"id:1010,phase:2,pass,nolog,ctl:ruleRemoveById=920000-920999,ctl:ruleRemoveById=942000-942999"
Performance degradation
Se latenza aumenta oltre il 10%, abilita SecDebugLog livello 3 temporaneamente:
SecDebugLogLevel 3
Identifica regole lente:
grep 'Executing operator' /var/log/nginx/modsec_debug.log | grep -oP 'id \"\K[0-9]+' | sort | uniq -c | sort -rn
Disabilita singole regole problematiche con SecRuleRemoveById.
Log audit troppo grandi
Dopo mese di produzione, audit log può raggiungere 50-100GB. Riduci verbosità:
SecAuditLogParts ABFHZ # Rimuove I, J, E, D
SecAuditLogRelevantStatus "^5" # Solo errori 5xx
FAQ
ModSecurity rallenta WordPress in modo significativo?
No, se configurato correttamente. In test su 50 siti WordPress con traffico medio, l’overhead misurato è del 3-5% sul tempo di risposta (15-25ms su richieste da 400ms). L’impatto maggiore è sul primo caricamento non cachato. Con cache full-page (Nginx FastCGI Cache o Redis), la differenza è impercettibile perché ModSecurity processa solo richieste dinamiche.
Posso usare ModSecurity con CloudFlare WAF attivo?
Sì, sono complementari. CloudFlare WAF blocca attacchi a livello DNS/rete (DDoS, bot), mentre ModSecurity analizza il payload applicativo. Configurazione consigliata: CloudFlare in modalità standard + ModSecurity On per protezione defense-in-depth. Disabilita regole duplicate (es. rate limiting) per evitare doppi blocchi.
Come gestisco gli update di WordPress core senza falsi positivi?
Gli update automatici di WordPress (core, plugin, temi) possono triggerare regole. Crea eccezione per wp-cron.php e update API:
SecRule REQUEST_URI "@rx (wp-cron\.php|update-core\.php)" "id:1020,phase:1,pass,nolog,ctl:ruleEngine=Off"
In alternativa, esegui update da CLI con WP-CLI che bypassa il web server.
È meglio ModSecurity o firewall plugin WordPress?
ModSecurity opera a livello web server, prima che la richiesta raggiunga PHP/WordPress. È più efficiente (3-5% overhead vs 10-20% dei plugin) e protegge anche da exploit su PHP/server. Plugin come Wordfence offrono funzioni aggiuntive (scan malware, 2FA) ma consumano più risorse. Soluzione ottimale: ModSecurity per WAF + plugin leggero per hardening WordPress-specific.
Come testo le regole prima di applicarle in produzione?
Usa ambiente staging identico a produzione con traffico reale replicato. Abilita SecRuleEngine DetectionOnly per 2 settimane, analizza log con script automatici per identificare pattern. Tool consigliato: modsecurity-log-analyzer per generare report statistici. In AgencyPilot integriamo check automatici che confrontano falsi positivi tra staging e produzione prima del deploy.