Un Web Application Firewall (WAF) filtra il traffico HTTP prima che arrivi all’applicazione, bloccando SQL injection, cross-site scripting e decine di altre tecniche di attacco note. Molte aziende pagano servizi cloud per questa protezione, ma esiste un’alternativa open source matura, gratuita e configurabile in autonomia: ModSecurity abbinato all’OWASP Core Rule Set (CRS). In questa guida installiamo ModSecurity da zero su un server Nginx, carichiamo il CRS, testiamo il blocco di attacchi reali simulati e portiamo la configurazione in produzione in 12 passi, con tutti i comandi copiabili e una configurazione finale completa.

Il tutorial è pensato per chi gestisce server Linux con Nginx e vuole un livello di protezione aggiuntivo rispetto al solo hardening applicativo, senza dipendere da un fornitore esterno. Copriamo anche gli errori più comuni, la gestione dei falsi positivi (il problema numero uno di chi adotta un WAF) e una sezione di risoluzione problemi con otto scenari reali.

Per team italiani ed europei la scelta di un WAF self-hosted ha anche un risvolto di governance: tenere il motore di filtraggio e i log delle richieste sulla propria infrastruttura, senza instradare il traffico attraverso un servizio cloud terzo, semplifica la mappatura dei trattamenti di dati personali richiesta dal GDPR e dalle linee guida nazionali in materia di sicurezza. Non è un caso che molte pubbliche amministrazioni e PMI con vincoli di compliance continuino a scegliere ModSecurity invece di affidarsi esclusivamente a un WAF cloud, anche quando quest’ultimo richiederebbe meno lavoro di manutenzione iniziale.

Perché un Web Application Firewall open source conta ancora nel 2026

Il mercato dei Web Application Firewall è dominato da soluzioni cloud a pagamento come Cloudflare WAF o AWS WAF, ma non tutte le applicazioni possono o vogliono instradare il traffico attraverso un provider terzo: vincoli di compliance, latenza, costo per richiesta o semplice preferenza per soluzioni self-hosted spingono ancora molti team verso ModSecurity. È il motore WAF open source più diffuso al mondo, nato come modulo Apache e poi riscritto come libreria standalone (libModSecurity) utilizzabile anche con Nginx tramite un connettore dedicato.

Il punto debole storico di ModSecurity non è mai stato il motore, ma le regole: scrivere e mantenere un rule set di qualità richiede competenze specialistiche che la maggior parte dei team non ha tempo di sviluppare. È qui che entra in gioco l’OWASP Core Rule Set, il progetto di regole generiche mantenuto dalla community OWASP e compatibile sia con ModSecurity su Apache, Nginx e IIS sia con il motore alternativo scritto in Go, Coraza. Il CRS organizza le protezioni in quattro livelli di paranoia (PL1-PL4): più si sale di livello, più aumenta la copertura contro tecniche di evasione sofisticate, ma aumenta anche il rischio di bloccare traffico legittimo.

Un WAF ben configurato non sostituisce la sicurezza a livello di codice: è una barriera aggiuntiva che compra tempo quando una libreria ha una vulnerabilità non ancora patchata, filtra bot e scanner automatici e dà visibilità su cosa viene effettivamente tentato contro l’applicazione attraverso i log di audit. Per i team che già usano strumenti SIEM, i log di ModSecurity sono una fonte di eventi particolarmente ricca da ingerire.

I casi d’uso più comuni restano tre: siti e-commerce che vogliono un filtro aggiuntivo davanti al carrello e al checkout senza spostare tutto su un CDN commerciale, API pubbliche esposte a internet che devono reggere scanner automatici continui, e ambienti con software legacy difficile da aggiornare, dove il WAF funziona come cuscinetto temporaneo mentre si pianifica una migrazione più profonda. In nessuno di questi scenari il WAF deve essere l’unica linea di difesa: resta una delle misure tecniche da documentare insieme a patching regolare, autenticazione forte e logging centralizzato.

Cos’è ModSecurity e il ruolo di OWASP Core Rule Set

