Fail2ban si installa per primo e di solito lì ci si ferma: il pacchetto c’è, il servizio gira, sshd sembra protetto. Sei mesi dopo si scopre che la jail legge un file che su questo sistema non esiste, e che tutto il resto — la posta, il pannello, il modulo di accesso del sito — non è mai stato protetto.

Vediamo che cosa conviene davvero attivare su un VPS ordinario con un sito, quali numeri metterci e come accertarsi che una jail funzioni invece di figurare soltanto nella configurazione.

Verifichi prima che sshd catturi qualcosa

Un solo comando:

sudo fail2ban-client status sshd

Nell’output ci sono le righe Currently failed, Total failed e Total banned. Se Total failed è zero su un server esposto da almeno un giorno, la jail non funziona. Confronti con la realtà:

sudo lastb | wc -l

Migliaia di tentativi falliti in lastb a fronte di zeri in Fail2ban significano una cosa sola: il filtro non guarda dove dovrebbe.

La causa più frequente è Debian 12. Non installa più rsyslog di default, il file /var/log/auth.log semplicemente non esiste, e la jail sshd di serie è impostata su quel file. Nessun messaggio di errore compare: il servizio parte, lo stato si mostra, i contatori restano a zero. Si risolve passando al journal di systemd:

[sshd]
enabled = true
backend = systemd

L’altra possibilità è reinstallare rsyslog, se un auth.log testuale le serve per altri strumenti. Ubuntu 24.04 si comporta allo stesso modo.

Dove scrivere le impostazioni

/etc/fail2ban/jail.conf non si tocca: viene sovrascritto all’aggiornamento del pacchetto, e qualsiasi modifica fatta lì un giorno sparirà in silenzio. Le sue impostazioni vanno in /etc/fail2ban/jail.local; quel file viene letto per ultimo e prevale su quello comune.

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.25
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd

ignoreip con il proprio indirizzo non è un lusso ma un’assicurazione: bloccarsi da soli per un errore di battitura capita prima di quanto sembri. Se non ha un indirizzo fisso, tenga una via d’ingresso di riserva oltre a SSH: la console o VNC del provider.

La jail che cambia il quadro: recidive

Una jail ordinaria ha memoria corta: cinque tentativi in dieci minuti, un’ora di blocco, e un’ora dopo tutto ricomincia. Un bot ci convive benissimo e tornerà domani e dopodomani. recidive colma proprio questo divario: non legge il registro di sistema ma quello di Fail2ban, cioè blocca chi Fail2ban ha già bloccato.

[recidive]
enabled  = true
bantime  = 1w
findtime = 1d
maxretry = 5

Si legge così: «bloccato cinque volte in un giorno, via per una settimana». La jail ha bisogno del file /var/log/fail2ban.log; se ha spostato la registrazione di Fail2ban su syslog, anche recidive richiederà backend = systemd.

L’effetto pratico di questa sola jail si nota di solito più della messa a punto fine di tutte le altre: gli habitué spariscono e nei registri resta solo rumore di fondo recente.

Che cos’altro attivare se il server ospita un sito

In ordine di utilità decrescente:

  • nginx-http-auth o apache-auth: forza bruta contro l’autenticazione di base. Serve se un’area amministrativa o un ambiente di prova è protetto da una password del server web;
  • nginx-botsearch: la scansione dei percorsi noti: /wp-login.php, /phpmyadmin, /.env. Non è un’intrusione ma una ricognizione, ed è proprio ciò che precede tutto il resto;
  • nginx-limit-req: funziona solo se in Nginx è definita limit_req_zone; senza di essa la jail è attiva e inutile;
  • postfix-sasl e dovecot: indispensabili se gestisce la posta in proprio. La ricerca delle password delle caselle è continua e di solito non la sorveglia nessuno.
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log

Il modulo di accesso del sito stesso è una storia a parte. L’applicazione non ha un registro proprio degli accessi falliti, e in access.log un tentativo fallito sembra un normale POST con codice 200 o 302, indistinguibile da uno riuscito con qualsiasi filtro generico. Ci sono due strade: far scrivere i fallimenti su syslog dall’applicazione (la maggior parte dei gestori di contenuti ha un’estensione per questo) oppure limitare la frequenza delle richieste alla pagina di accesso. La seconda è un limitatore, non un rilevatore, ed è meglio saperlo in anticipo.

I numeri

bantime = 10m è l’impostazione predefinita per cui Fail2ban viene spesso giudicato inutile: dieci minuti bastano a un bot per tornare. Un’ora al primo blocco più recidive per una settimana funziona molto meglio dei blocchi permanenti, che col tempo diventano un elenco di regole chilometrico.

maxretry = 3 per SSH è un modo sicuro per bloccare sé stessi. Cinque tentativi in dieci minuti tagliano fuori la forza bruta altrettanto bene.

Una ricerca lenta — un tentativo ogni cinque minuti — non cadrà mai nella finestra findtime. Non è un motivo per allungare la finestra a un giorno: otterrebbe falsi positivi contro i suoi stessi collaboratori. Contro la ricerca lenta non agisce una soglia, ma l’autenticazione con password disattivata.

Si accerti che il blocco arrivi ai pacchetti

Fail2ban si limita a chiamare un comando esterno. Se banaction non corrisponde a ciò che filtra davvero il traffico, il registro si riempirà di allegre righe Ban 198.51.100.7 mentre i pacchetti continueranno ad arrivare. Debian 12 usa nftables di default, e con UFW attivo la scelta giusta è banaction = ufw. Per verificare:

sudo nft list ruleset | grep -c f2b
sudo iptables -S | grep f2b

Almeno uno dei due deve mostrare regole. E il filtro in sé si può provare senza aspettare un attacco:

sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf

In fondo all’output si indica quante righe hanno trovato corrispondenza. Zero significa che filtro e registro non si incontrano, e proseguire con la configurazione è inutile.

Che cosa Fail2ban non fa

Non protegge dalla forza bruta distribuita: mille indirizzi con un tentativo ciascuno non raggiungeranno alcuna soglia con nessuna impostazione. Non corregge la causa: un indirizzo bloccato non migliora una password debole. E non sa nulla di reputazione: un indirizzo che ieri attaccava server altrui è pulito ai suoi occhi finché non se la prende con il suo. Da qui la combinazione pratica: chiavi al posto delle password, Fail2ban come limitatore di rumore e una lista di blocco condivisa (CrowdSec) come conoscenza dell’esperienza altrui.

Un problema a parte è che tutto questo va guardato ogni tanto. Nessuno digita fail2ban-client status jail per jail per settimane, e un aumento del numero di blocchi si nota quando qualcosa è già rotto. Nelle pagine dimostrative qui sotto gli stessi dati stanno su una sola pagina: l’elenco delle jail, chi è bloccato adesso e da dove arrivano i tentativi.