Fail2ban: proteger SSH e Nginx contra brute force

Um VPS com SSH na porta 22 recebe centenas de tentativas de login por dia de bots automatizados. Fail2ban monitora logs em tempo real e bane automaticamente IPs que excedem o limite de tentativas, reduzindo a superfície de ataque sem exigir intervenção manual.

Instalação e configuração básica

Instalar e configurar jails padrão para SSH:

bash
# Instalar Fail2ban:
sudo apt update && sudo apt install fail2ban

# IMPORTANTE: nunca editar /etc/fail2ban/jail.conf diretamente
# Criar arquivo de override que persiste em atualizações:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Ou criar arquivo limpo com apenas as configurações desejadas:
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# Banir por 1 hora após 5 tentativas em 10 minutos:
bantime  = 1h
findtime = 10m
maxretry = 5

# Nunca banir estes IPs (seu IP de acesso):
ignoreip = 127.0.0.1/8 ::1 203.0.113.10

# Usar UFW para banir (padrão: iptables):
banaction = ufw

# Notificação por email:
# destemail = admin@seudominio.com.br
# sender    = fail2ban@seudominio.com.br
# action    = %(action_mwl)s   # banir + email com log

[sshd]
enabled  = true
port     = ssh
logpath  = %(sshd_log)s
backend  = %(sshd_backend)s
maxretry = 3           # SSH: limite mais restritivo
bantime  = 24h         # banir por 24h após 3 tentativas
EOF

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Jail para Nginx

Proteger Nginx contra scan de vulnerabilidades e brute force em formulários:

bash
# Adicionar ao /etc/fail2ban/jail.local:

# Bloquear scans de bots (404 repetidos, tentativas de exploits):
[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 5

[nginx-botsearch]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 2
bantime  = 1h

# Filtro customizado para login de app Node.js (429 Too Many Requests):
# /etc/fail2ban/filter.d/nodejs-login.conf:
sudo tee /etc/fail2ban/filter.d/nodejs-login.conf << 'EOF'
[Definition]
failregex = ^<HOST> .* "POST /api/auth/login HTTP.*" (401|429) .*$
ignoreregex =
EOF

# Jail para login da API:
sudo tee -a /etc/fail2ban/jail.local << 'EOF'

[nodejs-login]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
filter   = nodejs-login
maxretry = 10
findtime = 5m
bantime  = 2h
EOF

sudo fail2ban-client reload

Monitorar e gerenciar bans

Verificar status, desbanir IPs e consultar estatísticas:

bash
# Status geral de todas as jails:
sudo fail2ban-client status

# Status de jail específica (IPs banidos, estatísticas):
sudo fail2ban-client status sshd
# Saída:
# Status for the jail: sshd
# |- Filter
# |  |- Currently failed: 2
# |  |- Total failed: 847
# `- Actions
#    |- Currently banned: 3
#    |- Total banned: 156
#    `- Banned IP list: 203.0.113.5 198.51.100.8 192.0.2.1

# Desbanir IP específico:
sudo fail2ban-client set sshd unbanip 203.0.113.5

# Banir IP manualmente:
sudo fail2ban-client set sshd banip 203.0.113.99

# Logs do Fail2ban:
sudo tail -f /var/log/fail2ban.log

# Testar filtro sem aplicar ban (dry run):
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

# Verificar se UFW aplicou os bans:
sudo ufw status numbered | grep DENY

Banimento progressivo (aumentar penalidade por reincidência)

Aumentar automaticamente o tempo de ban para IPs reincidentes:

bash
# /etc/fail2ban/jail.local — bantime progressivo:
[DEFAULT]
# bantime.increment: cada reincidência aumenta o ban exponencialmente
bantime.increment   = true
bantime.factor      = 1
bantime.formula     = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
# 1ª vez: 1h, 2ª: 2h, 3ª: 4h, 4ª: 8h... até ~1 ano

# Alternativa simples: ban permanente após N reincidências:
# Criar ação de ban permanente para IPs que voltam muitas vezes:
# /etc/fail2ban/action.d/ufw-permanent.conf:
sudo tee /etc/fail2ban/action.d/ufw-permanent.conf << 'EOF'
[Definition]
actionban  = ufw deny from <ip> to any
actionunban = ufw delete deny from <ip> to any
EOF

[sshd-permanent]
enabled  = true
port     = ssh
logpath  = %(sshd_log)s
maxretry = 10    # 10 tentativas totais ao longo do tempo
bantime  = -1    # -1 = ban permanente
action   = ufw-permanent

Integração com Telegram para alertas em tempo real

Receber notificação no Telegram quando um IP é banido:

bash
# /etc/fail2ban/action.d/telegram-notify.conf:
sudo tee /etc/fail2ban/action.d/telegram-notify.conf << 'EOF'
[Definition]
actionban = curl -s -X POST https://api.telegram.org/bot<TOKEN>/sendMessage   -d chat_id=<CHAT_ID>   -d text="🚫 Fail2ban: IP <ip> banido na jail <name> em $(hostname). Tentativas: <failures>. Log: $(tail -5 <logpath> | tr '
' ' ')"

actionunban = curl -s -X POST https://api.telegram.org/bot<TOKEN>/sendMessage   -d chat_id=<CHAT_ID>   -d text="✅ Fail2ban: IP <ip> desbanido da jail <name> em $(hostname)"
EOF

# Adicionar ao jail.local:
[DEFAULT]
action = %(action_)s
         telegram-notify[TOKEN=SEU_BOT_TOKEN, CHAT_ID=SEU_CHAT_ID]

# Testar a notificação:
sudo fail2ban-client set sshd banip 1.2.3.4
sudo fail2ban-client set sshd unbanip 1.2.3.4

Não quer configurar manualmente?

Implante o VPS Linux em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Os dois são complementares. UFW limit: bloqueio rápido por rate de conexão (mais de 6 tentativas em 30s por IP), implementado diretamente no kernel via iptables, sem overhead de log parsing. Fail2ban: lê logs, entende o contexto (tentativas de login falhadas vs conexões bem-sucedidas), bane por tempo configurável, suporta múltiplos serviços (SSH, Nginx, Postfix), envia alertas. Use ambos: UFW limit como primeira linha, Fail2ban para bans duradouros e alertas.

Sim, especialmente ao testar configurações. Use ignoreip no [DEFAULT] para adicionar seu IP fixo (escritório, VPN): ignoreip = 127.0.0.1/8 ::1 SEU_IP_FIXO. Se você se banir: acesse o VPS pelo console do provedor (KVM/VNC) e execute: sudo fail2ban-client set sshd unbanip SEU_IP. Para desenvolvimento: adicione IPs de CI/CD no ignoreip também.

Para logs pequenos (< 100MB/dia): overhead negligenciável. Para servidores com alto tráfego Nginx (GBs de log/dia): o parsing contínuo pode consumir CPU significativo. Otimizações: aumentar findtime (menos varreduras), usar logpath com rotação diária, configurar maxlines para limitar o que o Fail2ban lê por vez. Alternativa para alto tráfego: CrowdSec (sucessor do Fail2ban, com análise assíncrona e lista de IPs maliciosos compartilhada).

Fail2ban parseia arquivos de log, o Node.js precisa gerar logs estruturados em arquivo. Configure morgan ou pino para escrever em /var/log/minha-app/access.log, crie um filtro Fail2ban que identifica o padrão de falha (ex: "POST /api/auth/login.*401"), e configure a jail para ler esse arquivo. Mais simples: usar Nginx como proxy mesmo que seja só para rate limiting, aí os logs ficam em /var/log/nginx/access.log e filtros prontos já existem.

CrowdSec: sucessor espiritual do Fail2ban com arquitetura moderna, análise assíncrona (não bloqueia I/O), banco de dados de IPs maliciosos compartilhado pela comunidade (IPs já banidos em outros servidores bloqueados preventivamente), API REST, dashboard web, múltiplos "bouncers" (UFW, Nginx, HAProxy). Fail2ban: mais simples, battle-tested, menos dependências, configuração bem documentada. Para novos VPS: considere CrowdSec. Para manutenção de setup existente: Fail2ban continua excelente.