
Tenere un VPS online sotto attacco DDoS
Tre attacchi piuttosto diversi condividono il nome DDoS, e quasi ogni consiglio che leggerai fallisce perché li tratta come fossero la stessa cosa: risponde a un uplink saturo con una direttiva nginx, o a un flood di livello 7 ben congegnato con un server più grande. La domanda utile non è mai “come blocco questo” ma “a quale livello questo è fisicamente arrestabile, e chi controlla quel livello”. Questa pagina risponde per una macchina Linux a noleggio — cosa assorbe la rete sopra di te prima ancora che tu te ne accorga, cosa aiuta davvero dentro la macchina, e perché la mossa singolarmente più efficace è di solito smettere di essere raggiungibile del tutto.
La mitigazione volumetrica su questa rete è sempre attiva e non c'è nulla da abilitare: i flood sotto i 10 Gbps vengono assorbiti in silenzio, tra 10 e 100 Gbps il traffico passa dallo scrubbing presso il transit provider, e tutto ciò che supera questa soglia viene instradato su un percorso di scrubbing dedicato — le soglie sono scritte nella documentazione, non lasciate come aggettivo di marketing. Questo è il limite onesto di ciò che un host può offrirti. Ed è anche, per il traffico che davvero abbatte i piccoli server, la metà meno interessante del problema.
Perché gli attacchi che uccidono in modo affidabile un VPS da cinque dollari non sono i mostri da 340 Gbps che finiscono sui giornali. Sono 40.000 pacchetti al secondo di piccoli SYN che riempiono una tabella di stato che non hai mai guardato, oppure 300 richieste al secondo verso l'unico URL del tuo sito che esegue una query sul database — traffico che arriva su un'interfaccia perfettamente sana, attraverso un collegamento tutt'altro che saturo, e che nessuno scrubber a monte può distinguere dai tuoi utenti. Questi sono compiti tuoi, e si gestiscono con una ventina di righe di configurazione, a patto di capire prima quale dei tre stai affrontando.
Quanto segue presuppone Debian 13 o Ubuntu 24.04, nftables e nginx, e presuppone che tu abbia già fatto l'hardening della prima ora — un server che risponde ancora su porte che non serve non è pronto per essere difeso.
Tre attacchi, un solo nome
“DDoS” descrive un'intenzione, non un meccanismo, e i meccanismi non hanno quasi nulla in comune. Distinguerli correttamente non è pedanteria: è tutto il lavoro, perché ciascuno è arrestabile a un solo livello ed è invisibile agli altri.
I flood volumetrici puntano alla tua banda. L'amplificazione UDP — DNS, NTP, memcached, e ultimamente qualsiasi cosa risponda a una piccola domanda con una risposta enorme — permette a un attaccante di trasformare 1 Gbps della propria capacità in 50 Gbps puntati contro di te. Il bersaglio è il collegamento, non il server. La tua CPU si annoierà per tutta la durata.
Gli attacchi a protocollo e stato puntano a una tabella finita nel tuo kernel. Un SYN flood cerca di esaurire la coda di accept; un flood generico di pacchetti piccoli cerca di esaurire il connection tracking. Entrambi si misurano in pacchetti al secondo, non in bit al secondo, ed entrambi possono uccidere una macchina su un collegamento inattivo al 97%. Questa è la classe che abbatte i piccoli server, ed è la classe che la maggior parte delle guide salta.
I flood a livello applicativo puntano alla tua CPU o al tuo database, usando richieste indistinguibili da quelle reali perché sono reali. Cento richieste al secondo verso un endpoint di ricerca non sono nulla per una rete e sono fatali per un'applicazione PHP. Nessuno scrubber a monte può filtrarle per te: dall'esterno sembra esattamente un successo.
C'è una quarta possibilità che si presenta travestita da tutte e tre le altre e non è affatto un attacco: un link diventato virale da qualche parte, un tuo client che si comporta male, o un crawler maleducato. Escluderla per prima è gratis, ed è imbarazzante quanto spesso sia proprio questa la risposta.
La parte che non puoi risolvere dall'interno della macchina
Se 60 Gbps vengono puntati contro il tuo indirizzo e la tua porta è da 1 Gbps, la decisione su quei pacchetti viene presa da un router a monte di te, diversi hop prima di qualsiasi cosa tu amministri. Il tuo firewall non li vede mai. Non può: sono stati scartati per proteggere un collegamento che non possiedi. Questo è il fatto strutturale più importante sugli attacchi volumetrici, ed è il motivo per cui “blinda il tuo firewall contro il DDoS” è per lo più senza senso.
Quindi le uniche domande rilevanti sono cosa fa automaticamente il tuo provider e dove sono fissate le sue soglie. Le nostre sono pubblicate, non promesse: sotto i 10 Gbps il traffico viene assorbito senza alcun effetto visibile, tra 10 e 100 Gbps passa dallo scrubbing presso il transit provider e potresti notare un piccolo aumento di latenza, e sopra i 100 Gbps il prefisso viene instradato su un percorso di scrubbing dedicato — la latenza sale in modo più evidente, ma il servizio resta raggiungibile. Il null-routing di un indirizzo è un'opzione sul tavolo solo per attacchi sostenuti che minacciano l'intero PoP, e in quel caso te lo comunichiamo entro pochi minuti. Non c'è nulla da comprare e nulla da abilitare.
Ci sono due cose sullo scrubbing che vale la pena sapere e che i fornitori raramente dicono spontaneamente. La prima è che sei protetto dal vicinato, non solo dalle tue difese: un attacco verso un cliente che condivide il tuo /20 a monte viene assorbito prima di raggiungere il prefisso di chiunque — un flood da circa 340 Gbps puntato contro un range confinante a Parigi è passato senza impatto misurabile sul nostro, che è esattamente il risultato che nessuno nota. La seconda è che gli scrubber sono euristiche, e le euristiche a volte sbagliano: sulla stessa rete, uno scrubber ha una volta impiegato nove minuti a inviare reset TCP contro connessioni legittime mentre filtrava un attacco diretto a un vicino. Entrambi gli episodi sono nel registro pubblico degli incidenti. Se le tue sessioni muoiono in un modo che assomiglia a un reset attivo piuttosto che a un timeout durante l'attacco di qualcun altro, si tratta di una modalità di guasto reale, ed è meglio segnalarla che passare un'ora a fare debug in locale.
Novanta secondi di misurazione, prima di cambiare qualsiasi cosa
Ogni risposta sbagliata a un DDoS inizia con una modifica fatta prima che qualcuno capisse cosa stesse succedendo. Procurati prima quattro numeri. Stanno tutti su uno schermo e ti indicano da soli il livello coinvolto.
# 1. bits vs packets — which axis is saturated?
sar -n DEV 1 5 # or: ifstat -i eth0 1
# 2. socket states — SYN-RECV piling up means a SYN flood
ss -s
ss -tan state syn-recv | wc -l
# 3. kernel drop counters (these are the ones that matter)
nstat -az | grep -E 'ListenDrops|ListenOverflows|SyncookiesSent|TCPReqQFullDrop'
dmesg -T | tail -20 # look for: nf_conntrack: table full, dropping packet
# 4. connection tracking headroom
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_maxLeggili insieme. Bit al secondo alti, pacchetti al secondo bassi, CPU bassa è un attacco volumetrico, e il tuo compito è confermarlo e smettere di digitare comandi. Pacchetti al secondo alti, bit al secondo bassi — tanti pacchetti minuscoli — è un attacco di stato; guarda subito SyncookiesSent e il conteggio conntrack. Traffico modesto su entrambi gli assi con la CPU inchiodata è livello 7, e la risposta sta nel tuo web server e nel tuo database, non nel firewall.
Vale la pena interiorizzare una lettura controintuitiva: durante un vero flood volumetrico, la tua interfaccia può sembrare quasi calma. Stai vedendo i superstiti — quello che riesce a passare attraverso un collegamento già saturo, o quello che lo scrubber a monte ha lasciato passare. Un'interfaccia che mostra 900 Mbps su una porta da 1 Gbps mentre gli utenti segnalano irraggiungibilità totale non è una prova contro un attacco. Ne è la forma tipica.
Poi verifica che il traffico non sia semplicemente reale. Fai tail -f sul tuo access log per dieci secondi: un solo percorso ripetuto da migliaia di indirizzi distinti senza referrer è un attacco; una varietà di percorsi normali da browser normali è un pubblico, e limitarne la frequenza farà il lavoro dell'attaccante al posto suo.
Riduci il bersaglio prima dell'attacco, non durante
Ogni porta aperta è una coda che un attaccante può riempire, e ogni pacchetto che il tuo guest deve esaminare ti costa CPU e una voce in conntrack anche quando lo scarti. Il pacchetto più economico è quello che la tua macchina non riceve mai, ed è per questo che filtrare sopra il guest vale più che filtrare al suo interno.
Qui ci sono due livelli e non sono la stessa cosa. Il filtro edge è opzionale, configurato per singolo server, stateful e drop-by-default, e gira sull'hypervisor — il traffico che rifiuti lì non raggiunge mai la tua NIC virtuale, quindi non ti costa CPU, memoria né una voce nella tabella di stato. Il firewall del guest — il tuo ruleset nftables — è interamente tuo e non lo tocchiamo mai. Lo schema che sopravvive al contatto con un attacco è esprimere sull'edge la verità grossolana e stabile di livello 4 (“questa macchina serve le porte 80, 443 e SSH da questi indirizzi, e nient'altro esiste”) e riservare il ruleset del guest al lavoro fine che cambia con la tua applicazione. Entrambi sono descritti nella documentazione del firewall.
Poi restringi ciò che rimane. Limitare SSH agli indirizzi da cui amministri davvero non è qui una raffinatezza di hardening — elimina dalla tua macchina un'intera classe di attacchi a esaurimento delle connessioni. Un database vincolato a 127.0.0.1 o a un indirizzo WireGuard non può essere inondato di traffico da internet, in alcun modo. E se esponi un control plane puramente interno, mettilo dietro un'interfaccia WireGuard invece che su una porta pubblica protetta da una password.
SYN flood e la coda di accept
Un SYN flood sfrutta l'handshake: l'attaccante invia un flusso di richieste di connessione e non le completa mai, e ciascuna occupa uno slot nella coda SYN del kernel finché non va in timeout. Riempi la coda e gli handshake legittimi vengono scartati — il tuo servizio è up, in ascolto, e irraggiungibile.
Linux ha una soluzione pulita fin dagli anni '90, ed è attiva di default. Le SYN cookies permettono al kernel di smettere del tutto di allocare memoria per le connessioni semiaperte: codifica lo stato della connessione nel sequence number che rimanda indietro, e lo ricostruisce se il client completa l'handshake. Confermalo invece di darlo per scontato, e dai alle code spazio sufficiente per assorbire un picco:
# /etc/sysctl.d/99-flood.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_abort_on_overflow = 0
sysctl --systemtcp_synack_retries = 2 conta più di quanto sembri: il valore predefinito di cinque fa sì che un handshake contraffatto occupi il kernel per circa tre minuti, e portarlo a due lo riduce a circa sette secondi. Alzare somaxconn è solo metà del lavoro — la profondità della coda è il minimo tra quel valore e quello che l'applicazione ha richiesto, quindi nginx ha bisogno di listen 443 ssl backlog=8192; e di un reload prima che l'impostazione del kernel significhi qualcosa. È il classico caso di uno sysctl che sembra applicato e non fa nulla.
Se vuoi fermare il flood prima ancora che raggiunga l'applicazione, nftables può completare gli handshake per conto del kernel e consegnare solo le connessioni che si rivelano reali:
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport { 80, 443 } ct state new synproxy mss 1460 wscale 7 timestamp sack-perm
tcp dport { 80, 443 } accept
ip protocol icmp icmp type echo-request limit rate 5/second accept
}
}Testa synproxy su una macchina che puoi ancora raggiungere via console prima di farci affidamento; mal configurato, è un modo eccellente per chiuderti fuori dal tuo stesso server a colpi di firewall. Per la maggior parte delle persone il blocco sysctl qui sopra basta, e la classifica onesta è: prima syncookies, poi backlog, e synproxy solo se hai misurato che i primi due non erano sufficienti.
La tabella che nessuno guarda finché non è piena
Questo è quello che coglie in fallo anche le persone esperte. Un firewall stateful deve ricordare ogni flusso che ha visto, e quella memoria è nf_conntrack, una hash table di dimensione fissa scelta all'avvio. Una volta piena, il kernel scarta le nuove connessioni — tutte, tanto quelle dell'attaccante quanto quelle dei tuoi clienti — e registra un'unica riga in dmesg che nessuno sta guardando:
nf_conntrack: table full, dropping packetIl motivo per cui è così efficace è aritmetica pura. Una tabella predefinita su un piccolo VPS contiene qualche decina di migliaia di voci, e ogni pacchetto proveniente da un nuovo indirizzo sorgente ne crea una — inclusi i pacchetti UDP, inclusi i pacchetti che scarti, incluso il flood stesso. Ventimila pacchetti al secondo da sorgenti falsificate la riempiono in meno di due secondi, su un collegamento che trasporta pochi megabit. Il tuo grafico della banda non mostrerà assolutamente nulla.
# /etc/sysctl.d/99-conntrack.conf
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_buckets = 65536
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20
net.netfilter.nf_conntrack_udp_timeout = 20I timeout fanno più bene della dimensione. La durata predefinita per un flusso TCP established è cinque giorni, il che significa che un server up da una settimana sta mantenendo lo stato di connessioni terminate martedì scorso; un'ora è più che sufficiente per tutto ciò che non è una sessione SSH inattiva, e net.ipv4.tcp_keepalive_time = 600 mantiene vive quelle sessioni in modo corretto invece che per caso. Nel dimensionare, calcola circa 300 byte di memoria kernel per voce: 262.144 voci sono circa 80 MB, il che va benissimo su una macchina da 4 GB e non va affatto bene se lo imposti a dieci milioni perché lo diceva un forum.
Se gestisci qualcosa di genuinamente stateless e ad alto volume — un server DNS autoritativo, un server di gioco pubblico, un relay Tor — la risposta migliore è smettere del tutto di tracciarlo. Il connection tracking per un servizio che non ha connessioni significative è puro costo:
table ip raw {
chain prerouting {
type filter hook prerouting priority raw; policy accept;
udp dport 51820 notrack
tcp dport 9001 notrack
}
}Ricorda che questo traffico bypassa poi le tue regole ct state established, quindi ha bisogno di regole di accept esplicite nella chain filter. Questo è lo scambio: rinunci alla statefulness per quella porta e ottieni una tabella che non si può riempire.
Livello 7: il flood identico ai tuoi clienti
L'attacco più efficiente contro un piccolo server non è affatto un flood. Sono poche centinaia di richieste HTTP al secondo scelte con cura, ognuna perfettamente valida, ognuna diretta contro l'unico endpoint che esegue una query senza indice o renderizza una pagina non in cache. L'economia è brutale: la richiesta costa all'attaccante poche centinaia di byte e costa a te 200 millisecondi di CPU e una connessione al database. Perdi quella corsa a qualunque volume tu possa permetterti di servire.
L'istinto è bloccare l'attaccante. Contro una botnet distribuita su migliaia di indirizzi residenziali, ciascuno che invia due richieste al secondo, bloccare per IP è teatro — nessun rate limit per indirizzo che lasci passare i tuoi utenti reali scatterà mai, e passerai l'intero disservizio ad aggiungere regole mentre il sito resta giù. La mossa vincente è cambiare il costo, non il conteggio.
Prima la cache, e metti in cache anche il miss. Una cache a pagina intera trasforma una richiesta costosa in una lettura in memoria, e la direttiva che conta di più durante un attacco è quella che impedisce a mille miss simultanei di diventare mille query simultanee sul database:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app:64m max_size=2g inactive=60m;
server {
location / {
proxy_cache app;
proxy_cache_valid 200 301 302 10m;
proxy_cache_lock on; # one origin request per key, not a thousand
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on; # serve stale, refresh behind it
add_header X-Cache $upstream_cache_status always;
proxy_pass http://127.0.0.1:8080;
}
}proxy_cache_lock e use_stale sono le due righe che decidono se un picco di traffico è sopravvivibile. Senza di esse, nel momento in cui una chiave calda scade ogni richiesta in volo diventa una richiesta verso l'origine — lo stampede che trasforma un attacco gestibile in un disservizio. Con esse, il tuo backend serve una richiesta per chiave per intervallo e tutti gli altri ricevono una pagina leggermente stale, il che è lo scambio corretto in ogni situazione in cui l'alternativa è nessuna pagina affatto.
Poi limita specificamente la frequenza dei percorsi costosi, con generosità, e in un modo che fallisca in modo visibile:
limit_req_zone $binary_remote_addr zone=general:16m rate=20r/s;
limit_req_zone $binary_remote_addr zone=costly:16m rate=2r/s;
limit_conn_zone $binary_remote_addr zone=conn:16m;
limit_req_status 429;
limit_conn_status 429;
server {
limit_req zone=general burst=40 nodelay;
limit_conn conn 24;
location /search { limit_req zone=costly burst=5 nodelay; proxy_pass http://127.0.0.1:8080; }
location /login { limit_req zone=costly burst=3 nodelay; proxy_pass http://127.0.0.1:8080; }
}burst senza nodelay accoda le richieste in eccesso invece di rifiutarle, il che sotto attacco significa che nginx tiene educatamente aperte migliaia di connessioni per tuo conto — hai convertito un flood di richieste in un flood di connessioni. Usa nodelay, restituisci subito 429, e lascia che sia il client a gestirsela. E dietro qualsiasi proxy, ricorda che $binary_remote_addr è il proxy stesso a meno che tu non abbia configurato correttamente set_real_ip_from e real_ip_header; un rate limit basato sull'indirizzo del tuo stesso front-end blocca tutti gli utenti in blocco la prima volta che scatta.
Gli attacchi in stile Slowloris — molte connessioni, ciascuna che gocciola una richiesta un byte alla volta — mettono a malapena in difficoltà il modello a eventi di nginx, ma vale comunque la pena stringere i timeout: client_header_timeout 8s; client_body_timeout 8s; send_timeout 10s; reset_timedout_connection on;. Combinato con limit_conn, questa è l'intera difesa.
Perché fail2ban non è uno strumento anti-DDoS
fail2ban è genuinamente utile ed è rivolto a un problema diverso. Legge file di log, decide dopo N fallimenti in M minuti, e inserisce una regola firewall. Ogni parte di questo processo ha la forma sbagliata per un flood.
È troppo lento: un picco che dura quaranta secondi è già finito prima che si chiuda la finestra di ban. Legge i log, quindi l'attacco ti è già costato il prezzo pieno di ogni richiesta — il parsing avviene dopo il danno. Banna indirizzi singoli, quindi contro diecimila sorgenti o non fa nulla o inserisce diecimila regole lineari, a quel punto il firewall stesso diventa il collo di bottiglia e hai completato l'attacco per conto del tuo attaccante. Ed è guidato dai tuoi log, quindi un flood abbastanza grande da riempire un disco di righe di log può abbattere il server attraverso un percorso a cui non avevi mai pensato.
Se vuoi un blocco dinamico, fallo nel data plane, dove la ricerca è una hash e la scadenza è automatica. Un set dinamico di nftables è O(1) indipendentemente dalla dimensione, e dimentica da solo:
table inet filter {
set flooders {
type ipv4_addr
flags dynamic, timeout
timeout 10m
size 65535
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
ip saddr @flooders drop
tcp dport { 80, 443 } ct state new add @flooders { ip saddr limit rate over 50/second burst 100 packets } drop
tcp dport { 80, 443 } accept
}
}Leggi la regola con attenzione, perché la negazione trae in inganno molti: l'indirizzo viene aggiunto al set e scartato solo quando la frequenza è oltre il limite. Tieni fail2ban per ciò in cui è bravo — il tentativo lento di indovinare credenziali contro SSH e i login applicativi, dove un ban su scala temporale umana verso un indirizzo specifico è esattamente ciò che serve.
Non essere un'arma: l'amplificazione che stai ospitando
Ogni grande attacco volumetrico è alimentato da server i cui operatori non sapevano di starvi partecipando. Il meccanismo è un servizio UDP che risponde a una piccola richiesta contraffatta con una risposta enorme: un resolver DNS aperto restituisce 50 volte quanto gli è stato chiesto, NTP e memcached mal configurati sono peggio, e qualsiasi protocollo che risponde prima di poter verificare chi sta chiedendo è un candidato.
Le conseguenze ricadono su di te prima che sulla vittima. Il tuo uplink si riempie delle tue stesse risposte in uscita, il tuo provider vede traffico abusivo sostenuto uscire dalla tua porta, e i fornitori di transito che lo ricevono iniziano a fare null-routing dei range da cui proviene — questo è il motivo per cui la policy di uso accettabile traccia una linea netta sull'origine di attacchi e sull'amplificazione, pur permettendo sostanzialmente tutto il resto. Essere un riflettore inconsapevole è il modo più veloce per perdere un indirizzo che non ha nulla a che fare con quello che intendevi far girare.
# bind a recursive resolver to localhost, never 0.0.0.0
# unbound: interface: 127.0.0.1 + access-control: 0.0.0.0/0 refuse
# what is actually listening on a public address?
ss -ulpn
ss -tlpn
# from another machine, confirm you do not answer strangers
dig @your.server.ip example.com +short # must time out
ntpq -c rv your.server.ip # must failSe gestisci di proposito un servizio UDP pubblico — un server di gioco, un nameserver autoritativo, un endpoint WireGuard — la regola è la stessa che vale ovunque: limita la frequenza della risposta, mai della richiesta. Sia bind che knot implementano il response rate limiting; usalo, perché è la differenza tra servire i tuoi utenti e spedire l'attacco di qualcun altro.
La difesa più efficace è non essere trovabile
Tutto quanto sopra è contenimento del danno per un attacco che ha già trovato il tuo indirizzo. Vale la pena farlo, ed è comunque la seconda scelta migliore. Un'origine che nessuno sa nominare non è affatto attaccabile a livello 3 o 4, e separare l'indirizzo che serve il traffico dall'indirizzo che il mondo conosce è la cosa a maggior leva di tutta questa pagina.
Gli indirizzi di origine quasi mai trapelano per attacchi ingegnosi. Trapelano per la storia e per la trascuratezza, in un elenco breve e ben noto. I vecchi record DNS sono il colpevole abituale: gli archivi di DNS passivo ricordano per sempre il record A che avevi prima di mettere in campo un front-end. I log di certificate transparency sono pubblici e permanenti, quindi un certificato emesso per un hostname che puntava direttamente all'origine è una registrazione firmata e con timestamp di dove vivevi un tempo. La posta in uscita stampa l'indirizzo del server mittente negli header di ogni messaggio. E quello silenzioso: il tuo web server che risponde sul suo IP nudo, che permette a chiunque scansioni lo spazio degli indirizzi in cerca dell'HTML del tuo sito di trovarti per contenuto in un pomeriggio.
Quest'ultimo è risolvibile in due righe e quasi nessuno lo fa. Fai in modo che il server predefinito rifiuti tutto ciò che non arriva con un hostname che servi:
server {
listen 80 default_server;
listen 443 ssl default_server;
ssl_reject_handshake on; # nginx 1.19.4+: no certificate, no fingerprint
return 444; # close without a response
}Poi scegli un front. Un CDN commerciale è la risposta ovvia e comporta un costo reale che qui conta: stai aggiungendo un'azienda che termina il tuo TLS, vede il tuo plaintext, conosce la tua origine, e può ricevere un atto legale in una giurisdizione che non hai scelto — il che vanifica buona parte del motivo per cui il server è stato pagato in cripto senza un nome legato. Se lo fai comunque, tieni presente che l'origine non deve mai essere stata pubblica, e che l'intero schema fallisce nel momento in cui qualcuno trova un vecchio record.
L'alternativa che preserva la proprietà per cui hai pagato è essere il tuo stesso front. Un'istanza da cinque dollari in una seconda regione, con nginx come reverse proxy, e il filtro edge dell'origine che accetta traffico solo da quell'unico indirizzo e da nient'altro, ti dà un indirizzo sacrificabile che puoi rinumerare in cinque minuti e un control plane che risponde solo a te. È la stessa architettura, meno la terza parte. E dove il pubblico può usarlo, un servizio onion di Tor elimina del tutto l'IP dall'equazione — non c'è alcun indirizzo da inondare, anche se i servizi onion hanno una propria superficie d'attacco ai punti di introduzione, ed è per questo che il Tor moderno include una difesa proof-of-work proprio per questo.
Bloccare paesi, ASN e la forma del traffico
Prima o poi qualcuno suggerisce di bloccare un intero paese. È un approccio grossolano, occasionalmente corretto, e per lo più un modo per sentirsi impegnati.
È difendibile quando il tuo servizio ha un pubblico genuinamente limitato — un server di gioco regionale, uno strumento interno, un pannello di controllo — e sei pronto ad assumerti i falsi positivi, che includono i tuoi stessi utenti in viaggio e chiunque sia instradato in modo strano. È quasi inutile contro una botnet moderna, distribuita su connessioni residenziali in ogni paese, incluso il tuo, ed è attivamente dannoso per qualsiasi cosa pubblica: perderai silenziosamente persone reali e non vedrai mai quelle che se ne sono andate.
Bloccare per ASN è più preciso. Il traffico d'attacco che ha origine da una manciata di provider di hosting — range di VPS economici affittati a ore — può essere scartato per prefisso con molte meno vittime collaterali rispetto a un blocco per paese, perché gli utenti normali non navigano da un datacenter. I range dei datacenter sono anche, comodamente, da dove arrivano lo scraping e il credential stuffing.
Ma la versione durevole di questa idea non riguarda affatto la geografia: consiste nel filtrare sulla forma del traffico piuttosto che sulla sua origine. Un attacco di solito condivide qualcosa di strutturale — lo stesso user agent, lo stesso header mancante, lo stesso URL con lo stesso parametro di query, lo stesso fingerprint TLS, una totale assenza della seconda richiesta che un browser reale fa sempre. Trova la proprietà condivisa e scrivi una regola che non costa nulla e sopravvive al cambio di indirizzo dell'attaccante, cosa che farà più in fretta di quanto tu riesca a elencarli.
# in an attack: what do these requests have in common?
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 # addresses
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 # user agents
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 # pathsSe un solo user agent rappresenta il 90% delle richieste, sei a novanta secondi da una correzione. Se i primi venti indirizzi rappresentano il 2% del traffico ciascuno, smetti di cercare indirizzi — è un attacco distribuito e la risposta sono la cache e i rate limit.
Quando l'indirizzo è bruciato
A volte l'attacco è mirato, persistente e diretto proprio contro di te invece che contro un indirizzo casuale, e la risposta corretta è smettere di difendere l'indirizzo e abbandonarlo. Non è una sconfitta; per un piccolo servizio è spesso l'esito più economico possibile, ed è doloroso solo se non l'hai mai provato prima.
Ciò che rende veloce la manovra si decide in anticipo. Tieni i TTL DNS a 300 secondi come impostazione permanente — il costo è trascurabile e il beneficio è che puoi spostarti in cinque minuti invece che in un giorno. Mantieni il tuo deployment riproducibile, perché un server che puoi ricostruire è un server che puoi spostare; se ricostruirlo significa ricordare cosa hai fatto a marzo, non hai un piano, hai un ostaggio. Tieni un backup ripristinabile in una regione diversa, e conosci da un'esercitazione reale quanto tempo richiede un ripristino.
A quel punto la mossa vera e propria è breve: fai il deploy in un'altra regione, ripristina, verifica sul nuovo indirizzo tramite override dell'hostname prima di toccare il DNS, cambia il record, e tieni il vecchio server in funzione abbastanza a lungo perché gli ultimi resolver si aggiornino. La guida alla migrazione copre correttamente il sequenziamento, ed è la stessa sequenza sia che ti sposti per le prestazioni sia che ti sposti perché qualcuno è arrabbiato.
Il modello di fatturazione rende la prova praticamente gratuita: gli addebiti sono calcolati pro rata giornaliera sul tuo saldo, quindi una seconda macchina che fai il deploy, testi e distruggi nel giro di un pomeriggio costa centesimi, e non c'è alcun contratto, nessuna carta e nessun rinnovo da annullare. Non c'è alcun motivo perché la tua prima rinumerazione avvenga sotto attacco.
Ciò che tutto questo non ti garantisce
Due note conclusive oneste, perché il resto di questa pagina è ottimista per costruzione.
La prima è che nessun host assorbe tutto, e qualunque provider affermi il contrario sta vendendo. La capacità è finita, la capacità di scrubbing lo è ancora di più, e a una scala abbastanza grande l'atto economicamente razionale per una rete che protegge migliaia di clienti è smettere di annunciare un indirizzo. Noi lo trattiamo come ultima risorsa contro attacchi sostenuti che minacciano l'intero PoP, e te lo comunichiamo invece di lasciarti a fare debug da solo — ma una promessa che questo non possa mai accadere sarebbe una bugia, e dovresti diffidare di qualsiasi host che te la fa.
La seconda è che la ritorsione non è nel menu. I servizi booter e stresser sono attacchi a pagamento travestiti da strumenti di test, sono illegali nella maggior parte delle giurisdizioni che contano, sono ampiamente monitorati, e ti faranno terminare il server qui, in base a una policy che permette quasi tutto il resto. L'asimmetria che rende il DDoS attraente per un attaccante lo rende inutile per chi si difende: non puoi sommergere di traffico chi non ha nulla da perdere. Assorbi, metti in cache, spostati, e lascia che diventi noioso — cosa che, per chiunque paghi per il traffico che invia, prima o poi succede.
- Individua il livello in novanta secondi
Prima di qualsiasi modifica alla configurazione, procurati i due assi e i due contatori. Bit alti e pacchetti bassi è volumetrico; pacchetti alti e bit bassi è un attacco di stato; entrambi modesti con la CPU inchiodata è livello 7.
sar -n DEV 1 5 # bits/s and packets/s per interface ss -s # socket summary; watch synrecv nstat -az | grep -E 'ListenDrops|ListenOverflows|SyncookiesSent' cat /proc/sys/net/netfilter/nf_conntrack_count dmesg -T | grep -i conntrack | tailPoi escludi il successo e l'autolesionismo: scorri l'access log per dieci secondi. Se i percorsi sembrano quelli di un sito web in uso, hai un pubblico o un bug, non un attaccante.
- Rendi costoso attaccare il kernel
Due file, un reload. Sono sicuri su qualsiasi server general-purpose e rimuovono i due modi più comuni in cui una piccola macchina va giù.
cat > /etc/sysctl.d/99-flood.conf <<'EOF' net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 8192 net.core.somaxconn = 8192 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_keepalive_time = 600 net.netfilter.nf_conntrack_max = 262144 net.netfilter.nf_conntrack_buckets = 65536 net.netfilter.nf_conntrack_tcp_timeout_established = 3600 net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20 EOF sysctl --system sysctl net.ipv4.tcp_syncookies net.netfilter.nf_conntrack_max # verify, do not assumePoi fai in modo che l'applicazione corrisponda: nginx ha bisogno di
listen 443 ssl backlog=8192;altrimenti la coda del kernel che hai appena ampliato resta limitata al numero più piccolo richiesto dal processo. - Chiudi tutto ciò che non servi, sopra il guest
Enumera cosa è realmente in ascolto, decidi cosa deve stare su un indirizzo pubblico, e sposta le regole grossolane verso il filtro edge dell'hypervisor così che il traffico non raggiunga mai la tua NIC virtuale.
ss -tlpn; ss -ulpn # every public listener, with the process that owns itIn Server → Network → Firewall, imposta il filtro edge su drop di default e permetti solo le porte che servi, con SSH limitato agli indirizzi da cui amministri. Mantieni il ruleset
nftablesdel guest come secondo livello — ridondanza, non alternativa — e vincola database e interfacce di amministrazione a127.0.0.1o a un indirizzo WireGuard invece di proteggere con il firewall una porta pubblica. - Metti una cache davanti al percorso costoso
Questa è in assoluto la modifica a maggior valore per qualsiasi cosa renderizzi pagine. Aggiungi
proxy_cacheconproxy_cache_lock oneproxy_cache_use_stale, così mille miss simultanei diventano una sola richiesta verso l'origine e tutti gli altri ricevono una pagina leggermente vecchia.nginx -t && systemctl reload nginx curl -sI https://example.com/ | grep -i x-cache # MISS, then HITVerifica l'hit rate prima di fidartene. Una cache che non fa mai hit a causa di un
Set-Cookiesu ogni risposta è il falso senso di sicurezza più comune in tutto questo esercizio — controlla concurl -sIdue volte e leggiX-Cache. - Limita la frequenza dei percorsi che ti costano, non del visitatore
Applica un limite globale generoso e uno stretto sugli endpoint che toccano un database. Sempre
nodelay, sempre con un codice di stato, mai una coda silenziosa.limit_req_zone $binary_remote_addr zone=general:16m rate=20r/s; limit_req_zone $binary_remote_addr zone=costly:16m rate=2r/s; limit_req_status 429;Se nginx sta dietro a un qualsiasi proxy, configura prima
set_real_ip_fromereal_ip_header. Un limite basato sull'indirizzo del tuo front-end non rallenta un attaccante — rallenta tutti simultaneamente, la prima volta che scatta. - Togli l'indirizzo di origine dalla vista pubblica
Rendi inutile l'IP nudo, poi decidi cosa mettergli davanti. Il rifiuto è due righe e chiude per sempre la strada della scansione-di-internet-in-cerca-del-tuo-HTML.
server { listen 80 default_server; listen 443 ssl default_server; ssl_reject_handshake on; return 444; }Poi verifica le fughe di informazioni nell'ordine in cui di solito avvengono: record A storici negli archivi di DNS passivo, hostname nei log di certificate transparency che un tempo risolvevano verso l'origine, e header della posta in uscita. Se anche uno solo di questi nomina ancora l'indirizzo, un front-end non ti salverà — prima rinumera, poi metti un front.
- Informa l'operatore di rete, con i numeri
La mitigazione volumetrica è automatica e non richiede alcun ticket, ma una segnalazione corredata di prove permette a una persona di confermare cosa ha fatto l'automazione e di individuare le modalità di guasto che questa non può vedere — uno scrubber che resetta sessioni legittime, per esempio.
Invia l'indirizzo di destinazione, l'orario di inizio in UTC, il protocollo e le porte di destinazione, la frequenza su entrambi gli assi così come l'hai misurata, e se i tuoi stessi contatori mostrano i pacchetti in arrivo o che svaniscono a monte. Cinquanta righe di
tcpdump -ni eth0 -c 200valgono più di un paragrafo di descrizione. “Il sito è lento” non è azionabile; “185.x.x.x, 14:02 UTC, UDP verso 443, ~1,2 Mpps, l'interfaccia mostra 40 Kpps in arrivo” lo è. - Fai le prove di rinumerazione quando va tutto bene
Imposta oggi stesso i TTL DNS a 300 secondi e lasciali così. Poi fai l'esercitazione una volta, dall'inizio alla fine, su una macchina che distruggerai un'ora dopo.
# deploy a second instance in another region, restore, verify by hostname override curl --resolve example.com:443:<new-ip> https://example.com/ -sIAnnota quanto ti è costato in minuti. Quel numero è il tuo vero piano anti-DDoS — più di qualsiasi regola in questa guida — perché è quello che dice per quanto tempo un attacco mirato può tenerti offline. Gli addebiti sono calcolati pro rata giornaliera sul tuo saldo, quindi l'intera esercitazione costa centesimi.
Dove ogni attacco può davvero essere fermato
| Livello | Ferma | Non può fermare | Cosa ti costa | Chi lo controlla |
|---|---|---|---|---|
| Scrubbing a monte | Flood volumetrici — assorbiti sotto i 10 Gbps, scrubbing fino a 100, deviati via BGP oltre | Tutto ciò che rientra nei volumi di traffico normali: attacchi di stato, livello 7 | Niente. Sempre attivo, nessun ticket, latenza occasionale durante una deviazione | Noi, automaticamente |
| Filtro edge dell'hypervisor | Tutto ciò che è diretto a una porta chiusa, prima che raggiunga la tua NIC virtuale — niente CPU, nessuna voce di stato | Attacchi sulle porte che devi tenere aperte | Nulla a parte le regole che scrivi; stateful, drop-by-default | Tu, per singolo server |
| Firewall del guest (nftables) | SYN flood con synproxy, frequenze di pacchetti per sorgente, protocolli indesiderati | Traffico che ha già saturato il collegamento a monte — non arriva mai | CPU e una voce conntrack per ogni pacchetto, compresi quelli scartati | Tu, interamente |
| Tuning del kernel (sysctl) | Esaurimento della coda di accept e di conntrack — il classico killer dei piccoli server | Qualsiasi connessione valida e completata | Circa 80 MB di RAM con una dimensione conntrack sensata. Due file | Tu, interamente |
| Applicazione (cache + rate limit) | Flood di livello 7, stampede di richieste, endpoint costosi | Attacchi a livello di pacchetto — non raggiungono mai il web server | Pagine leggermente stale, e 429 per gli utenti che hai valutato male | Tu, interamente |
| Front-end reverse-proxy | Attacchi diretti all'origine — non c'è alcun indirizzo pubblico da colpire | Attacchi contro il front stesso, e tutto ciò che accade dopo che il tuo IP di origine è trapelato | 5 dollari/mese e un'altra macchina da tenere aggiornata | Tu, se lo gestisci da solo |
| CDN commerciale | Grandi attacchi di livello 3/4 e livello 7, su larga scala, con una coda di supporto | Un'origine che è mai stata pubblica, o che risponde sul proprio IP nudo | Terminazione TLS da parte di terzi che sanno chi sei e possono ricevere un atto legale | Loro |
| Servizio onion di Tor | Ogni attacco basato su IP — non c'è alcun indirizzo nel protocollo | Il flooding dei punti di introduzione, ed è per questo che il Tor moderno include una difesa proof-of-work | Latenza, e un pubblico disposto a usare Tor | Tu e la rete |
Domande che meritano risposta
Devo attivare la protezione DDoS sul mio server?
No. La mitigazione volumetrica si trova a monte dei PoP ed è sempre attiva — non c'è alcun prodotto da comprare né alcun interruttore da azionare. Sotto i 10 Gbps il traffico viene assorbito senza effetti visibili; tra 10 e 100 Gbps passa dallo scrubbing presso il transit provider, dove potresti notare un breve aumento di latenza; sopra i 100 Gbps il prefisso viene instradato su un percorso di scrubbing dedicato, la latenza sale in modo più evidente e il servizio resta raggiungibile. Le soglie sono pubblicate nella documentazione. Quello che non è automatico è tutto ciò che sta sopra il livello 4: l'esaurimento della tabella di stato e i flood a livello applicativo sembrano traffico ordinario visti da monte e vanno gestiti sulla tua macchina.
Farete il null-routing del mio IP se subisco un attacco?
Solo come ultima risorsa, e solo per attacchi sostenuti che minacciano l'intero PoP e non solo il tuo server — e in quel caso te lo comunichiamo entro pochi minuti invece di lasciarti scoprirlo da solo. Qualsiasi host che promette che questo non possa mai accadere sta descrivendo marketing, non una rete: a una scala sufficiente, proteggere migliaia di clienti significa prima o poi ritirare un indirizzo. La protezione realistica è renderti un bersaglio poco attraente — tieni l'indirizzo di origine non pubblicato, mantieni un piano di rinumerazione già collaudato, e conosci il tuo tempo di ripristino.
Il mio sito è giù ma il grafico della banda sembra normale. Cosa sta succedendo?
Quasi certamente esaurimento dello stato — il caso classico è una tabella nf_conntrack piena. Ventimila piccoli pacchetti al secondo da sorgenti falsificate riempiono una tabella predefinita in pochi secondi usando appena qualche megabit, quindi il grafico non mostra nulla e ogni nuova connessione viene scartata. Controlla dmesg -T | grep conntrack alla ricerca di “table full, dropping packet” e confronta nf_conntrack_count con nf_conntrack_max. L'altra possibilità è la lettura opposta: il collegamento a monte è già saturo, quindi la tua interfaccia ti mostra i superstiti invece dell'attacco.
Un piano più grande sopravviverà a un attacco a cui uno piccolo non sopravviverebbe?
A volte, e non per il motivo che ci si aspetta. Più vCPU e RAM aiutano davvero contro i flood di livello 7 e l'esaurimento dello stato, perché questi attacchi esauriscono CPU, memoria e spazio nelle tabelle. Contro un flood volumetrico il piano è quasi irrilevante — i pacchetti vengono scartati a monte della tua porta qualunque cosa ci sia dietro, e una porta da 2,5 Gbps non ti salva da 40 Gbps. Sistema prima la cache e il dimensionamento di conntrack, prima di fare l'upgrade; costa meno e di solito si scopre che era quello il problema reale.
Posso mettere Cloudflare o un altro CDN davanti a un VPS no-KYC?
Tecnicamente sì, e funziona bene. Capisci lo scambio prima di farlo: il CDN termina il tuo TLS e vede il tuo plaintext, conosce il tuo indirizzo di origine, tiene un account che ti identifica per email e spesso per metodo di pagamento, e può ricevere un atto legale in una giurisdizione che non hai scelto. Per un server comprato senza un nome collegato, questo reintroduce esattamente la parte terza che l'intero schema doveva evitare. Se il tuo modello di minaccia è il downtime piuttosto che l'esposizione, è una scelta ragionevole — ma l'origine non deve mai essere stata pubblica, altrimenti un vecchio record DNS vanifica tutto. Se il tuo modello di minaccia include chi sa dove ti trovi, gestisci invece il tuo reverse proxy su una seconda istanza, e fai in modo che l'origine accetti traffico solo da lì.
fail2ban basta per fermare un DDoS?
No, ed è lo strumento sbagliato più che uno strumento debole. Reagisce su scala temporale umana dopo aver fatto il parsing dei log, quindi le richieste ti sono già costate tutto quello che dovevano costarti; banna un indirizzo alla volta, il che non fa nulla contro diecimila sorgenti e trasforma il tuo firewall in una scansione lineare se glielo permetti; e dipende da log che un attaccante può inondare. Usalo per ciò in cui è eccellente — il tentativo lento di indovinare credenziali contro SSH e i form di login — e usa un set dinamico di nftables con un timeout per tutto ciò che avviene alla velocità di un flood.
Conviene bloccare interi paesi o ASN?
Bloccare per paese è difendibile solo quando il tuo pubblico è genuinamente limitato e accetti di perdere gli utenti in viaggio e quelli instradati in modo strano. È quasi inutile contro una botnet moderna, distribuita su connessioni residenziali ovunque, incluso il tuo stesso paese. Bloccare gli ASN dei datacenter è più preciso — le persone normali non navigano da un range di hosting — e cattura come bonus anche scraping e credential stuffing. L'istinto migliore è filtrare sulla forma del traffico: uno user agent condiviso, un header mancante, un percorso ripetuto. Quella regola continua a funzionare anche dopo che l'attaccante cambia indirizzo, cosa che farà entro pochi minuti.
Gestire un servizio onion di Tor mi rende immune?
Immune agli attacchi basati su IP, sì — non c'è alcun indirizzo nel protocollo contro cui puntare un flood, il che è una postura di sicurezza genuinamente diversa, non un miglioramento incrementale. Non è immune in generale: i servizi onion possono essere attaccati ai loro punti di introduzione, ed è per questo che il Tor moderno include una difesa proof-of-work che rende costoso il flooding per il client. Costa anche latenza e ti limita a un pubblico disposto a usare Tor. Molte persone gestiscono entrambi — un front clearnet per la copertura, e un indirizzo onion che continua a funzionare quando quello clearnet è sotto attacco.
Keep exploring
Blindare un nuovo VPS nella prima ora
Il prerequisito per questa pagina: chiudere le porte che non servi elimina un'intera classe di attacchi prima ancora di iniziare a fare tuning.
Migrare un VPS senza downtime
L'esercitazione di rinumerazione per intero, con il sequenziamento DNS che decide per quanto tempo un indirizzo bruciato ti terrà offline.
VPS per server di gioco
Il carico di lavoro che più attira questo tipo di attacco, e il tuning specifico per UDP che va di pari passo con una porta di gioco pubblica.
Deploy your offshore server.
Scegli una regione. Scegli un piano. Incolla una chiave. Paga. I prossimi 47 secondi sono a nostro carico.