ModSecurity funziona come un motore di ispezione che intercetta ogni richiesta HTTP (e opzionalmente la risposta) e la confronta con un insieme di regole scritte in un linguaggio dedicato, SecLang. Dalla versione 3 in avanti il progetto è diviso in due componenti: libModSecurity, la libreria che implementa il motore di regole, e un connettore specifico per ogni web server, perché libModSecurity da sola non sa come agganciarsi al ciclo di vita delle richieste di Nginx o Apache. Per Nginx il connettore si chiama ModSecurity-nginx ed è distribuito come modulo dinamico da caricare nel file di configurazione principale.

Il Core Rule Set, da parte sua, non è legato a un singolo motore: le stesse regole funzionano con ModSecurity su Apache, Nginx e IIS, e con Coraza, il motore WAF scritto in Go che sta guadagnando terreno come alternativa più leggera. Questo disaccoppiamento tra motore e regole è la ragione per cui imparare il CRS oggi resta una competenza trasferibile anche se in futuro si cambia motore. Le regole del CRS coprono, tra le altre cose, SQL injection, cross-site scripting, path traversal, local e remote file inclusion, rilevamento di scanner automatici e protezione contro richieste HTTP malformate.

Il concetto centrale da capire prima di iniziare è la modalità operativa: ModSecurity può lavorare in DetectionOnly, dove registra le violazioni nei log senza bloccare nulla, oppure in modalità di blocco attivo. Ogni installazione seria deve attraversare una fase di osservazione in DetectionOnly prima di passare al blocco, altrimenti il rischio di bloccare traffico legittimo (i famigerati falsi positivi) è alto fin dal primo giorno.

Il progetto, originariamente sviluppato come modulo standalone per Apache e poi mantenuto per anni da Trustwave SpiderLabs, è oggi gestito sotto l’ombrello OWASP con il nome di repository owasp-modsecurity/ModSecurity. Il cambio di governance non ha intaccato la compatibilità all’indietro delle regole SecLang: configurazioni scritte per versioni precedenti continuano in larga parte a funzionare, il che rende ModSecurity una delle poche componenti di sicurezza open source con un ciclo di vita realmente lungo.

Prerequisiti e versioni software necessarie

Prima di iniziare verifica di avere accesso root (o sudo) su un server Linux con Nginx già funzionante. Il processo richiede la compilazione di componenti da codice sorgente: non esistono ancora pacchetti binari ufficiali per tutte le distribuzioni, anche se Fedora distribuisce un pacchetto nginx-mod-modsecurity precompilato aggiornato regolarmente, e il progetto Digitalwave mantiene build alternative per Debian. La tabella seguente riassume le versioni usate in questa guida.

ComponenteVersione usata in questa guidaNote
Sistema operativoUbuntu 24.04 LTS / Debian 13 (Trixie)Comandi compatibili con la famiglia Debian/Ubuntu
Nginx1.26.x o successiva (mainline)Serve il codice sorgente corrispondente per compilare il modulo dinamico
libModSecurity3.0.14 (stabile)Pacchettizzata ad esempio nel repository Digitalwave per Debian Trixie
Connettore ModSecurity-nginx1.0.4Versione distribuita anche nei repository Fedora 43/44 a settembre 2026
OWASP Core Rule Set4.27.0 (ramo LTS da 4.25.0)Rilasciata a giugno 2026; supporto alla serie legacy 3.3.x fino al Q3 2026
Compilatori e libreriebuild-essential, libpcre3-dev, libxml2-dev, libcurl4-openssl-dev, libyajl-devRichieste per compilare libModSecurity

Tieni conto che il ramo CRS avanza rapidamente: se al momento della lettura è disponibile una versione più recente della 4.27.0, puoi sostituirla nei comandi di download senza cambiare il resto della procedura, perché la struttura delle directory del progetto è stabile da anni.

Passo 1 e 2: preparare il server e compilare libModSecurity 3.0.14

Il primo passo è aggiornare il sistema e installare tutte le dipendenze di compilazione. ModSecurity si appoggia a diverse librerie di sistema per il parsing XML, le espressioni regolari PCRE, il supporto JSON (YAJL) e le richieste HTTP (libcurl), quindi è importante installarle tutte prima di lanciare la build.

sudo apt update && sudo apt upgrade -y
sudo apt install -y git g++ make build-essential autoconf automake \
  libtool libpcre3 libpcre3-dev libxml2 libxml2-dev libcurl4-openssl-dev \
  libyajl-dev doxygen pkgconf libgeoip-dev libssl-dev \
  dh-autoreconf libfuzzy-dev gettext ssdeep

