Vai al contenuto
GLGiuseppe Lanza Scrivimi

Fail2ban su Linux: bloccare gli attacchi a forza bruta

Configurare fail2ban su Ubuntu 24.04 per bloccare con nftables gli attacchi a forza bruta: jail per SSH e web, ban progressivi, jail recidive, filtri personalizzati, comandi di gestione e risoluzione dei problemi.

Sistemi: Ubuntu 24.04 LTS · fail2ban 1.0 · nftablesLivello: IntermedioTempo: 45 minutiAggiornata: novembre 2026

Come funziona fail2ban

Un server esposto su Internet riceve di continuo tentativi di accesso automatici: password provate a raffica su SSH, sulle aree protette del sito, sui pannelli di login. fail2ban legge i log dei servizi, riconosce i tentativi falliti e, quando un indirizzo ne accumula troppi in poco tempo, lo blocca nel firewall per un periodo stabilito.

Il lavoro è organizzato in jail (“prigioni”). Ogni jail mette insieme quattro elementi:

  • la sorgente dei log: un file o il journal di systemd;
  • il filtro: le espressioni regolari (failregex) che riconoscono un tentativo fallito ed estraggono l’indirizzo dell’attaccante;
  • le soglie: quanti tentativi (maxretry) in quanto tempo (findtime) e per quanto bloccare (bantime);
  • l’azione: che cosa fare al ban, di solito aggiungere l’indirizzo a un set di nftables.
Servizio sshd, nginx Log journal o file Filtro failregex → IP Contatore maxretry / findtime Azione di ban banaction nftables Set nftables addr-set-sshd IP bloccato fino a bantime fail2ban.log jail recidive superata la soglia Ogni ban finisce nel log di fail2ban: la jail recidive lo rilegge
Il percorso di un tentativo fallito: dal log del servizio al filtro, dal contatore all’azione che inserisce l’indirizzo in un set di nftables.

fail2ban non sostituisce il firewall e non rende sicura una password debole: riduce il rumore e rallenta gli attacchi automatici. Le basi restano un firewall con policy restrittiva (vedi Firewall completo con nftables) e SSH con le sole chiavi (vedi SSH sicuro su Linux).

Installazione e file di configurazione

sudo apt update
sudo apt install fail2ban
fail2ban-client version          # Ubuntu 24.04: 1.0.2
sudo systemctl enable --now fail2ban
systemctl status fail2ban

Ubuntu 24.04 distribuisce fail2ban 1.0.2 con alcune modifiche del pacchetto Debian/Ubuntu che vedremo tra poco. Già dopo l’installazione la jail sshd è attiva.

“No module named 'asynchat'”

La prima versione del pacchetto uscita con Ubuntu 24.04 non partiva, perché Python 3.12 non include più il modulo asynchat. Il problema è corretto negli aggiornamenti: se il servizio non si avvia con questo errore, esegui sudo apt update && sudo apt full-upgrade e controlla la versione con apt policy fail2ban.

I file di configurazione

La configurazione sta in /etc/fail2ban:

