Não é checklist genérico do DigitalOcean. Aqui eu documento as 14 camadas de hardening que aplico em toda infraestrutura que monto — desde VPS de $5 até clusters com múltiplos serviços. Cada item vem com o código real que uso, explicado.
Se você tem um servidor exposto à internet, ele já está sendo escaneado. Bots automatizados testam SSH, portas abertas e configs default 24/7. A questão não é se você vai ser atacado — é quando.
Aviso Legal: Todo o conteúdo deste artigo é publicado exclusivamente para fins educacionais e de segurança defensiva. Configurações foram anonimizadas. Use apenas em infraestrutura própria ou com autorização explícita.
1. SSH Hardening — Superfície de ataque #1
O SSH é o vetor mais explorado. A configuração default do OpenSSH é insegura por design — permite root, senhas, forwarding e ciphers fracos.
# /etc/ssh/sshd_config
Port 2222 # Porta não-padrão reduz 99% dos bots
PermitRootLogin no # NUNCA root via SSH
PasswordAuthentication no # Somente chaves Ed25519
PubkeyAuthentication yes
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deploy # Whitelist explícita
# Ciphers modernos — descarte tudo pré-2020
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Desabilitar tudo que não é necessário
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
Gerar chave Ed25519 com 100 rounds KDF
ssh-keygen -t ed25519 -a 100 -C "deploy@prod-$(date +%Y)"
ssh-copy-id -p 2222 deploy@seu-servidor
# NUNCA desconecte a sessão atual antes de testar
ssh -p 2222 deploy@seu-servidor # nova janela!
Alerta de login em tempo real
Toda conexão SSH gera notificação:
cat << 'SCRIPT' | sudo tee /usr/local/bin/login_alert.sh
#!/bin/bash
BODY="Login: $PAM_USER from $PAM_RHOST on $(hostname) at $(date)"
echo "$BODY" | mail -s "SSH Alert: $(hostname)" admin@yourdomain.com
# Webhook (Discord/Slack/Telegram)
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"content\": \"$BODY\"}" >/dev/null 2>&1 &
SCRIPT
sudo chmod +x /usr/local/bin/login_alert.sh
echo "session optional pam_exec.so /usr/local/bin/login_alert.sh" \
| sudo tee -a /etc/pam.d/sshd
2. Firewall (UFW) — Deny all, allow explicit
# Reset e configuração limpa
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Apenas o necessário
sudo ufw allow 2222/tcp comment "SSH"
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"
# Rate limit no SSH (anti-brute force no kernel)
sudo ufw limit 2222/tcp
# Ativar
sudo ufw --force enable
sudo ufw status verbose
iptables avançado — conntrack + rate limiting
# Limite de 6 conexões novas por minuto por IP no SSH
sudo iptables -A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW \
-m recent --set --name SSH
sudo iptables -A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 6 --name SSH -j DROP
# Drop de pacotes inválidos
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# Persistir regras
sudo iptables-save | sudo tee /etc/iptables/rules.v4
3. Fail2Ban — Banimento inteligente com integração UFW
sudo apt install fail2ban -y
cat << 'EOF' | sudo tee /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = ufw # Integra com UFW diretamente
ignoreip = 127.0.0.1/8
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400 # 24h no primeiro ban
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 120
bantime = 7200 # 2h
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 4
findtime = 60
bantime = 86400
EOF
sudo systemctl enable --now fail2ban
# Verificar
sudo fail2ban-client status sshd
# Output: Currently banned: 47, Total banned: 1,283 ← em 30 dias
4. Kernel Hardening — sysctl em produção
Cada parâmetro aqui tem um motivo. Não é copy-paste — é medida de defesa contra classes específicas de ataque:
cat << 'EOF' | sudo tee /etc/sysctl.d/99-hardening.conf
# === Anti-Spoofing ===
net.ipv4.conf.all.rp_filter = 1 # Reverse path filtering
net.ipv4.conf.default.rp_filter = 1
# === Source Routing (desabilitar OBRIGATÓRIO) ===
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# === ICMP Redirects (vetor de MitM) ===
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# === SYN Flood Protection ===
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
# === IPv6 (desabilitar se não usar) ===
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
# === Logging de pacotes impossíveis ===
net.ipv4.conf.all.log_martians = 1
# === ASLR máximo ===
kernel.randomize_va_space = 2
# === Restringir dmesg e kernel pointers ===
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
# === Desabilitar Magic SysRq ===
kernel.sysrq = 0
# === Hardlinks/Symlinks protection ===
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
# === Performance (bonus para servers web) ===
net.core.somaxconn = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
EOF
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
5. Systemd Service Hardening — Sandbox de verdade
Todo serviço que rodo em produção usa sandboxing via systemd. Não basta Restart=always — é preciso limitar o que o processo pode fazer:
# /etc/systemd/system/myapp.service
[Unit]
Description=Production API Service
After=network.target mongod.service redis-server.service
Requires=mongod.service redis-server.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
EnvironmentFile=/opt/myapp/.env
ExecStart=/opt/myapp/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
Restart=always
RestartSec=5
# === HARDENING (o que 90% das pessoas ignora) ===
NoNewPrivileges=true # Processo não pode escalar privilégios
ProtectSystem=strict # /usr, /boot, /etc são read-only
ProtectHome=true # /home inacessível
ReadWritePaths=/opt/myapp/data # Apenas o diretório de dados
PrivateTmp=true # /tmp isolado por processo
PrivateDevices=true # Sem acesso a /dev
ProtectKernelTunables=true # Sem alterar sysctl
ProtectControlGroups=true # Sem alterar cgroups
ProtectKernelModules=true # Sem carregar módulos
# === Limites ===
LimitNOFILE=65535
LimitNPROC=4096
# === Logging ===
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
Múltiplos serviços orquestrados
Em produção real, não é um serviço isolado — são vários interdependentes:
# API principal
sudo systemctl enable --now myapp-api.service
# Worker assíncrono (Celery)
sudo systemctl enable --now myapp-worker.service
# Scheduler (Celery Beat)
sudo systemctl enable --now myapp-beat.service
# Frontend (SSR)
sudo systemctl enable --now myapp-portal.service
# Verificar tudo de uma vez
for svc in myapp-api myapp-worker myapp-beat myapp-portal nginx mongod redis-server; do
STATUS=$(systemctl is-active $svc)
[ "$STATUS" = "active" ] && echo " ✓ $svc: ATIVO" || echo " ✗ $svc: $STATUS"
done
6. Redis Hardening — Bind localhost + LRU
Redis nunca deve ser exposto:
REDIS_CONF="/etc/redis/redis.conf"
# Bind somente localhost
sudo sed -i 's/^bind .*/bind 127.0.0.1 ::1/' "$REDIS_CONF"
# Daemonize
sudo sed -i 's/^daemonize no/daemonize yes/' "$REDIS_CONF"
# Eviction policy LRU (evita crash por memória)
echo "maxmemory 256mb" | sudo tee -a "$REDIS_CONF"
echo "maxmemory-policy allkeys-lru" | sudo tee -a "$REDIS_CONF"
# Desabilitar comandos perigosos
echo 'rename-command FLUSHDB ""' | sudo tee -a "$REDIS_CONF"
echo 'rename-command FLUSHALL ""' | sudo tee -a "$REDIS_CONF"
echo 'rename-command CONFIG ""' | sudo tee -a "$REDIS_CONF"
echo 'rename-command DEBUG ""' | sudo tee -a "$REDIS_CONF"
sudo systemctl restart redis-server
redis-cli ping # PONG
7. MongoDB Hardening — Auth + Bind + Network
# /etc/mongod.conf
cat << 'EOF' | sudo tee /etc/mongod.conf
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: 27017
bindIp: 127.0.0.1 # NUNCA 0.0.0.0
security:
authorization: enabled # Auth obrigatória
EOF
# Criar admin user
mongosh --eval '
use admin;
db.createUser({
user: "admin",
pwd: passwordPrompt(),
roles: ["root"]
});
'
sudo systemctl restart mongod
# Health check com retry loop
for i in $(seq 1 15); do
if mongosh --quiet --eval "db.runCommand({ping:1})" 2>/dev/null; then
echo "MongoDB healthy"
break
fi
echo "Tentativa $i/15..."
sleep 2
done
8. Auditoria com auditd — Quem fez o quê
sudo apt install auditd -y
cat << 'EOF' | sudo tee /etc/audit/rules.d/hardening.rules
# Identity changes
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
# SSH config changes
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Cron modifications
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Root commands
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
# Kernel modules
-w /sbin/insmod -p x -k kernel_modules
-w /sbin/modprobe -p x -k kernel_modules
# Network config changes
-w /etc/hosts -p wa -k network
-w /etc/nginx/ -p wa -k nginx_config
# Systemd service manipulation
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/systemctl -k systemd
EOF
sudo systemctl restart auditd
# Consultar: quem editou /etc/passwd nas últimas 24h?
sudo ausearch -k identity --start recent
9. Postfix Relay Seguro — SASL + TLS obrigatório
Para servidores que enviam email (alertas, notificações):
# /etc/postfix/main.cf (trecho relevante)
smtpd_banner = $myhostname ESMTP
biff = no
compatibility_level = 3.6
# TLS obrigatório para envio
smtp_tls_security_level = encrypt
smtp_tls_wrappermode = yes
smtp_tls_CApath = /etc/ssl/certs
# SASL auth para relay externo
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
# Relay via SMTP externo (porta 465/TLS)
relayhost = [smtp.provider.com]:465
# Restringir relay
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
# Configurar credenciais
echo "[smtp.provider.com]:465 user@domain.com:password" | sudo tee /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
sudo systemctl restart postfix
# Testar
echo "Test" | mail -s "Hardening Alert Test" admin@yourdomain.com
10. Automação de Setup — Script one-shot
Automatizo toda a instalação com um setup script que provê o servidor inteiro:
#!/bin/bash
set -euo pipefail
echo "=== Setup Production Server ==="
# 1. System dependencies
apt-get update && apt-get install -y \
curl wget git build-essential certbot python3-certbot-nginx \
logrotate fail2ban auditd ufw
# 2. Node.js 22 LTS
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
# 3. MongoDB 8.0
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg
echo "deb [signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg] \
https://repo.mongodb.org/apt/ubuntu $(lsb_release -cs)/mongodb-org/8.0 multiverse" \
| tee /etc/apt/sources.list.d/mongodb-org-8.0.list
apt-get update && apt-get install -y mongodb-org
# 4. Redis
apt-get install -y redis-server
# 5. Nginx
apt-get install -y nginx
# 6. Python venv + secrets gerados
python3 -m venv /opt/myapp/.venv
SECRET_KEY=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
cat > /opt/myapp/.env << ENV
SECRET_KEY=$SECRET_KEY
JWT_SECRET_KEY=$JWT_SECRET
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
MONGODB_URL=mongodb://localhost:27017
REDIS_URL=redis://localhost:6379/0
ENV
# 7. Deploy systemd services
# (instala cada um dos .service files)
systemctl daemon-reload
for svc in myapp-api myapp-worker myapp-beat; do
systemctl enable --now "$svc"
done
echo "=== Setup completo ==="
11. Backup automatizado com Google Drive
Backup diário às 3h com upload para Google Drive e retenção de 30 dias:
# Cron
echo "0 3 * * * /opt/myapp/backup.sh --quiet >> /var/log/backup.log 2>&1" \
| sudo crontab -
# O que o script faz:
# 1. mongodump → /tmp/backup/mongodb/
# 2. redis-cli BGSAVE → copia RDB
# 3. Copia configs: .env, nginx.conf, systemd units
# 4. rsync do source code (--exclude __pycache__, node_modules, .git)
# 5. Gera metadata JSON com versões e timestamps
# 6. tar.gz com timestamp
# 7. Upload para Google Drive via API
# 8. Remove backups locais > 30 dias
Restore com opções seletivas
# Restore completo
./restore.sh --from /backups/backup-2026-03-08.tar.gz
# Restore seletivo
./restore.sh --skip-code --skip-redis # só banco e configs
./restore.sh --skip-mongo --skip-config # só código
# Sempre faz backup do .env atual antes de sobrescrever
12. Logrotate — Manter disco limpo
cat << 'EOF' | sudo tee /etc/logrotate.d/myapp
/var/log/myapp*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 root root
postrotate
systemctl reload myapp-api 2>/dev/null || true
endscript
}
EOF
13. Monitoramento ativo
Verificação de saúde automatizada
#!/bin/bash
# /usr/local/bin/health_check.sh — roda via cron a cada 5min
FAILED=""
# Verificar serviços
for svc in nginx mongod redis-server myapp-api; do
if ! systemctl is-active --quiet "$svc"; then
FAILED+="$svc "
systemctl restart "$svc" # auto-restart
fi
done
# Verificar portas
for PORT in 80 443 8000 27017 6379; do
if ! ss -tlnp | grep -q ":$PORT "; then
FAILED+="port:$PORT "
fi
done
# HTTP smoke test
for ENDPOINT in "/api/health" "/"; do
CODE=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8000$ENDPOINT")
[ "$CODE" != "200" ] && FAILED+="http:$ENDPOINT($CODE) "
done
# Redis ping
redis-cli ping | grep -q PONG || FAILED+="redis-ping "
# Alertar se algo falhou
if [ -n "$FAILED" ]; then
echo "FALHA: $FAILED" | mail -s "ALERT: $(hostname)" admin@yourdomain.com
fi
Disk e memory watch
# Alerta se disco > 85%
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
[ "$DISK_USAGE" -gt 85 ] && echo "Disk at ${DISK_USAGE}%!" | mail -s "DISK ALERT" admin@yourdomain.com
# Alerta se RAM > 90%
MEM_USAGE=$(free | awk '/Mem/{printf("%.0f"), $3/$2*100}')
[ "$MEM_USAGE" -gt 90 ] && echo "RAM at ${MEM_USAGE}%!" | mail -s "MEM ALERT" admin@yourdomain.com
14. Cleanup seguro antes de reprovisionar
Quando preciso reprovisionar um servidor, faço cleanup completo:
#!/bin/bash
set -euo pipefail
# 1. Parar serviços
for svc in myapp-api myapp-worker myapp-beat mongod redis-server nginx; do
systemctl stop "$svc" 2>/dev/null || true
systemctl disable "$svc" 2>/dev/null || true
done
# 2. Matar processos órfãos
pkill -f 'uvicorn' || true
for PID in $(pgrep -x nginx); do kill -9 "$PID"; done
# 3. PRESERVAR certs Let's Encrypt (evitar rate limit)
mkdir -p /tmp/letsencrypt-saved
cp -a /etc/letsencrypt/live /tmp/letsencrypt-saved/ 2>/dev/null || true
cp -a /etc/letsencrypt/archive /tmp/letsencrypt-saved/ 2>/dev/null || true
# 4. Purge de pacotes
apt-get purge -y nginx* mongodb* redis* || true
apt-get autoremove -y
# 5. Remover dados
rm -rf /var/lib/mongodb /var/log/mongodb
rm -rf /var/lib/redis /var/log/redis
# 6. Limpar systemd
rm -f /etc/systemd/system/myapp-*.service
systemctl daemon-reload
echo "=== Cleanup completo — servidor pronto para reprovisionar ==="
Checklist final — 14 camadas
| # | Camada | Status |
|---|---|---|
| 1 | SSH: porta custom, root off, somente Ed25519, ciphers modernos | ☐ |
| 2 | UFW: deny all, allow explícito, limit SSH | ☐ |
| 3 | Fail2Ban: SSH 24h ban, nginx filter, UFW integration | ☐ |
| 4 | Kernel: sysctl anti-spoof, SYN flood, ASLR, kptr_restrict | ☐ |
| 5 | Systemd: sandbox com NoNewPrivileges, ProtectSystem, PrivateTmp | ☐ |
| 6 | Redis: bind 127.0.0.1, LRU, comandos perigosos desabilitados | ☐ |
| 7 | MongoDB: auth enabled, bind localhost | ☐ |
| 8 | Auditd: identity, SSH config, root commands, kernel modules | ☐ |
| 9 | Postfix: SASL + TLS obrigatório, relay restrito | ☐ |
| 10 | Setup automatizado: one-shot script, secrets gerados | ☐ |
| 11 | Backup: mongodump, Redis RDB, configs, Google Drive, retenção 30d | ☐ |
| 12 | Logrotate: rotação diária, compressão, 14 dias | ☐ |
| 13 | Monitoramento: health check, disk/mem alerts, auto-restart | ☐ |
| 14 | Cleanup: preserva certs, purge completo, reprovisioning | ☐ |
Conclusão
Segurança é infraestrutura como código. Cada camada aqui é um script que pode ser versionado, testado e replicado. O objetivo não é perfeição — é reduzir a superfície de ataque a um mínimo aceitável e ter visibilidade sobre o que acontece no seu servidor.
A diferença entre um servidor “configurado” e um servidor hardened está nos detalhes: systemd sandboxing, Redis com comandos renomeados, auditd monitorando cada execve como root, backups que de fato funcionam quando você precisa.
Este artigo documenta práticas de segurança defensiva aplicadas em infraestrutura real. Todas as configurações foram anonimizadas. Use estas técnicas apenas em servidores próprios ou sob autorização.
Rafael Cavalcanti da Silva — Fullstack Developer & Security Specialist rafaelroot.com
Rafael Cavalcanti da Silva — Fullstack Developer & Security Specialist rafaelroot.com