Con le dipendenze pronte, cloniamo il repository di libModSecurity e selezioniamo il tag della versione 3.0.14 prima di compilare. La build genera un file .so che il connettore Nginx caricherà più avanti.

cd /usr/local/src
sudo git clone --depth 1 -b v3.0.14 https://github.com/owasp-modsecurity/ModSecurity
cd ModSecurity
sudo git submodule init
sudo git submodule update
sudo ./build.sh
sudo ./configure
sudo make -j"$(nproc)"
sudo make install

Su macchine con poca RAM (meno di 2 GB) la compilazione di libModSecurity può richiedere oltre 30 minuti e andare in out-of-memory durante il linking: se succede, riduci il parallelismo con make -j1 invece di -j"$(nproc)" oppure aggiungi temporaneamente swap.

Passo 3 e 4: installare il connettore Nginx e compilare il modulo dinamico

libModSecurity da sola non si collega a Nginx: serve il connettore ModSecurity-nginx, distribuito come modulo dinamico da compilare contro i sorgenti della versione esatta di Nginx installata sul server. Verifica la versione corrente con nginx -v e scarica il codice sorgente corrispondente.

nginx -v
cd /usr/local/src
sudo git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity-nginx
NGINX_VERSION=$(nginx -v 2>&1 | grep -oP '[\d.]+')
sudo wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz
sudo tar -xzvf nginx-${NGINX_VERSION}.tar.gz
cd nginx-${NGINX_VERSION}
sudo ./configure --with-compat --add-dynamic-module=../ModSecurity-nginx
sudo make modules
sudo cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/

Un errore comune in questo passo è compilare il connettore contro una versione di Nginx diversa da quella effettivamente in esecuzione sul server: il modulo si carica senza errori ma va in crash silenzioso al primo riavvio. Controlla sempre che NGINX_VERSION corrisponda esattamente all’output di nginx -v prima di procedere.

Passo 5 e 6: configurare modsecurity.conf e caricare il modulo in Nginx

Il repository di ModSecurity fornisce un file di configurazione di esempio che copiamo come punto di partenza, rinominandolo e attivando la modalità DetectionOnly per la fase di osservazione iniziale. Il file modsecurity.conf-recommended arriva già con SecRuleEngine DetectionOnly impostato di default, proprio per evitare che chi lo copia senza leggerlo finisca per bloccare traffico fin dal primo riavvio: è una delle poche scelte di configurazione predefinita pensate esplicitamente per essere sicure anche se non toccate.

Prima di procedere, assicurati che l’utente con cui Nginx esegue i worker (tipicamente www-data su Debian/Ubuntu) abbia i permessi di scrittura sulla cartella dove risiede modsec_audit.log, altrimenti il processo di avvio avrà successo ma nessun evento verrà mai scritto su disco, un problema silenzioso che scopri solo quando ti serve davvero consultare i log.

sudo mkdir -p /etc/nginx/modsec
sudo cp /usr/local/src/ModSecurity/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
sudo cp /usr/local/src/ModSecurity/unicode.mapping /etc/nginx/modsec/

sudo sed -i 's/SecAuditLogFormat Native/SecAuditLogFormat JSON/' /etc/nginx/modsec/modsecurity.conf
sudo sed -i 's#SecAuditLog /var/log/.*#SecAuditLog /var/log/nginx/modsec_audit.log#' /etc/nginx/modsec/modsecurity.conf

Ora carichiamo il modulo dinamico e attiviamo ModSecurity nel blocco server o http di Nginx. Il caricamento del modulo va fatto nel contesto globale, fuori da qualsiasi blocco server, mentre l’attivazione va dentro il blocco server o location dove vuoi applicare la protezione.

load_module modules/ngx_http_modsecurity_module.so;

http {
    server {
        listen 443 ssl;
        server_name esempio.it;

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

        location / {
            proxy_pass http://127.0.0.1:8080;
        }
    }
}

Il file main.conf referenziato qui è un file che creiamo subito dopo, nel passo successivo, e che fa da punto di ingresso unico per caricare sia modsecurity.conf sia le regole del Core Rule Set.

Passo 7 e 8: installare OWASP Core Rule Set 4.27 e includerlo in Nginx