PercorsoContenuto
jail.confValori predefiniti e definizione di tutte le jail disponibili, quasi tutte disattivate
jail.d/defaults-debian.confImpostazioni del pacchetto Ubuntu
jail.local, jail.d/*.localLe tue modifiche
filter.d/Filtri: le espressioni regolari per ogni servizio
action.d/Azioni: ban con nftables, iptables, invio di email…
fail2ban.conf / fail2ban.localImpostazioni del demone: log, database
Non modificare jail.conf

jail.conf appartiene al pacchetto: un aggiornamento può sovrascriverlo o chiederti di scegliere tra due versioni. Le tue impostazioni vanno nei file .local, che contengono solo le righe che cambiano. Non copiare jail.conf in jail.local: ti porteresti dietro centinaia di righe che non aggiornerai mai.

I file si leggono in quest’ordine, e ogni valore sovrascrive quello precedente: jail.conf, poi jail.d/*.conf in ordine alfabetico, poi jail.local, infine jail.d/*.local in ordine alfabetico. Ecco cosa contiene il file del pacchetto Ubuntu:

/etc/fail2ban/jail.d/defaults-debian.conf
[DEFAULT]
banaction = nftables
banaction_allports = nftables[type=allports]
backend = systemd

[sshd]
enabled = true

Due scelte importanti: i ban si fanno con nftables (in jail.conf il valore predefinito sarebbe iptables) e i log si leggono dal journal di systemd, per tutte le jail. La seconda ha una conseguenza che spiego nella prossima sezione.

Journal o file di log: il backend

Il backend dice a fail2ban dove leggere:

  • systemd: legge dal journal, filtrando per servizio con le regole journalmatch del filtro. Il parametro logpath viene ignorato;
  • auto (o pyinotify, polling): legge i file indicati da logpath.

Su Ubuntu 24.04 sshd scrive nel journal. Il file /var/log/auth.log esiste solo se è installato rsyslog, che copia i messaggi dal journal ai file classici: c’è nell’installazione standard di Ubuntu Server, ma manca spesso nelle immagini minimali, nei container e in alcune immagini cloud. Leggere dal journal funziona in tutti i casi, ed è per questo che il pacchetto imposta backend = systemd.

La conseguenza: il valore è in [DEFAULT], quindi vale anche per le jail che leggono file, come quelle di nginx e Apache o la jail recidive. Con il backend systemd quelle jail ignorano logpath e cercano nel journal messaggi che lì non ci sono: partono senza errori e non bannano mai nessuno. Per ogni jail che legge un file devi scrivere backend = auto, come vedrai negli esempi.

# i tentativi falliti di SSH nel journal
sudo journalctl -u ssh --since today | grep -E "Failed|Invalid"
# l'unità su Ubuntu si chiama ssh.service, non sshd.service

Non preoccuparti del nome dell’unità: il filtro sshd cerca nel journal sia per unità sia per nome del processo (_COMM=sshd), quindi trova i messaggi anche su Ubuntu.

Le impostazioni generali

Crea /etc/fail2ban/jail.local con i valori comuni a tutte le jail:

/etc/fail2ban/jail.local
[DEFAULT]
# Indirizzi mai bannati: loopback e reti di amministrazione.
# Mettici SEMPRE la rete da cui ti colleghi.
ignoreip = 127.0.0.1/8 ::1 192.168.50.0/24 198.51.100.7 2001:db8:50::/64

# Soglie: 5 tentativi in 10 minuti = ban di 1 ora
maxretry = 5
findtime = 10m
bantime  = 1h

# Ban progressivi: ogni nuovo ban dello stesso IP dura il doppio
# del precedente (1h, 2h, 4h...), fino a un massimo di una settimana
bantime.increment = true
bantime.maxtime   = 1w
bantime.rndtime   = 10m

# Azioni nftables (sono già le impostazioni del pacchetto Ubuntu:
# le ripeto per rendere esplicita la scelta)
banaction          = nftables-multiport
banaction_allports = nftables-allports
  • ignoreip accetta indirizzi, reti CIDR e nomi DNS, separati da spazi. È la tua assicurazione contro il ban di te stesso.
  • bantime.increment consulta il database di fail2ban per sapere quante volte un indirizzo è già stato bannato e allunga il ban di conseguenza. bantime.rndtime aggiunge una parte casuale, così i bot non possono prevedere quando tornare.
  • nftables-multiport blocca l’indirizzo solo sulle porte della jail; nftables-allports su tutte. I file corrispondenti sono in /etc/fail2ban/action.d/: nftables.conf, nftables-multiport.conf, nftables-allports.conf.

Il database conserva la storia dei ban per un giorno, valore troppo basso per ban progressivi che arrivano a una settimana e per la jail recidive. Allungalo:

/etc/fail2ban/fail2ban.local
[DEFAULT]
dbpurgeage = 30d

La jail sshd

La jail è già attiva grazie al file del pacchetto. Personalizzala in un file dedicato:

/etc/fail2ban/jail.d/sshd.local
[sshd]
enabled  = true
# se sshd ascolta su un'altra porta: port = 2222
port     = ssh
# aggressive: riconosce anche le connessioni chiuse prima
# dell'autenticazione e gli errori di negoziazione
filter   = sshd[mode=aggressive]
backend  = systemd
maxretry = 4

La modalità aggressive unisce le modalità normal, ddos ed extra del filtro: utile su un server esposto, dove molti bot si limitano ad aprire e chiudere connessioni. Ricarica e verifica:

sudo fail2ban-client -t            # controlla la configurazione
sudo systemctl reload fail2ban
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     37
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 1
   |- Total banned:     3
   `- Banned IP list:   203.0.113.66

La riga Journal matches conferma che la jail legge dal journal. Se invece vedi File list: /var/log/auth.log, sta usando il file.

Le jail per nginx e Apache

Le jail nginx-http-auth e apache-auth riconoscono gli errori di autenticazione HTTP Basic (le aree protette da auth_basic o AuthType Basic). Leggono i log degli errori, che sono file: ricordati backend = auto.

/etc/fail2ban/jail.d/web.local
[nginx-http-auth]
enabled = true
backend = auto
port    = http,https
logpath = /var/log/nginx/error.log

# Se usi Apache invece di nginx:
# [apache-auth]
# enabled = true
# backend = auto
# port    = http,https
# logpath = /var/log/apache2/error.log

In jail.conf esistono altre jail pronte per il web, per esempio nginx-limit-req (richieste respinte dal limite limit_req di nginx) e nginx-botsearch (scansioni alla ricerca di pagine vulnerabili). Si attivano allo stesso modo.

Dietro un proxy o una CDN

Se davanti al server c’è un bilanciatore o una CDN, nel log compare l’indirizzo del proxy, non quello del client. fail2ban bannerebbe il proxy, cioè tutti. Configura prima il server web perché registri l’IP reale (modulo real_ip di nginx, mod_remoteip di Apache); anche così, il ban nel firewall locale non ferma il traffico che arriva dal proxy, e va fatto sul proxy stesso.

La jail recidive

Alcuni bot aspettano la fine del ban e ricominciano. La jail recidive legge il log di fail2ban stesso e banna più a lungo, su tutte le porte, chi viene bannato ripetutamente da qualsiasi jail.

/etc/fail2ban/jail.d/recidive.local
[recidive]
enabled   = true
backend   = auto
logpath   = /var/log/fail2ban.log
banaction = %(banaction_allports)s
# 3 ban in un giorno = fuori per una settimana
maxretry  = 3
findtime  = 1d
bantime   = 1w

Con i ban progressivi già attivi, recidive è in parte ridondante, ma ha un vantaggio: blocca l’indirizzo su tutte le porte, non solo su quella del servizio attaccato. Il findtime di un giorno funziona solo se dbpurgeage è almeno altrettanto lungo, come abbiamo impostato prima.

Un filtro personalizzato

Esempio pratico: un sito WordPress dietro nginx. I tentativi di login falliti su /wp-login.php rispondono con codice 200 (la pagina di login viene ripresentata), quelli riusciti con un redirect 302. Nel log di accesso di nginx una riga tipica è:

203.0.113.66 - - [12/Nov/2026:10:15:42 +0100] "POST /wp-login.php HTTP/1.1" 200 4521 "-" "Mozilla/5.0"

Crea il filtro:

/etc/fail2ban/filter.d/nginx-wp-login.conf
[Definition]
# <HOST> è il segnaposto che cattura l'indirizzo IPv4 o IPv6.
# fail2ban riconosce e toglie da solo la data, quindi il filtro
# non deve descriverla
failregex = ^<HOST> -[^"]*"POST /wp-login\.php[^"]*" 200
ignoreregex =

Prima di attivarlo, provalo sul log reale con fail2ban-regex:

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-wp-login.conf

Nel riepilogo guarda le righe Failregex: N total e Lines: … matched: devono corrispondere al numero di tentativi falliti che ti aspetti. Con --print-all-matched vedi le righe riconosciute, con --print-all-missed quelle scartate. Lo stesso comando funziona sul journal, per esempio per provare il filtro di SSH:

sudo fail2ban-regex systemd-journal sshd

Quando il filtro è corretto, crea la jail:

/etc/fail2ban/jail.d/wordpress.local
[nginx-wp-login]
enabled  = true
backend  = auto
filter   = nginx-wp-login
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 5m
sudo fail2ban-client -t && sudo systemctl reload fail2ban
sudo fail2ban-client status nginx-wp-login

Due consigli sulle espressioni: ancorale sempre all’inizio della riga con ^, e non usare .* prima di <HOST>. Un’espressione troppo larga può catturare un indirizzo scritto dall’attaccante in un altro campo (per esempio nello user agent) e farti bannare chiunque.

Gestire i ban e vedere le regole

I comandi di fail2ban-client

sudo fail2ban-client status                        # elenco delle jail attive
sudo fail2ban-client status sshd                   # dettaglio e IP bannati
sudo fail2ban-client set sshd banip 203.0.113.66   # ban manuale
sudo fail2ban-client set sshd unbanip 203.0.113.66 # sblocco
sudo fail2ban-client unban 203.0.113.66            # sblocco da tutte le jail
sudo fail2ban-client reload sshd                   # ricarica una sola jail
sudo fail2ban-client -t                            # verifica la configurazione

Il log del demone racconta tutto quello che succede: Found per ogni tentativo riconosciuto, Ban e Unban.

sudo tail -f /var/log/fail2ban.log
sudo grep " Ban " /var/log/fail2ban.log | tail

I ban in nftables

Le azioni nftables creano una tabella dedicata, inet f2b-table, con una catena agganciata a input a priorità −1 (quindi valutata prima di un firewall a priorità filter) e un set di indirizzi per ogni jail: addr-set-sshd per IPv4, addr6-set-sshd per IPv6.

sudo nft list table inet f2b-table
sudo nft list set inet f2b-table addr-set-sshd
table inet f2b-table {
        set addr-set-sshd {
                type ipv4_addr
                elements = { 203.0.113.66 }
        }
        chain f2b-chain {
                type filter hook input priority filter - 1; policy accept;
                tcp dport 22 ip saddr @addr-set-sshd reject
        }
}

L’output reale contiene anche i set IPv6 e le regole delle altre jail. Il blocco predefinito è reject: l’attaccante riceve un rifiuto immediato. Le scadenze non sono nel set: è fail2ban a togliere l’indirizzo allo scadere del bantime, anche dopo un riavvio, grazie al suo database.

Ricaricare nftables cancella i ban

Se il tuo /etc/nftables.conf inizia con flush ruleset, ogni ricarica del firewall elimina anche la tabella f2b-table. Dopo systemctl reload nftables esegui sempre sudo systemctl restart fail2ban, che ricrea tabella e ban. I dettagli nella guida Firewall completo con nftables.

Notifica via email (facoltativa)

fail2ban può mandarti un messaggio a ogni ban. Serve un programma sendmail funzionante sul server, per esempio Postfix configurato come relay o il più leggero msmtp-mta, e il pacchetto whois per le informazioni sull’indirizzo bannato. Aggiungi a [DEFAULT] in jail.local:

/etc/fail2ban/jail.local – aggiunta
destemail = sistemi@example.com
sender    = fail2ban@server1.example.com
mta       = sendmail
# ban + email con whois; action_mwl aggiunge anche le righe di log
action    = %(action_mw)s

Su un server molto attaccato riceveresti decine di messaggi al giorno: valuta di attivare l’email solo per la jail recidive, scrivendo action = %(action_mw)s nella sua sezione invece che in [DEFAULT].

Problemi comuni

SintomoCausa probabileCosa fare
Il servizio fail2ban non parteErrore di sintassi in un file .local, pacchetto non aggiornato (asynchat)sudo fail2ban-client -t e journalctl -u fail2ban; aggiorna il pacchetto
Una jail non compare in fail2ban-client statusManca enabled = true, nome della sezione sbagliato, file con estensione diversa da .conf/.localControlla il nome del file e della sezione; leggi /var/log/fail2ban.log all’avvio
“Have not found any log file” all’avviologpath inesistente, per esempio /var/log/auth.log senza rsyslogPer SSH usa backend = systemd; per i file verifica il percorso
La jail web è attiva ma Total failed resta a 0Ereditato backend = systemd dal file del pacchetto: logpath viene ignoratoAggiungi backend = auto nella jail; verifica che status mostri File list
Il filtro non trova nienteFormato del log diverso da quello previsto dal filtroProva con fail2ban-regex e --print-all-missed
L’IP risulta bannato ma continua a collegarsiPorta della jail diversa da quella reale, tabella f2b-table cancellata da una ricarica di nftables, traffico verso container Docker (passa da forward, non da input)Controlla port; sudo nft list table inet f2b-table; riavvia fail2ban
Viene bannato sempre lo stesso indirizzo internoProxy, bilanciatore o NAT davanti al serverFai registrare al server web l’IP reale del client; aggiungi il proxy a ignoreip
Ti sei bannato da soloTroppi tentativi da un indirizzo non presente in ignoreipDa un altro indirizzo o dalla console: sudo fail2ban-client set sshd unbanip <tuo-IP>; poi aggiungi la tua rete a ignoreip
I ban progressivi non cresconodbpurgeage troppo basso: la storia dei ban viene cancellataImposta dbpurgeage in fail2ban.local almeno pari a bantime.maxtime

Il passo successivo

fail2ban è l’ultimo strato, non il primo. Riduci prima la superficie d’attacco: un firewall con policy drop e SSH aperto solo alle reti di amministrazione (Firewall completo con nftables), poi SSH con le sole chiavi e senza accesso di root (SSH sicuro su Linux). Con questi due passi, nei log di fail2ban vedrai molti meno tentativi, e quelli rimasti meritano davvero attenzione.

Prima di andare in produzione

Prova sempre la configurazione in laboratorio, salva quella attuale prima di modificarla e tieni un accesso di emergenza (console o accesso fuori banda) quando lavori su firewall e VPN da remoto.