O Fail2ban é instalado em primeiro lugar e por aí costuma ficar: o pacote está lá, o serviço corre, o sshd parece protegido. Meio ano depois descobre-se que a jail lê um ficheiro que neste sistema não existe, e que tudo o resto — o correio, o painel, o formulário de acesso do sítio — nunca esteve protegido.

Vejamos o que convém mesmo ativar num VPS vulgar com um sítio, que números pôr ali e como garantir que uma jail funciona em vez de apenas constar na configuração.

Verifique primeiro se o sshd apanha alguma coisa

Um único comando:

sudo fail2ban-client status sshd

Na saída estão as linhas Currently failed, Total failed e Total banned. Se Total failed for zero num servidor exposto há pelo menos um dia, a jail não funciona. Confronte com a realidade:

sudo lastb | wc -l

Milhares de tentativas falhadas no lastb perante zeros no Fail2ban significam uma coisa só: o filtro não está a olhar para onde deve.

A causa mais frequente é o Debian 12. Já não instala o rsyslog por omissão, o ficheiro /var/log/auth.log simplesmente não existe, e a jail sshd que vem de origem está configurada para ler esse ficheiro. Não aparece mensagem de erro nenhuma: o serviço arranca, o estado é mostrado, os contadores ficam a zero. Resolve-se passando para o diário do systemd:

[sshd]
enabled = true
backend = systemd

A outra hipótese é repor o rsyslog como pacote, se um auth.log em texto lhe fizer falta para outras ferramentas. O Ubuntu 24.04 comporta-se da mesma maneira.

Onde escrever as definições

Em /etc/fail2ban/jail.conf não se mexe: é sobrescrito na atualização do pacote, e qualquer alteração feita ali um dia desaparecerá em silêncio. As suas definições vão para /etc/fail2ban/jail.local; esse ficheiro é lido em último lugar e prevalece sobre o comum.

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

ignoreip com o seu próprio endereço não é um luxo mas um seguro: bloquear-se a si próprio por um erro de escrita acontece mais depressa do que parece. Se não tem endereço fixo, mantenha uma entrada de reserva além do SSH: a consola ou o VNC do alojamento.

A jail que muda o panorama: recidive

Uma jail vulgar tem memória curta: cinco tentativas em dez minutos, uma hora de bloqueio, e uma hora depois recomeça tudo. Um robô convive muito bem com isso e voltará amanhã e depois. A recidive fecha exatamente essa brecha: não lê o registo do sistema mas o do próprio Fail2ban, ou seja, bloqueia quem o Fail2ban já bloqueou.

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

Lê-se assim: «bloqueado cinco vezes num dia, fora durante uma semana». A jail precisa do ficheiro /var/log/fail2ban.log; se passou o registo do próprio Fail2ban para syslog, a recidive também vai precisar de backend = systemd.

O efeito prático desta única jail costuma notar-se mais do que o ajuste fino de todas as outras: os habituais desaparecem e nos registos fica apenas ruído de fundo recente.

Que mais ativar se o servidor aloja um sítio

Por utilidade decrescente:

  • nginx-http-auth ou apache-auth: força bruta contra a autenticação básica. Necessário se uma área de administração ou um ambiente de testes está protegido por palavra-passe do servidor web;
  • nginx-botsearch: a varredura de caminhos conhecidos: /wp-login.php, /phpmyadmin, /.env. Não é uma intrusão mas reconhecimento, e é precisamente ele que precede tudo o resto;
  • nginx-limit-req: só funciona se no próprio Nginx estiver definida a limit_req_zone; sem ela a jail está ativa e é inútil;
  • postfix-sasl e dovecot: indispensáveis se gere correio próprio. A busca de palavras-passe de caixas de correio é contínua e habitualmente não é vigiada por ninguém.
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log

O formulário de acesso do próprio sítio é história à parte. A aplicação não tem registo próprio de acessos falhados, e no access.log uma tentativa falhada parece um POST vulgar com código 200 ou 302, indistinguível de uma bem-sucedida por qualquer filtro genérico. Há dois caminhos: fazer a aplicação escrever as falhas no syslog (a maioria dos gestores de conteúdos tem uma extensão para isso) ou limitar a frequência de pedidos à página de acesso. O segundo é um limitador, não um detetor, e é melhor sabê-lo de antemão.

Os números

bantime = 10m é a definição por omissão por causa da qual o Fail2ban é muitas vezes tido por inútil: dez minutos chegam a um robô para voltar. Uma hora no primeiro bloqueio mais a recidive por uma semana funciona muito melhor do que bloqueios permanentes, que com o tempo se transformam numa lista de regras interminável.

maxretry = 3 para SSH é uma forma segura de se bloquear a si próprio. Cinco tentativas em dez minutos cortam a força bruta igualmente bem.

Uma busca lenta — uma tentativa de cinco em cinco minutos — nunca cairá dentro da janela findtime. Não é motivo para esticar a janela para um dia: obteria falsos positivos contra os seus próprios colaboradores. Contra a busca lenta não atua um limiar, mas a autenticação por palavra-passe desativada.

Garanta que o bloqueio chega aos pacotes

O Fail2ban limita-se a chamar um comando externo. Se banaction não corresponder ao que filtra realmente o tráfego, o registo encher-se-á de alegres linhas Ban 198.51.100.7 enquanto os pacotes continuam a chegar. O Debian 12 usa nftables por omissão, e com o UFW ativo o correto é banaction = ufw. Para verificar:

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

Pelo menos um dos dois deve mostrar regras. E o próprio filtro pode ser testado sem esperar por um ataque:

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

No fim da saída indica-se quantas linhas corresponderam. Zero significa que o filtro e o registo não se encontram, e continuar a configurar não faz sentido.

O que o Fail2ban não faz

Não protege da força bruta distribuída: mil endereços com uma tentativa cada não atingirão limiar nenhum com definição nenhuma. Não corrige a causa: um endereço bloqueado não melhora uma palavra-passe fraca. E nada sabe de reputação: um endereço que ontem atacava servidores alheios está limpo aos seus olhos até se virar para o seu. Daí a combinação prática: chaves em vez de palavras-passe, o Fail2ban como limitador de ruído e uma lista de bloqueio partilhada (CrowdSec) como conhecimento da experiência alheia.

Um problema à parte é que tudo isto tem de ser visto de vez em quando. Ninguém escreve fail2ban-client status jail a jail durante semanas, e um aumento do número de bloqueios nota-se quando algo já se partiu. Nas páginas de demonstração abaixo os mesmos dados cabem numa só página: a lista de jails, quem está bloqueado neste momento e de onde vêm as tentativas.