Scarichiamo la release 4.27.0 del Core Rule Set direttamente dal repository ufficiale, copiamo il file di configurazione di esempio e lo rinominiamo in modo definitivo, perché il progetto lo sovrascriverebbe a ogni aggiornamento se lasciato con il nome originale.

cd /etc/nginx/modsec
sudo git clone --depth 1 -b v4.27.0 https://github.com/coreruleset/coreruleset crs
sudo mv crs/crs-setup.conf.example crs/crs-setup.conf

sudo tee /etc/nginx/modsec/main.conf > /dev/null <<'EOF'
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.conf
EOF

L'ordine degli Include è importante: modsecurity.conf deve caricarsi prima (definisce le variabili e la modalità del motore), poi crs-setup.conf (definisce il paranoia level e le eccezioni globali) e infine le singole regole nella cartella rules. Invertire l'ordine è uno degli errori più frequenti e produce un motore che parte ma ignora le regole.

Testa la sintassi di Nginx e ricarica il servizio:

sudo nginx -t
sudo systemctl reload nginx

Passo 9: avviare in modalità DetectionOnly e leggere l'audit log

Con SecRuleEngine DetectionOnly ancora attivo (impostato di default nel file recommended), ModSecurity registra ogni violazione nel log di audit senza bloccare nulla. Questa fase andrebbe mantenuta per almeno una settimana di traffico reale, idealmente comprendendo un ciclo di picco (il lunedì mattina per un sito B2B, ad esempio) prima di passare al blocco attivo.

Genera traffico normale verso il sito e osserva il file di log JSON configurato in precedenza:

sudo tail -f /var/log/nginx/modsec_audit.log | jq '.transaction.messages[]?.message'

Un output tipico durante la fase di osservazione, per una richiesta legittima che però contiene caratteri sospetti in un campo di testo libero, è simile a questo:

"Detected SQL injection attack: common injection testing detected"
[file "crs-setup-conf"] [id "942100"] [rev ""] [msg "SQL Injection Attack Detected via libinjection"]
[severity "CRITICAL"] [ver "OWASP_CRS/4.27.0"] [tag "paranoia-level/1"]

Se questo tipo di evento compare su un campo che in realtà contiene testo libero legittimo (un commento utente con un apostrofo seguito da "or", per esempio), è il primo segnale che servirà un'esclusione specifica per quella regola e quel parametro, argomento che trattiamo nel passo sul tuning.

Durante questa fase conviene anche tenere un semplice foglio di calcolo o un file di testo con tre colonne: percorso della richiesta, id della regola che si è attivata e se il blocco sarebbe stato corretto o un falso positivo. Dopo una settimana questo elenco diventa la base per decidere quali esclusioni scrivere prima di passare al blocco attivo, invece di procedere a intuito o aspettare le segnalazioni degli utenti una volta già in produzione.

Passo 10: testare il WAF con SQL Injection e XSS simulati

Prima di fidarti della configurazione, verifica che il motore stia davvero intercettando pattern di attacco noti. Usa richieste che richiamano tecniche OWASP standard, mai contro sistemi che non controlli direttamente.

# Test SQL injection (deve generare un evento nel log)
curl -s "https://esempio.it/?id=1' OR '1'='1" -o /dev/null -w "%{http_code}\n"

# Test cross-site scripting
curl -s "https://esempio.it/?q=" -o /dev/null -w "%{http_code}\n"

# Test path traversal
curl -s "https://esempio.it/?file=../../../../etc/passwd" -o /dev/null -w "%{http_code}\n"

In modalità DetectionOnly tutte e tre le richieste restituiranno comunque 200 (o il codice applicativo normale), ma genereranno una voce nel log di audit. In modalità di blocco attivo, invece, dovresti ricevere un 403 Forbidden generato direttamente da ModSecurity, non dall'applicazione a monte: un buon modo per verificarlo è controllare che la risposta non contenga l'HTML tipico dell'app, ma la pagina di errore generica di Nginx o del CRS.

Vale la pena ripetere questo set di test ogni volta che si aggiorna il Core Rule Set o si modifica il paranoia level, perché un aggiornamento delle regole può involontariamente rimuovere la copertura su un pattern che prima veniva bloccato. Trattalo come un piccolo test di regressione da eseguire in staging prima di ogni deploy che tocchi la configurazione del WAF, non come un controllo da fare una tantum il primo giorno.

