Bloqueando tentativas de invasão no SSH com Fail2ban

Tabela de Conteúdo

Todo servidor com SSH exposto na internet recebe tentativas de login o dia inteiro, geralmente bots testando usuários e senhas comuns. O fail2ban monitora os logs de autenticação e bane automaticamente quem errar demais.

Instalando

$ sudo apt install fail2ban

Em distribuições baseadas em RHEL:

$ sudo dnf install fail2ban

Configurando o jail do SSH

O fail2ban vem com um arquivo de configuração padrão, jail.conf, mas ele é sobrescrito em atualizações. Por isso as customizações vão em jail.local:

$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edite a seção [sshd] de jail.local:

[sshd]
enabled = true
port = ssh
maxretry = 5
findtime = 10m
bantime = 1h
  • maxretry é o número de tentativas erradas antes do ban
  • findtime é a janela de tempo em que essas tentativas são contadas
  • bantime é quanto tempo o IP fica banido

Se a porta do SSH não for a padrão, ajuste port de acordo.

Evitando se banir sozinho

Antes de reiniciar o serviço, garanta que seu próprio IP não caia no bloqueio. Adicione um ignoreip na seção [DEFAULT] de jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.0.5

Coloque ali seu IP fixo ou a faixa da VPN que você usa pra administrar o servidor. Sem isso é fácil se trancar de fora depois de errar a senha algumas vezes testando a própria configuração.

Iniciando o serviço

$ sudo systemctl enable --now fail2ban
$ sudo systemctl status fail2ban

Verificando bans ativos

$ sudo fail2ban-client status sshd

O comando mostra quantos IPs já foram banidos e a lista dos que estão banidos no momento. Pra dar uma ideia real do volume, essa é a saída de uma das minhas VPS:

Status for the jail: sshd
|- Filter
|  |- Currently failed:    4
|  |- Total failed:    483296
|  `- File list:    /var/log/auth.log
`- Actions
   |- Currently banned:    3
   |- Total banned:    63964
   `- Banned IP list:    195.178.110.26 124.193.81.23 176.53.159.197

Mais de 480 mil tentativas de login falhas e 63 mil bans desde que o jail está ativo deixar o SSH exposto sem isso é convite pra brute force o tempo todo.

Vale entender de onde vem esse número: o arquivo de log (/var/log/fail2ban.log) gira e mantém só os registros recentes — o log mais antigo que ainda sobra nessa VPS é de poucos dias atrás. Quem sustenta o “Total failed”/“Total banned” ao longo do tempo é o banco interno do fail2ban (/var/lib/fail2ban/fail2ban.sqlite3), que não é afetado pela rotação do log de texto.

Desbloqueando um IP

Se algum IP legítimo acabar banido:

$ sudo fail2ban-client set sshd unbanip 203.0.113.10

Testando o filter sem esperar um ataque de verdade

Antes de confiar que o jail vai funcionar, dá pra testar o filter contra o log real sem esperar alguém tentar invadir de fato:

$ sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

O comando mostra quantas linhas do log bateram com o regex do filter. Se o número vier zero mesmo com tentativas de login falhas no log, o problema está no filter ou no caminho do log — não no jail em si.

Na mesma VPS, a parte que importa da saída é o resumo no final:

Results
=======

Failregex: 19513 total
Ignoreregex: 0 total

Lines: 25951 lines, 10212 ignored, 9301 matched, 6438 missed
[processed in 1.46 sec]

9301 das 25951 linhas do log bateram com o filter. O resto (ignored/missed) é log de outra coisa — sessão aberta/fechada, cron, etc. — não tentativa de autenticação, então não é motivo de preocupação. Zero em matched é que seria sinal de problema.

Diagnosticando quando o fail2ban não bane ninguém

O primeiro lugar pra olhar é o log do próprio fail2ban:

$ sudo tail -f /var/log/fail2ban.log

Um erro comum: o backend errado pra onde o log do SSH realmente está. Em distribuições que não gravam mais em /var/log/auth.log (só no journal do systemd), o backend auto às vezes não pega as linhas certas. Force explicitamente:

[sshd]
backend = systemd

Depois de qualquer mudança em jail.local, recarregue:

$ sudo fail2ban-client reload

Banindo mais forte quem reincide

Um ban de 1 hora não impede que o mesmo IP volte a tentar depois. Pra quem já foi banido várias vezes, vale aumentar a punição. Duas formas de fazer isso:

bantime.increment, mais simples, dobra o tempo de ban a cada reincidência do mesmo IP no mesmo jail:

[DEFAULT]
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w

O jail recidive, mais flexível, olha o histórico de bans em todos os jails e aplica uma punição própria pra quem se repete:

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 1w
findtime = 1d
maxretry = 5

Ativar o recidive valeu na hora: no mesmo dia em que liguei nessa VPS, ele já pegou IPs que o sshd tinha acabado de banir e voltaram a tentar depois do ban expirar:

fail2ban.actions [2303383]: NOTICE  [sshd] Ban 176.53.159.197
fail2ban.actions [2303383]: NOTICE  [recidive] Ban 176.53.159.197
fail2ban.actions [2303383]: NOTICE  [sshd] Ban 195.178.110.26
fail2ban.actions [2303383]: NOTICE  [recidive] Ban 195.178.110.26
$ sudo fail2ban-client status recidive
Status for the jail: recidive
|- Filter
|  |- Currently failed:    47
|  |- Total failed:    182
|  `- File list:    /var/log/fail2ban.log
`- Actions
   |- Currently banned:    8
   |- Total banned:    8
   `- Banned IP list:    176.53.159.197 176.53.159.198 195.178.110.26 45.148.10.152 45.148.10.151 45.148.10.141 45.148.10.147 45.148.10.157

8 IPs banidos pelo recidive já no primeiro dia — todos reincidentes que o sshd já tinha banido antes.

Na prática, bantime.increment resolve a maioria dos casos com bem menos configuração. O recidive compensa quando você tem vários jails (SSH, Nginx, etc.) e quer uma política única de “reincidente” cobrindo todos eles.

Simples assim :)