Configurare fail2ban e bloccare automaticamente gli attacchi brute force
Come configurare fail2ban in modo pulito tramite jail.local, far funzionare il jail sshd su tutte e quattro le distribuzioni LTS attuali, verificare i blocchi e toglierli di nuovo, e quali errori altrimenti ti rallentano.
Ogni server con un indirizzo IPv4 pubblico riceve tentativi di login sulla porta 22 già pochi minuti dopo la prima installazione. Non è un attacco mirato, è rumore di fondo. fail2ban legge le righe di log di questi tentativi falliti e fa bloccare al firewall l'IP di origine per un tempo definito. Lo strumento è pacchettizzato in tutte le distribuzioni trattate qui e si mette in piedi in pochi minuti. Le insidie stanno altrove: su Debian 12 fail2ban parte volentieri con un errore di configurazione, su certi sistemi sembra bloccare senza che venga scartato davvero un solo pacchetto, e una riga sconsiderata in jail.local chiude fuori proprio te.
Questo articolo copre esattamente questi punti, per Debian 13, Debian 12, Ubuntu 24.04 LTS e Ubuntu 22.04 LTS.
Perché jail.local e mai jail.conf
Il file /etc/fail2ban/jail.conf appartiene al pacchetto. A ogni aggiornamento può essere sovrascritto, e nel migliore dei casi dpkg ti chiede come procedere, nel peggiore (durante un aggiornamento non presidiato) non chiede proprio niente. Le tue personalizzazioni vivono perciò in /etc/fail2ban/jail.local. Questo file dopo l'installazione non esiste, lo crei tu, e deve contenere solo i valori che cambi davvero.
Conta l'ordine in cui fail2ban legge. La pagina di manuale jail.conf(5) lo indica esplicitamente:
jail.confjail.d/*.confin ordine alfabeticojail.localjail.d/*.localin ordine alfabetico
I file letti più tardi vincono. In concreto significa: la tua jail.local sovrascrive anche le impostazioni di /etc/fail2ban/jail.d/defaults-debian.conf, il file che porta con sé il pacchetto della distribuzione. Molte guide affermano il contrario e per questo consigliano un file proprio in jail.d/. Non è necessario. jail.d/ ha senso solo se vuoi distribuire i singoli jail uno per uno tramite un sistema di gestione della configurazione.
Che cosa impone già la tua distribuzione
La differenza più grande tra i quattro sistemi non sta in fail2ban stesso, ma in quell'unico file che il pacchetto deposita in /etc/fail2ban/jail.d/defaults-debian.conf. Guardalo per primo:
cat /etc/fail2ban/jail.d/defaults-debian.conf
| Sistema | fail2ban | Contenuto di defaults-debian.conf |
|---|---|---|
| Debian 13 | 1.1.0 | banaction = nftables, banaction_allports = nftables[type=allports], per [sshd] in più backend = systemd, journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd ed enabled = true |
| Debian 12 | 1.0.2 | solo [sshd] ed enabled = true |
| Ubuntu 24.04 | 1.0.2 | banaction = nftables, banaction_allports = nftables[type=allports], backend = systemd, più [sshd] con enabled = true |
| Ubuntu 22.04 | 0.11.2 | solo [sshd] ed enabled = true |
Da qui discende quasi tutto il resto. Debian 13 e Ubuntu 24.04 leggono di serie il journal di systemd e bloccano tramite nftables. Debian 12 e Ubuntu 22.04 ricadono sulle impostazioni di jail.conf, quindi banaction = iptables-multiport e backend = auto. E auto non significa affatto "scegli tu quello giusto". Il commento in jail.conf dice che auto prova nell'ordine pyinotify e polling. Entrambi sono metodi basati su file. Il valore auto non sceglie mai il journal. Senza il file /var/log/auth.log il jail sshd gira lì a vuoto.
Installazione e primo controllo
apt update
apt install -y fail2ban
Su Debian 12 e Ubuntu 22.04 serve un pacchetto in più, se vuoi analizzare il journal invece di un file di log:
apt install -y python3-systemd
Su Debian 13 python3-systemd è una dipendenza rigida del pacchetto ed è già presente. Su Debian 12 è soltanto raccomandato, ed è la fonte di errore più frequente su questo sistema. Verifica poi versione e stato:
apt-cache policy fail2ban
fail2ban-client --version
systemctl status fail2ban --no-pager
Se qui compare già active (running), il grosso è fatto. Se invece c'è scritto failed, salta direttamente alla sezione sui messaggi di errore.
Costruire la propria jail.local
Crea /etc/fail2ban/jail.local. Questa versione funziona su tutti e quattro i sistemi, a patto che python3-systemd sia installato:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.7
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 7d
banaction = nftables
banaction_allports = nftables[type=allports]
[sshd]
enabled = true
backend = systemd
port = ssh
maxretry = 4
bantime = 2h
Il significato dei valori in chiaro: findtime è la finestra temporale mobile, maxretry il numero di tentativi falliti al suo interno, bantime la durata del blocco. Quattro tentativi falliti in dieci minuti portano qui a due ore di ban. Le indicazioni di tempo accettano suffissi come m, h, d e w, un numero puro significa secondi. Un bantime di -1 blocca in modo permanente.
La parte interessante è bantime.increment. Con questa opzione fail2ban raddoppia il tempo di blocco a ogni ricaduta dello stesso IP, con il tetto fissato da bantime.maxtime. Un singolo errore di battitura di un utente reale costa due ore, un bot ostinato dopo pochi giri arriva a una settimana. La funzione esiste da fail2ban 0.11, quindi è disponibile anche su Ubuntu 22.04. Perché resti valida anche attraverso i riavvii, il database deve conservare lo storico, cosa che si regola con dbpurgeage in /etc/fail2ban/fail2ban.conf, di serie un giorno. Se imposti bantime.maxtime = 7d, alza di conseguenza anche dbpurgeage in un file fail2ban.local.
Due trappole in questo blocco. Primo, port = ssh: si risolve tramite /etc/services nella porta 22. Se il tuo servizio SSH gira sulla 2222, lì deve esserci port = 2222, altrimenti nasce una regola firewall per una porta a cui non bussa nessuno. Secondo, ignoreip: inserisci lì l'IP fisso del tuo ufficio, ma mai un'intera rete di un provider. E non affidarti solo a quello. Il valore ignoreself è comunque impostato di serie su true e protegge gli IP del server stesso.
Differenze a seconda del sistema
Su Debian 12 hai due strade equivalenti. O resti con backend = systemd e installi python3-systemd, oppure installi rsyslog, ometti backend e lavori con /var/log/auth.log. Su Debian 12 dovresti inoltre impostare banaction in modo consapevole. Il pacchetto raccomanda nftables oppure iptables, e di questi apt di norma installa solo nftables. L'impostazione predefinita di jail.conf è però iptables-multiport. Su un'installazione snella fail2ban ricorre quindi a un binario che non c'è proprio.
Su Ubuntu 22.04 rsyslog è incluso nello standard server, /var/log/auth.log esiste e il jail funziona senza interventi. Se lì non vuoi cambiare niente, ometti semplicemente backend e banaction nella tua jail.local. fail2ban 0.11.2 conosce sì l'azione nftables, ma su questo sistema la via di iptables-multiport è quella più battuta.
Su Debian 13 e Ubuntu 24.04 la tua jail.local coincide con le impostazioni del pacchetto. Non fa danni, rende soltanto la configurazione esplicita e quindi leggibile.
Applicare e verificare che funzioni davvero
Rileggere la configurazione senza perdere i ban già attivi:
fail2ban-client reload
Su Debian 12 e Ubuntu 24.04 fail2ban 1.0.2 segnala all'avvio e al ricaricamento l'avviso 'allowipv6' not defined in 'Definition'. È innocuo, il servizio continua comunque a lavorare normalmente.
Un systemctl restart fail2ban serve di rado e azzera i ban in corso. Poi le interrogazioni di stato:
fail2ban-client ping
fail2ban-client status
fail2ban-client status sshd
L'ultimo output è quello significativo. Mostra Currently failed, Total failed, il numero dei ban e l'elenco degli indirizzi bloccati. È esattamente qui che la maggior parte delle guide si ferma, ed è esattamente qui che comincia il problema: un jail che parte pulito e riporta "0 banned" può essere completamente cieco. Un jail senza riscontri ha lo stesso identico aspetto di un jail che legge la sorgente di log sbagliata.
Tre controlli rendono visibile questa differenza. Primo: il filtro vede davvero quelle righe?
fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf
Con un backend basato su file, invece:
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
Alla fine compare una riga come Lines: 4211 lines, 0 ignored, 137 matched, 4074 missed. Se lì c'è 0 matched mentre nel log si vedono chiaramente dei tentativi falliti, non torna il filtro oppure la selezione nel journal.
Secondo: quale filtro sul journal usa realmente il jail?
fail2ban-client get sshd journalmatch
La risposta cambia a seconda del sistema, e questa differenza è importante. Debian 13 riporta _SYSTEMD_UNIT=ssh.service + _COMM=sshd, Ubuntu 24.04 invece _SYSTEMD_UNIT=sshd.service + _COMM=sshd. Lì la unit si chiama quindi sshd.service, su Debian ssh.service. Su Debian 12 e Ubuntu 22.04 allo stato di fabbrica torna No journal match filter set, perché lì il backend systemd non è preimpostato e il jail legge un file di log. Non è un errore, è la conferma che un filtro sul journal non esiste proprio. Solo con backend = systemd nella tua jail.local su questi sistemi ne diventa attivo uno.
Le singole condizioni sono separate da + e vengono combinate in OR. _SYSTEMD_UNIT=ssh.service + _COMM=sshd corrisponde quindi a tutto ciò che proviene dalla unit ssh.service oppure a qualsiasi processo di nome sshd. Per la controprova nel journal usa esattamente il nome della unit che compare nell'output qui sopra:
journalctl -u ssh --no-pager -n 50
journalctl _COMM=sshd --no-pager -n 50
Su Ubuntu 24.04 la prima riga diventa di conseguenza journalctl -u sshd --no-pager -n 50. Chi usa qui il nome sbagliato vede un output vuoto e poi cerca dalla parte sbagliata.
Se qui compaiono righe con Failed password for invalid user ma fail2ban non conta niente, la causa sta nella selezione nel journal. Diventa rilevante quando usi OpenSSH dai backports oppure passi all'attivazione via socket. Da OpenSSH 9.8 il processo figlio si chiama sshd-session e non più sshd, e con l'attivazione via socket la unit non si chiama più ssh.service. In quel caso aggiungi nella tua jail.local (su Ubuntu 24.04 con _SYSTEMD_UNIT=sshd.service):
[sshd]
journalmatch = _SYSTEMD_UNIT=ssh.service + _COMM=sshd + _COMM=sshd-session
Terzo, la prova dei fatti. Blocca a mano un indirizzo della rete di documentazione e controlla se arriva davvero nel firewall:
fail2ban-client set sshd banip 203.0.113.10
Il comando risponde con 1, cioè un indirizzo bloccato. Con quale strumento verifichi questo blocco dipende dalla banaction effettiva, che a seconda della distribuzione è preimpostata in modo diverso:
| Sistema | banaction di serie | Verifica del blocco attivo |
|---|---|---|
| Debian 13 | nftables | nft list table inet f2b-table |
| Ubuntu 24.04 | nftables | nft list table inet f2b-table |
| Debian 12 | iptables-multiport | iptables -n -L f2b-sshd |
| Ubuntu 22.04 | iptables-multiport | iptables -n -L f2b-sshd |
Il più vecchio Debian 11 si comporta come Debian 12. Da dove venga la differenza lo vedi in /etc/fail2ban/jail.d/defaults-debian.conf: solo Debian 13 e Ubuntu 24.04 portano lì un banaction = nftables, gli altri restano con l'impostazione iptables-multiport di jail.conf. Chi non vuole tenere a mente questa differenza server per server mette banaction = nftables nella propria jail.local, come nell'esempio più sopra, e poi verifica ovunque con nft.
Con l'azione nftables esiste una tabella f2b-table nella famiglia inet, al suo interno una chain f2b-chain con priorità filter - 1 e un set di nome addr-set-sshd. Lì deve comparire 203.0.113.10 come elemento. Una catena f2b-sshd su questi sistemi non esiste, e iptables -n -L f2b-sshd risponde infatti con No chain/target/match by that name. Al contrario, su Debian 12 e Ubuntu 22.04 nft list table inet f2b-table segnala Error: No such file or directory, finché lì lavora iptables-multiport. Nessuno dei due messaggi dimostra una configurazione rotta, dimostrano solo che il comando non corrisponde all'azione impostata.
Con l'azione iptables 203.0.113.10 deve comparire di conseguenza nella catena f2b-sshd. Solo quando l'IP compare nel firewall la catena è chiusa dal log fino al pacchetto scartato. Non dimenticare di ripulire.
Togliere di nuovo i blocchi
Sbloccare un singolo indirizzo da un jail preciso:
fail2ban-client set sshd unbanip 203.0.113.10
Un indirizzo da tutti i jail in una volta sola:
fail2ban-client unban 203.0.113.10
E in caso di emergenza, tutto a zero:
fail2ban-client unban --all
L'ultimo comando è quello che ti serve quando ti sei chiuso fuori da solo e sei seduto davanti a una console di emergenza sul server. Toglie tutti i blocchi senza riavviare il servizio. Se al server non arrivi proprio più, resta solo la strada della console nell'area clienti. Sui server root KVM e sui server dedicati di KernelHost trovi lì una sessione VNC che funziona indipendentemente da SSH. È anche il motivo per cui su un server senza una seconda via di accesso non dovresti impostare bantime = -1.
Un blocco che togli torna subito se i tentativi falliti vengono ancora conteggiati nella finestra temporale. Per questo unban azzera anche i relativi tentativi falliti. Chi deve restare libero in modo permanente va in ignoreip, seguito da fail2ban-client reload.
Convivenza con UFW e nftables
Qui nasce il danno più difficile da notare, perché fail2ban continua allegramente a segnalare blocchi mentre i pacchetti passano.
UFW è un frontend per iptables. All'avvio o al ricaricamento riscrive la tabella filter tramite iptables-restore. In quel momento le chain create da fail2ban spariscono senza sostituzione, e fail2ban non le ricrea da solo. Dopo ogni ufw enable, ufw disable o ufw reload vale perciò la regola: systemctl restart fail2ban. Fino ad allora in fail2ban-client status sshd compare un lungo elenco di IP bloccati, di cui nemmeno uno viene davvero fermato.
È più pulito far bloccare fail2ban direttamente tramite UFW. L'azione si trova in /etc/fail2ban/action.d/ufw.conf ed è presente in tutte e quattro le distribuzioni. In jail.local:
[DEFAULT]
banaction = ufw
banaction_allports = ufw
Così i blocchi finiscono come regole dentro UFW stesso e sopravvivono al suo ricaricamento. Controllo:
ufw status numbered
Gli indirizzi bloccati compaiono lì come DENY IN molto in alto nell'elenco. È importante che le regole di blocco stiano prima della tua apertura per la porta 22, altrimenti ha effetto per prima l'apertura. L'azione fornita di serie le inserisce perciò in testa con insert.
Chi non usa UFW e cura invece un proprio insieme di regole in /etc/nftables.conf ha un problema imparentato. Un flush ruleset all'inizio di quel file, così come lo propone il modello standard, al ricaricamento cancella anche f2b-table. O rinunci al flush globale e svuoti solo la tua tabella, oppure agganci il riavvio di fail2ban al servizio nftables. Il vantaggio della tabella propria di fail2ban è che con priorità filter - 1 agisce prima della normale tabella filter e quindi anche prima delle regole provenienti da iptables-nft.
Quello che non dovresti mischiare è banaction = nftables con un tuo insieme di regole iptables, aspettandoti che iptables -L mostri i blocchi. Le due strade funzionano in parallelo, ma ogni strumento mostra solo il proprio insieme di regole. Verifica sempre con lo strumento che corrisponde alla banaction impostata.
I messaggi di errore alla lettera e cosa ci sta dietro
I log li trovi, a seconda del sistema, in due posti:
journalctl -u fail2ban --no-pager -n 100
tail -n 100 /var/log/fail2ban.log
Failed during configuration: Have not found any log file for sshd jail
Il messaggio distintivo di Debian 12. Il jail gira con backend = auto, cerca /var/log/auth.log, e il file non esiste perché non è installato alcun rsyslog e tutto finisce nel journal. Il servizio non parte proprio, systemctl status fail2ban mostra Failed with result 'exit-code'. Due soluzioni: o apt install -y rsyslog e un riavvio, oppure in jail.local sotto [sshd] la riga backend = systemd. La seconda strada è la più moderna, ma richiede il pacchetto successivo.
Backend 'systemd' failed to initialize due to No module named 'systemd'
Hai impostato backend = systemd, ma manca il collegamento Python al journal. Su Debian 12 python3-systemd è solo raccomandato e nelle installazioni senza pacchetti raccomandati non viene tirato dentro. Rimedio:
apt install -y python3-systemd
systemctl restart fail2ban
Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?
Il client non raggiunge il server. Nella stragrande maggioranza dei casi il servizio semplicemente non è in esecuzione, perché si è fermato su un errore di configurazione. Prima leggi systemctl status fail2ban, poi il journal. Di rado, dopo un'interruzione brusca, resta in giro un file socket orfano, e allora l'avvio del server segnala Server already running. In quel caso rimuovi il file socket e riavvia.
NOK: ('sshd',)
La risposta a fail2ban-client status sshd quando quel jail a runtime non esiste. O manca enabled = true, oppure hai sbagliato a scrivere il nome, oppure un errore di sintassi più in alto in jail.local si è mangiato la sezione. Quali jail girano davvero lo mostra fail2ban-client status. Quello che fail2ban ha assemblato da tutti i file lo mostra il dump della configurazione:
fail2ban-client -d
Error banning e iptables not found
Il jail conta correttamente, ma il blocco fallisce sull'azione. Tipico sulle installazioni Debian 12 snelle, dove è presente solo nftables mentre l'impostazione predefinita è iptables-multiport. O recuperi con apt install -y iptables, oppure in jail.local passi a banaction = nftables. La seconda strada è quella che Debian 13 e Ubuntu 24.04 percorrono comunque già da soli.
Quando non serve più niente: lo smontaggio ordinato
Poiché tutte le personalizzazioni stanno in jail.local, la via del ritorno è corta. Metti da parte il file, riavvia il servizio, e sei di nuovo alle impostazioni del pacchetto:
mv /etc/fail2ban/jail.local /root/jail.local.bak
systemctl restart fail2ban
fail2ban-client status
Se così funziona, l'errore è nella tua configurazione, e reinserisci i blocchi uno alla volta. Se non funziona nemmeno così, dipende dall'ambiente, quindi dalla sorgente di log mancante o dallo strumento firewall mancante. Proprio per questo jail.conf va lasciato intatto: questo smontaggio in dieci secondi è possibile solo finché il file del pacchetto è allo stato di fabbrica.
Per chiudere, l'inquadramento. fail2ban riduce il rumore nei log e ferma i tentativi di accesso lenti e ripetuti. Un attacco serio da una grande botnet usa ogni indirizzo una sola volta e passa accanto a qualsiasi limitazione di frequenza. Il passo più efficace contro il brute force su SSH resta la disattivazione dell'accesso con password a favore delle chiavi. E contro gli attacchi volumetrici aiuta comunque solo il filtraggio in rete, da noi con 3,2 Tbps di filtraggio Arbor in tempo reale direttamente nel datacenter di Francoforte sul Meno e fino a 17 Tbps di capacità di filtraggio globale nelle tariffe Professional. fail2ban è lo strato sottostante, sull'host, e lì svolge il suo compito in modo affidabile non appena tre cose sono a posto: la sorgente di log giusta, l'azione firewall giusta e una jail.local che, nel dubbio, puoi rimuovere di nuovo in un colpo solo.
Domande frequenti
Devo davvero lasciare jail.conf intatto?
Perché su Debian 12 fail2ban non parte dopo l'installazione?
Che cosa significa esattamente backend = auto?
Come tolgo di nuovo un blocco?
Perché fail2ban blocca, ma l'attaccante passa lo stesso?
Da che cosa capisco che fail2ban funziona davvero?
fail2ban sostituisce una protezione DDoS?
2026 KernelHost GmbH. Tutti i diritti riservati. Questa guida è protetta dal diritto d'autore. La ripubblicazione su altri siti web, anche parziale o in forma modificata, non è consentita senza il nostro consenso scritto. Le citazioni con indicazione della fonte e un link sono le benvenute.