Passo 11 e 12: tuning dei Paranoia Level e passaggio al Blocking Mode

Il Core Rule Set organizza le proprie regole su quattro livelli di paranoia crescenti. Più sali di livello, più aumenta la copertura contro tecniche di evasione avanzate, ma aumenta proporzionalmente anche la probabilità di bloccare richieste legittime, soprattutto su form complessi e API con payload JSON annidati.

Non esiste un livello "giusto" valido per tutti i siti: un blog aziendale con un solo form di contatto può restare comodamente a PL1 per anni, mentre un portale che gestisce pagamenti o dati sanitari dovrebbe pianificare fin da subito un percorso verso PL2, accettando che la fase di tuning richiederà più tempo. La scelta del livello è quindi una decisione di risk management, non solo tecnica, e andrebbe presa insieme a chi si occupa di compliance quando l'applicazione tratta categorie di dati particolari.

Paranoia LevelCoperturaFalsi positivi tipiciUso consigliato
PL1Regole base, pattern di attacco inequivocabiliRariBaseline di produzione per la maggior parte dei siti
PL2Aggiunge euristiche più ampieFrequenti su form complessiApp che gestiscono dati sensibili, dopo tuning dedicato
PL3Pattern matching molto aggressivoComuni, richiede tuning estensivoSettori ad alto rischio come banking e sanità
PL4Matching quasi paranoico su distribuzione dei caratteriMolto comuniAsset critici o solo in modalità detection

Il CRS 4 introduce anche la possibilità di separare il livello di blocco da quello di rilevamento tramite due variabili distinte, blocking_paranoia_level e detection_paranoia_level: puoi bloccare attivamente solo le regole PL1 mentre registri (senza bloccare) tutto quello che le regole PL2 rilevano, un approccio molto più sicuro per iniziare a raccogliere dati su un livello più alto senza rischiare interruzioni.

# In crs-setup.conf
SecAction \
 "id:900000,\
  phase:1,\
  pass,\
  t:none,\
  nolog,\
  setvar:tx.blocking_paranoia_level=1,\
  setvar:tx.detection_paranoia_level=2"

# Esclusione puntuale per un parametro che genera falsi positivi
SecRule REQUEST_URI "@streq /commenti" \
 "id:1000,\
  phase:1,\
  pass,\
  nolog,\
  ctl:ruleRemoveTargetById=942100;ARGS:testo_commento"

Quando il tasso di falsi positivi osservato in DetectionOnly scende a un livello gestibile (idealmente zero su un campione di traffico di almeno una settimana), puoi passare al blocco attivo cambiando una sola direttiva e ricaricando Nginx.

sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf
sudo nginx -t && sudo systemctl reload nginx

Non fare questo passaggio di venerdì pomeriggio: se qualcosa va storto preferisci avere il team al completo per il rollback rapido (basta invertire il sed sopra e ricaricare).

Errori comuni da evitare durante la configurazione

Questi sono gli errori che causano la maggior parte dei problemi segnalati nelle community dedicate a ModSecurity, in ordine di frequenza osservata. Molti derivano non da una configurazione sbagliata in senso stretto, ma da scorciatoie prese per andare in produzione più in fretta, saltando la fase di osservazione o copiando una configurazione d'esempio senza adattarla al proprio traffico reale.

  • Passare direttamente al Blocking Mode senza fase di osservazione. È la causa numero uno di downtime autoinflitto: senza dati reali sui falsi positivi, il primo giorno in blocco attivo può interrompere checkout, form di login o upload legittimi.
  • Compilare il connettore Nginx contro la versione sbagliata dei sorgenti. Il modulo si carica ma va in crash al primo traffico reale o ignora silenziosamente le regole; verifica sempre nginx -v prima della build.
  • Invertire l'ordine degli Include in main.conf. Se crs-setup.conf viene caricato prima di modsecurity.conf, alcune variabili di default non sono ancora definite e il comportamento diventa imprevedibile.
  • Lasciare il paranoia level a PL1 su applicazioni che gestiscono dati sensibili senza mai rivalutarlo. PL1 copre solo gli attacchi più ovvi; per API bancarie o sanitarie serve almeno PL2 con un percorso di tuning dedicato.
  • Non monitorare le dimensioni del file di audit log. In formato JSON e con traffico elevato, il log può crescere di diversi gigabyte al giorno e saturare il disco se non è impostata una rotazione.
  • Applicare esclusioni troppo ampie per silenziare i falsi positivi. Disabilitare un'intera regola a livello globale invece di scoparla su un singolo parametro (tramite ctl:ruleRemoveTargetById) riapre la porta esattamente alla classe di attacco che quella regola copriva.

