Hardening di Nginx su HestiaCP: come bloccare i DDoS di Layer 7 e velocizzare i siti con la cache
Guida pratica per creare un template Nginx personalizzato su HestiaCP che mitiga gli attacchi DDoS di livello applicativo (Layer 7) e, insieme, abilita la cache dei contenuti dinamici per velocizzare i siti.
Un attacco DDoS "classico" punta a saturare la banda o le connessioni di rete del server (i cosiddetti attacchi di Layer 3/4, a livello di rete e trasporto). Ma esiste un'altra categoria, spesso più insidiosa perché richiede molta meno banda per fare danni: gli attacchi di Layer 7, il livello applicativo. Qui il bersaglio non è la connessione di rete in sé, ma il server web e le applicazioni che ci girano sopra: migliaia di richieste HTTP "legittime" nella forma, ma inviate così rapidamente da esaurire CPU, memoria e connessioni al database, mandando in crisi anche un server con una connessione internet tutt'altro che satura.
Chi gestisce i propri siti con HestiaCP, il pannello di controllo server open source basato su Nginx (da solo o come reverse proxy davanti ad Apache), ha a disposizione uno strumento potente ma poco conosciuto per difendersi da questo tipo di attacchi: i template Nginx personalizzati. In questa guida vediamo come creare un template su misura che aggiunge regole di mitigazione dei DDoS applicativi e, allo stesso tempo, come sfruttare la cache di Nginx per velocizzare i contenuti dinamici (PHP, applicazioni Node.js, Docker) riducendo ulteriormente il carico sul server, anche in assenza di attacchi.
Come funzionano i template Nginx in HestiaCP
HestiaCP non scrive la configurazione di ogni sito "a mano": per ogni dominio applica un template, cioè un file modello con segnaposto (placeholder) che il pannello sostituisce con i valori reali del dominio (nome a dominio, percorso dei file, utente di sistema, eccetera) ogni volta che il dominio viene creato o "ricostruito". I template di default si trovano in:
/usr/local/hestia/data/templates/web/nginx/ # Nginx usato come reverse proxy davanti ad Apache
/usr/local/hestia/data/templates/web/nginx/php-fpm/ # Nginx con PHP-FPM (nessun Apache)
Ogni template è in realtà una terna di file con lo stesso nome e estensioni diverse: .tpl (configurazione per HTTP), .stpl (configurazione per HTTPS) e .sh (uno script opzionale eseguito alla generazione). Per esempio il template "default" è composto da default.tpl, default.stpl e default.sh.
Regola fondamentale: non modificare mai i template originali. Ad ogni aggiornamento di HestiaCP i file di default vengono sovrascritti, e qualunque personalizzazione fatta direttamente su di essi andrebbe persa. La procedura corretta è copiarli con un nuovo nome e lavorare solo sulla copia:
cd /usr/local/hestia/data/templates/web/nginx/php-fpm/
cp default.tpl hardened.tpl
cp default.stpl hardened.stpl
cp default.sh hardened.sh 2>/dev/null || true
(l'esempio usa i template per Nginx + PHP-FPM; se il sito gira dietro Nginx + Apache, lo stesso procedimento va fatto nella cartella nginx/ invece che in nginx/php-fpm/).
Dentro ai file copiati si trovano segnaposto come %domain%, %docroot%, %user%, %home% e %backend_lsnr%: HestiaCP li sostituisce automaticamente con i valori corretti per ogni dominio che userà il template, quindi vanno lasciati intatti mentre si aggiungono le nuove direttive attorno ad essi.
Prima regola di hardening: limitare la frequenza delle richieste (rate limiting)
Il modulo ngx_http_limit_req_module, incluso di default in Nginx, permette di contare le richieste per indirizzo IP (o per qualsiasi altra variabile) e di rifiutare quelle che superano una soglia, restituendo un errore HTTP 429 Too Many Requests. È la difesa più efficace e più semplice da attivare contro i flood applicativi: un bot che spara centinaia di richieste al secondo contro la pagina di login o il motore di ricerca interno del sito viene bloccato prima che possa consumare risorse sul backend PHP.
La zona di conteggio va dichiarata una sola volta, a livello globale, nel blocco http della configurazione principale di Nginx (/etc/nginx/nginx.conf, oppure in un file separato incluso da esso, ad esempio /etc/nginx/conf.d/ddos-mitigation.conf):
# 10 MB di memoria condivisa bastano per tracciare circa 160.000 IP
limit_req_zone $binary_remote_addr zone=req_zone_general:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=req_zone_login:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=conn_zone:10m;
Qui sono state create due zone distinte per il rate limiting: una generica (req_zone_general, 20 richieste al secondo per IP) per tutto il sito, e una molto più severa (req_zone_login, 5 richieste al minuto) da applicare solo alla pagina di login, tipico bersaglio di attacchi brute-force oltre che DDoS. È stata aggiunta anche una zona per limit_conn, che invece limita non la frequenza ma il numero di connessioni simultanee per IP, utile contro i tentativi di saturare il pool di connessioni del backend.
Nel template personalizzato (hardened.tpl e hardened.stpl), dentro al blocco server { ... }, si applicano le zone appena definite:
server {
listen %ip%:%web_port%;
server_name %domain_idn% %alias_idn%;
root %docroot%;
# Limita le connessioni simultanee per singolo IP su tutto il sito
limit_conn conn_zone 20;
location / {
# Rate limit generale: consente brevi picchi (burst) senza
# bloccare subito un utente che clicca più volte in fretta
limit_req zone=req_zone_general burst=40 nodelay;
limit_req_status 429;
try_files $uri $uri/ /index.php?$args;
}
location = /wp-login.php {
limit_req zone=req_zone_login burst=5 nodelay;
limit_req_status 429;
include /home/%user%/conf/web/%domain%/nginx.hosting.conf*;
}
# ... il resto del template (gestione PHP-FPM, errori, ecc.)
# resta quello del template "default" originale.
}
Qualche spiegazione dei parametri usati, pensata per chi non lavora tutti i giorni con Nginx:
rate=20r/s: la velocità "a regime" consentita, in questo caso 20 richieste al secondo per ogni IP.burst=40: una sorta di "serbatoio" di richieste extra che un IP può accumulare ed esaurire in un colpo, per non penalizzare un utente reale che naviga rapidamente tra più pagine (ogni immagine, ogni file CSS o JS di una pagina genera comunque una richiesta).nodelay: senza questa opzione, Nginx metterebbe in coda (ritardando) le richieste che superano la soglia; connodelayle richieste in eccesso rispetto al burst vengono invece rifiutate immediatamente con errore 429, evitando di accumulare connessioni in attesa che a loro volta consumerebbero risorse.limit_req_status 429: imposta esplicitamente il codice di errore restituito (il default sarebbe comunque 503, ma 429 è semanticamente più corretto e viene riconosciuto meglio da CDN e strumenti di monitoraggio).
Seconda regola di hardening: tagliare le connessioni lente (anti Slowloris)
Un'altra famiglia di attacchi di Layer 7, nota come Slowloris, non manda tante richieste ma pochissime, mantenendo però ogni connessione aperta il più a lungo possibile inviando i dati a scatti infinitesimali: in questo modo esaurisce il numero massimo di connessioni simultanee che il server può gestire, senza mai dover generare traffico elevato. Il rimedio è riducere i tempi massimi che Nginx concede a un client per completare l'invio della richiesta:
client_body_timeout 10s;
client_header_timeout 10s;
keepalive_timeout 15s;
send_timeout 10s;
# Limita anche le dimensioni, per evitare upload abnormi usati come vettore di flood
client_max_body_size 10m;
large_client_header_buffers 4 8k;
Questi valori (molto più bassi dei 60 secondi di default) vanno inseriti anch'essi nel blocco server { ... } del template personalizzato, oppure nel blocco http { ... } globale se si vuole applicarli a tutti i domini del server. Attenzione a non esagerare: tempi troppo aggressivi possono interrompere upload legittimi di file grandi da connessioni lente o mobili; 10 secondi sono generalmente un buon compromesso per siti normali.
Ottimizzare il caching dei contenuti dinamici
Mitigare i DDoS applicativi non significa solo "bloccare i cattivi": significa anche far costare pochissimo rispondere a una richiesta, così che anche un volume elevato di traffico (malevolo o semplicemente un picco di popolarità improvviso) non metta in ginocchio il server. La cache di Nginx per i contenuti dinamici serve esattamente a questo: le pagine generate da PHP (o da un'app Node.js dietro proxy) vengono salvate temporaneamente su disco e rispedite identiche alle richieste successive, senza dover rieseguire il codice applicativo e le query al database ogni volta.
HestiaCP distingue due casi, in base allo stack scelto per il dominio:
Nginx + PHP-FPM: FastCGI cache
Per i domini che usano Nginx con PHP-FPM (senza Apache), si usa la FastCGI cache. Nella configurazione globale (lo stesso file /etc/nginx/conf.d/ddos-mitigation.conf usato sopra, o nginx.conf) si definisce la cache:
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=FASTCGICACHE:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
Nel template personalizzato, nel blocco che gestisce PHP-FPM, va aggiunta l'inclusione del file di attivazione dopo la riga include /etc/nginx/fastcgi_params; già presente nel template originale:
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass %backend_lsnr%;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
# Attiva la cache per questo dominio (il file viene creato da HestiaCP
# quando la cache FastCGI viene abilitata dal pannello per il dominio)
include /home/%user%/conf/web/%domain%/nginx.fastcgi_cache.conf*;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Non tutte le pagine però vanno messe in cache: un carrello e-commerce, un'area personale dopo il login, o un endpoint che genera contenuti diversi per ogni utente non devono restituire a un visitatore la pagina salvata per un altro. Si escludono quei percorsi impostando la variabile $no_cache:
set $no_cache 0;
if ($request_uri ~* "/(carrello|checkout|wp-admin|my-account)") {
set $no_cache 1;
}
if ($http_cookie ~* "wordpress_logged_in") {
set $no_cache 1;
}
Una volta creato il template, la cache FastCGI per uno specifico dominio si abilita dalla pagina di modifica del dominio nel pannello HestiaCP (sezione dedicata al template Nginx/backend), selezionando il template appena creato. Per svuotare la cache in qualunque momento (ad esempio dopo un aggiornamento del sito) basta usare il pulsante "Purge Nginx Cache" nella stessa pagina, oppure da riga di comando:
v-purge-nginx-cache utente dominio.it
Nginx + Apache: proxy cache
Quando invece il dominio usa Nginx come reverse proxy davanti ad Apache (configurazione molto comune su HestiaCP per supportare .htaccess e mod_php), la FastCGI cache non è disponibile: si usa invece la proxy cache di Nginx, che funziona in modo concettualmente identico ma si applica al traffico proxato (va bene anche per applicazioni Node.js o container Docker dietro proxy). I template personalizzati per questo scopo seguono una convenzione di nome dedicata: caching-nomepersonalizzato.tpl, caching-nomepersonalizzato.stpl e caching-nomepersonalizzato.sh, da creare sempre copiando il template di partenza equivalente fornito da HestiaCP nella cartella nginx/ (non nginx/php-fpm/) e selezionabile poi come "template di caching" dedicato dalla pagina del dominio.
Un avvertimento sui WebSocket
Sia il template standard sia quelli di proxy cache includono di default la direttiva proxy_hide_header Upgrade;, inserita originariamente per un problema di compatibilità con Safari. Questa riga però blocca anche le connessioni WebSocket (usate ad esempio da chat in tempo reale, notifiche push lato browser o alcuni pannelli di amministrazione): se l'applicazione ospitata ne ha bisogno, va rimossa dal template personalizzato.
Applicare il template e verificarne il funzionamento
Dopo aver salvato le modifiche ai file .tpl/.stpl, occorre selezionare il nuovo template per il dominio desiderato: nel pannello HestiaCP si apre la pagina di modifica del dominio (Web > nome dominio > Modifica), si espandono le opzioni avanzate e si sceglie il template appena creato (hardened, nel nostro esempio) dal menu a tendina del template Nginx/Proxy. In alternativa, da riga di comando, si può forzare la ricostruzione della configurazione per tutti i domini dell'utente con:
v-rebuild-user nomeutente
Prima di mettere tutto in produzione, è buona norma validare la sintassi e ricaricare Nginx manualmente:
nginx -t && systemctl reload nginx
Per verificare che il rate limiting funzioni davvero, si può simulare un piccolo flusso di richieste ripetute dalla propria macchina (da non confondere con un vero test di carico, che va eseguito con cautela e solo su sistemi di cui si è proprietari):
for i in $(seq 1 80); do
curl -o /dev/null -s -w "%{http_code}\n" https://dominio.it/
done | sort | uniq -c
Se la configurazione è corretta, le prime richieste (entro il burst configurato) restituiranno 200, mentre quelle successive, superata la soglia, risponderanno 429. È il segnale che la zona di rate limiting sta facendo il suo lavoro.
Qualche avvertenza prima di attivare tutto in produzione
Il rate limiting basato sull'indirizzo IP ha un limite intrinseco di cui tenere conto: molti utenti che navigano da reti aziendali, università o alcune reti mobili condividono lo stesso IP pubblico tramite NAT, e soglie troppo basse rischiano di bloccare utenti legittimi insieme a eventuali bot. Per questo è consigliabile partire con valori prudenti (come i 20 r/s dell'esempio), monitorare i log degli errori 429 per qualche giorno e solo successivamente stringere le soglie dove necessario. È anche buona pratica escludere dal rate limiting gli indirizzi IP dei propri strumenti di monitoraggio (uptime checker, CDN, eventuali servizi di backup) usando la direttiva geo o semplici blocchi map prima della zona di limit_req, per evitare falsi allarmi.
Infine, il rate limiting a livello applicativo descritto in questa guida è un primo, efficace livello di difesa contro i DDoS di Layer 7, ma non sostituisce una protezione specifica a livello di rete (Layer 3/4) per attacchi volumetrici molto grandi: per quelli restano indispensabili servizi di mitigazione a monte del server, come un CDN con protezione anti-DDoS o il filtro offerto da alcuni provider di hosting e dal proprio fornitore di connettività.
Fonti ufficiali
Share
What's Your Reaction?
Like
0
Cancella like
0
Amore
0
Buffo
0
arrabbiato
0
Sad
0
Wow
0