Risoluzione dei problemi più comuni

Ecco gli scenari di troubleshooting più frequenti riscontrati durante l'installazione e la messa in produzione di ModSecurity con il Core Rule Set. Per ognuno vale la stessa regola generale: prima di modificare qualsiasi regola, isola il problema guardando l'audit log in formato JSON con jq, individua l'id della regola coinvolta e intervieni sempre sul parametro specifico, non sulla regola intera.

ProblemaCausa probabileSoluzione
Nginx non parte dopo load_moduleIl modulo è stato compilato contro una versione diversa di NginxRicompila il connettore con i sorgenti della versione esatta installata
Nessun evento nel log di auditIl percorso main.conf non è referenziato in nessun blocco serverVerifica la direttiva modsecurity_rules_file dentro il blocco server corretto
Tutte le richieste restituiscono 403Il motore è passato a On con paranoia level troppo alto senza tuningTorna a DetectionOnly, raccogli dati, poi riattiva gradualmente
Errore "unknown directive modsecurity"Il modulo dinamico non è stato caricato nel contesto globaleSposta load_module fuori dal blocco http, all'inizio di nginx.conf
Build di libModSecurity fallisce per memoria insufficienteVPS con meno di 2 GB di RAM e compilazione parallelaUsa make -j1 oppure aggiungi uno swapfile temporaneo
Falsi positivi su upload di fileIl body multipart supera i limiti di default di ModSecurityAumenta SecRequestBodyLimit e SecRequestBodyNoFilesLimit in modsecurity.conf
Log di audit cresce senza controlloManca una policy di rotazione dedicataAggiungi una voce logrotate specifica per modsec_audit.log
Le regole del CRS non vengono applicate dopo un aggiornamentoIl file crs-setup.conf.example è stato sovrascritto dal nuovo git pullMantieni crs-setup.conf fuori dal controllo di versione del repository CRS o con un diff dedicato

Monitoraggio continuo, performance e trattamento dei log

Una volta superata la fase di blocco attivo, il lavoro non è finito: un WAF trascurato perde valore rapidamente, perché nuove rotte applicative vengono aggiunte senza passare mai da una fase di osservazione e il rischio di falsi positivi silenziosi cresce nel tempo. Vale la pena fissare una cadenza di revisione, anche solo mensile, in cui si riesaminano gli eventi con anomaly score più alto e si verifica che nessuna funzionalità recente stia generando blocchi inattesi.

Sul fronte delle performance, l'impatto di ModSecurity dipende quasi interamente dal paranoia level e dal volume di espressioni regolari attive: a PL1 l'overhead per richiesta è generalmente trascurabile rispetto alla latenza di rete, mentre a PL3 e PL4 l'ispezione di body di grandi dimensioni (upload, payload JSON pesanti) può diventare misurabile. La direttiva SecRequestBodyLimit esiste anche per questo: limitare la dimensione del body ispezionato evita che un singolo upload enorme blocchi il worker Nginx per secondi. Se gestisci traffico ad alto volume, esegui sempre un test di carico con uno strumento come k6 o wrk prima e dopo l'attivazione del blocco, confrontando percentili p95 e p99, non solo la media.

Il file di audit log merita un'attenzione specifica dal punto di vista della protezione dei dati: contiene indirizzi IP, header delle richieste e, se SecRequestBodyAccess è attivo, anche il corpo delle richieste, che può includere dati personali inseriti dagli utenti in form o campi di testo. Trattalo come un asset da proteggere al pari dei log applicativi: restringi l'accesso al filesystem, definisci un periodo di retention coerente con le finalità dichiarate (tipicamente poche settimane per il solo scopo di sicurezza) e valuta se disabilitare SecResponseBodyAccess, che nella maggior parte dei casi non serve e aumenta sia il volume di log sia la superficie di dati sensibili registrati.

Consigli avanzati per ambienti di produzione

Una volta che la configurazione base è stabile, ci sono diversi passi successivi che aumentano il valore reale del WAF senza stravolgere l'architettura già in piedi. Nessuno di questi è obbligatorio per avere una protezione funzionante, ma ognuno colma un limite specifico dell'installazione minima che abbiamo appena completato.

  • Virtual patching. Quando esce una vulnerabilità critica su una libreria che non puoi aggiornare immediatamente (penso a un CVE con CVSS alto pubblicato un venerdì sera), una regola SecLang scritta su misura può bloccare lo specifico pattern di exploit nel giro di minuti, mentre il patch ufficiale viene testato con calma.
  • Integrazione con Fail2ban. Collegando il parsing del log di audit a Fail2ban puoi bannare temporaneamente gli IP che generano un numero anomalo di violazioni critiche in pochi minuti, riducendo il carico sulle regole stesse.
  • Esportazione verso un SIEM. Il formato JSON del log di audit si presta bene all'ingestione in una pipeline di raccolta log centralizzata, trasformando ModSecurity in una fonte di detection oltre che di blocco.
  • Valutare Coraza per i nuovi deployment. Se stai progettando un'infrastruttura da zero, vale la pena valutare Coraza, il motore WAF scritto in Go compatibile con le stesse regole CRS: elimina le dipendenze C++ di libModSecurity e si integra più facilmente in ambienti containerizzati.
  • Automatizzare gli aggiornamenti del CRS. Il progetto rilascia aggiornamenti delle regole con una certa frequenza; pianifica un processo (anche solo un cron che fa git pull su un branch di staging) per rimanere aggiornato senza intervento manuale continuo.

Quando scegliere ModSecurity invece di un WAF cloud gestito

Non è una scelta valida per tutti i contesti, ed è bene dirlo chiaramente prima di investire ore di lavoro nella configurazione. Un WAF cloud gestito ha il vantaggio dell'attivazione quasi immediata, di regole aggiornate centralmente dal fornitore e di una capacità di assorbire picchi di traffico anomali che un singolo server Nginx non può replicare da solo. Se il team non ha tempo o competenze per gestire un ciclo di tuning dei falsi positivi, o se il traffico previsto richiede una scalabilità elastica difficile da garantire su infrastruttura propria, un servizio gestito resta spesso la scelta più pragmatica, anche a costo di una minore trasparenza su come vengono trattati i log delle richieste.

ModSecurity con il Core Rule Set diventa invece la scelta più sensata quando esiste già competenza Linux/Nginx interna, quando i vincoli di compliance rendono preferibile non instradare il traffico applicativo attraverso un provider terzo, o quando il progetto ha bisogno di regole molto specifiche e su misura (penso al virtual patching rapido per una vulnerabilità appena scoperta) che un servizio gestito generalista non permette di scrivere e distribuire con la stessa rapidità. È anche l'opzione naturale per chi gestisce più siti su un parco server comune e vuole applicare una policy di sicurezza uniforme senza pagare una licenza per ogni singolo host o dominio protetto.

Molte organizzazioni, in pratica, finiscono per usare entrambi gli approcci in combinazione: un livello di filtraggio DDoS e bot management gestito a monte, e un ModSecurity con CRS a valle, più vicino all'applicazione, per avere visibilità e controllo granulare su cosa viene effettivamente bloccato e perché. Questa combinazione a più livelli è spesso più robusta di qualsiasi singola soluzione, perché nessun layer di sicurezza da solo riesce a intercettare tutte le classi di attacco con lo stesso livello di dettaglio.

Progetto completo: configurazione end-to-end funzionante

Ecco la struttura completa dei file così come dovrebbero risultare a fine tutorial, pronta per essere adattata al proprio dominio. Puoi copiare questi file come base di partenza per qualsiasi nuovo deployment.

# /etc/nginx/nginx.conf (estratto)
load_module modules/ngx_http_modsecurity_module.so;

http {
    server {
        listen 443 ssl;
        server_name esempio.it;

        ssl_certificate     /etc/letsencrypt/live/esempio.it/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/esempio.it/privkey.pem;

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

        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}
# /etc/nginx/modsec/main.conf
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.conf

# /etc/nginx/modsec/modsecurity.conf (righe chiave)
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Serial
SecAuditLogFormat JSON
SecAuditLog /var/log/nginx/modsec_audit.log
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072

Con questi file, un restart di Nginx pulito e un paio di settimane di osservazione in DetectionOnly prima del blocco attivo, hai un WAF open source funzionante davanti a qualsiasi applicazione servita da Nginx, senza dipendere da un fornitore cloud terzo e senza costi di licenza.

Se gestisci più domini sullo stesso server, puoi riutilizzare lo stesso main.conf e lo stesso set di regole CRS per tutti i blocchi server, applicando però esclusioni diverse per ciascun dominio quando necessario: basta creare un file di esclusioni specifico per host (ad esempio exclusions-dominio-a.conf) e includerlo subito dopo le regole del CRS, prima della chiusura del blocco server corrispondente. Questo evita di duplicare l'intera configurazione per ogni sito e mantiene un'unica fonte di verità per gli aggiornamenti del Core Rule Set.

Domande frequenti

ModSecurity funziona anche con Apache, non solo con Nginx?
Sì, ModSecurity è nato come modulo Apache e resta pienamente supportato su quella piattaforma; questa guida si concentra su Nginx perché richiede un passo di compilazione aggiuntivo (il connettore dedicato) che su Apache non è necessario nella stessa forma, dato che su Apache il modulo si carica direttamente tramite i meccanismi nativi del web server senza bisogno di un connettore esterno.

Quanto tempo richiede l'intera installazione?
Tra compilazione di libModSecurity, connettore Nginx e configurazione del Core Rule Set, conta circa 45-60 minuti su un server con risorse adeguate, escludendo la fase di osservazione in DetectionOnly che dovrebbe durare almeno una settimana.

Il Core Rule Set rallenta il sito?
L'impatto sulla latenza è generalmente contenuto a basso paranoia level (PL1), ma cresce ai livelli superiori perché ogni richiesta viene confrontata con più regole ed espressioni regolari; su siti ad altissimo traffico vale la pena misurare la latenza aggiunta con un test di carico prima e dopo l'attivazione, concentrandosi sui percentili alti (p95, p99) piuttosto che sulla sola media, perché è lì che si nota per primo un eventuale collo di bottiglia.

Posso usare ModSecurity su un sito già in produzione senza rischi?
Sì, a patto di installarlo sempre con SecRuleEngine DetectionOnly come primo passo e di non passare al blocco attivo finché non hai verificato l'assenza di falsi positivi su un campione di traffico reale.

Che differenza c'è tra ModSecurity e Coraza?
Coraza è un motore WAF scritto in Go, compatibile con le stesse regole SecLang del Core Rule Set, ma senza le dipendenze C++ di libModSecurity: è spesso preferito nei nuovi deployment containerizzati o su Kubernetes, mentre ModSecurity resta la scelta più consolidata su installazioni Nginx o Apache tradizionali.

Devo disabilitare tutto il paranoia level 2 se genera troppi falsi positivi?
Non è necessario disabilitarlo del tutto: puoi usare detection_paranoia_level per continuare a registrare gli eventi di PL2 senza bloccarli, mentre mantieni il blocco attivo solo su PL1, un approccio più graduale rispetto allo spegnimento completo.

ModSecurity protegge anche dagli attacchi DDoS?
Non è il suo scopo primario: ModSecurity ispeziona il contenuto delle richieste per bloccare pattern di attacco applicativo, mentre la mitigazione DDoS volumetrica richiede strumenti dedicati a livello di rete o un servizio di protezione a monte.

Dove trovo aggiornamenti ufficiali sulle nuove versioni del Core Rule Set?
Il repository ufficiale del progetto su GitHub (coreruleset/coreruleset) pubblica changelog e tag di release per ogni versione, inclusi i dettagli sulle nuove regole e sulle modifiche ai paranoia level.

Serve un hardware dedicato per far girare ModSecurity?
No, nella maggior parte dei casi un server o una VM che già ospita Nginx può accogliere anche ModSecurity senza upgrade hardware, soprattutto se resti a PL1 o PL2; solo con traffico molto elevato e paranoia level alti conviene valutare risorse di CPU aggiuntive dedicate specificamente all'ispezione delle richieste.