Uma torrente de riscos de luz esmeralda inundando um datacenter escuro, dividindo-se e curvando-se ao redor de um defletor luminoso a montante, com apenas algumas linhas limpas alcançando a lâmina de servidor preto-fosco atrás dele
Guia de rede

Manter um VPS no ar sob ataque DDoS

Três ataques bastante diferentes compartilham o nome DDoS, e quase todo conselho que você vai ler falha porque os trata como uma coisa só: responde a um uplink saturado com uma diretiva do nginx, ou a um flood de camada 7 engenhoso com um servidor maior. A pergunta útil nunca é “como eu bloqueio isso”, e sim “em qual camada isso é fisicamente detível, e quem controla essa camada”. Esta página responde isso para uma máquina Linux alugada — o que a rede acima de você absorve antes que você sequer veja, o que realmente ajuda dentro da máquina, e por que o movimento mais eficaz costuma ser simplesmente parar de ser endereçável.

A mitigação volumétrica nesta rede está sempre ativa e não há nada para você habilitar: floods abaixo de 10 Gbps são absorvidos silenciosamente, de 10 a 100 Gbps passam por scrubbing no provedor de trânsito, e qualquer coisa acima disso é anunciada em um caminho de scrubbing dedicado — os limiares estão escritos na documentação, não deixados como um adjetivo de marketing. Esse é o limite honesto do que um host consegue entregar. E é também, para o tráfego que de fato derruba servidores pequenos, a metade menos interessante do problema.

Porque os ataques que matam um VPS de cinco dólares de forma confiável não são os monstros de 340 Gbps que viram notícia. São 40.000 pacotes por segundo de SYNs pequenos que enchem uma tabela de estado que você nunca olhou, ou 300 requisições por segundo para a única URL do seu site que roda uma consulta ao banco de dados — tráfego que chega a uma interface perfeitamente saudável, por um link que está longe de estar cheio, e que nenhum scrubber upstream consegue distinguir dos seus usuários. Esses são seus para resolver, e são resolvidos com umas vinte linhas de configuração, desde que você primeiro descubra qual dos três está diante de você.

O que vem a seguir assume Debian 13 ou Ubuntu 24.04, nftables e nginx, e assume que você já fez o hardening da primeira hora — um servidor que ainda responde em portas que não serve não está pronto para ser defendido.

Três ataques, um só nome

“DDoS” descreve uma intenção, não um mecanismo, e os mecanismos não têm quase nada em comum entre si. Separá-los direito não é pedantismo: é o trabalho inteiro, porque cada um só é detível em exatamente uma camada e é invisível nas outras.

Floods volumétricos miram na sua banda. Amplificação UDP — DNS, NTP, memcached e, mais recentemente, qualquer coisa que responda a uma pergunta pequena com uma resposta grande — deixa um atacante transformar 1 Gbps da própria capacidade em 50 Gbps direcionados a você. O alvo é o link, não o servidor. Sua CPU vai ficar entediada o tempo todo.

Ataques de protocolo e de estado miram em uma tabela finita dentro do seu kernel. Um SYN flood tenta esgotar a fila de aceitação; um flood genérico de pacotes pequenos tenta esgotar o rastreamento de conexões. Os dois são medidos em pacotes por segundo, não em bits por segundo, e os dois conseguem matar uma máquina em um link 97% ocioso. Essa é a classe que derruba servidores pequenos, e a classe que a maioria dos guias pula.

Floods de camada de aplicação miram na sua CPU ou no seu banco de dados, usando requisições indistinguíveis de requisições reais porque elas são reais. Cem requisições por segundo para um endpoint de busca não é nada para uma rede e é fatal para uma aplicação PHP. Nenhum scrubber upstream consegue filtrar isso por você: de fora, parece exatamente um sucesso.

Existe uma quarta coisa que chega disfarçada das três e não é um ataque de jeito nenhum: um link que viralizou em algum lugar, um cliente seu se comportando mal, ou um crawler sem modos. Descartar essa hipótese primeiro não custa nada, e é constrangedor com que frequência ela é a resposta.

A parte que você não consegue resolver de dentro da máquina

Se 60 Gbps são direcionados ao seu endereço e sua porta é de 1 Gbps, a decisão sobre esses pacotes é tomada em um roteador upstream de você, vários saltos antes de qualquer coisa que você administra. Seu firewall nunca os vê. Não pode: eles foram descartados para proteger um link que não é seu. Esse é o fato estrutural mais importante sobre ataques volumétricos, e o motivo pelo qual “reforce seu firewall contra DDoS” é, na maior parte das vezes, bobagem.

Então as únicas perguntas que importam são o que o seu provedor faz automaticamente e onde ficam os limiares dele. Os nossos são publicados, não prometidos: abaixo de 10 Gbps é absorvido sem efeito visível, de 10 a 100 Gbps passa por scrubbing no provedor de trânsito e você pode notar um pico de latência, e acima de 100 Gbps o prefixo é anunciado em um caminho de scrubbing dedicado — a latência sobe de forma mais perceptível, e o serviço continua alcançável. Fazer null-route de um endereço só entra em jogo para ataques sustentados que ameaçam o PoP mais amplo, e avisamos você em minutos se isso acontecer. Não há nada para comprar nem para habilitar.

Vale a pena saber duas coisas sobre scrubbing que fornecedores raramente contam por conta própria. A primeira é que você é protegido pela vizinhança, não só pelas suas próprias defesas: um ataque a um cliente que compartilha o seu /20 upstream é absorvido antes de alcançar o prefixo de qualquer um — um flood de ~340 Gbps direcionado a uma faixa vizinha em Paris passou sem impacto mensurável na nossa, que é exatamente o desfecho que ninguém percebe. A segunda é que scrubbers são heurísticas, e heurísticas às vezes erram: na mesma rede, um scrubber já passou nove minutos emitindo resets de TCP contra conexões legítimas enquanto filtrava um ataque a um vizinho. Os dois incidentes estão no log público de incidentes. Se suas sessões morrerem de um jeito que parece um reset ativo em vez de um timeout durante o ataque de outra pessoa, isso é um modo de falha real e vale a pena reportar em vez de depurar localmente por uma hora.

Noventa segundos de medição, antes de mudar qualquer coisa

Toda resposta errada a um DDoS começa com uma mudança feita antes de alguém entender o que estava acontecendo. Pegue quatro números primeiro. Eles cabem em uma tela e nomeiam a camada para você.

# 1. bits vs packets  — which axis is saturated?
sar -n DEV 1 5            # or: ifstat -i eth0 1

# 2. socket states     — SYN-RECV piling up means a SYN flood
ss -s
ss -tan state syn-recv | wc -l

# 3. kernel drop counters (these are the ones that matter)
nstat -az | grep -E 'ListenDrops|ListenOverflows|SyncookiesSent|TCPReqQFullDrop'
dmesg -T | tail -20       # look for: nf_conntrack: table full, dropping packet

# 4. connection tracking headroom
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

Leia-os em conjunto. Muitos bits por segundo, poucos pacotes por segundo, CPU baixa é um ataque volumétrico, e seu trabalho é confirmar isso e parar de digitar. Muitos pacotes por segundo, poucos bits por segundo — um monte de pacotes minúsculos — é um ataque de estado; olhe imediatamente para SyncookiesSent e a contagem do conntrack. Tráfego modesto nos dois eixos com a CPU saturada é camada 7, e a resposta está no seu servidor web e no seu banco de dados, não no seu firewall.

Vale a pena internalizar uma leitura contraintuitiva: durante um flood volumétrico de verdade, sua interface pode parecer quase calma. Você está vendo os sobreviventes — o que passa por um link que já está cheio, ou o que o scrubber upstream deixou passar. Uma interface mostrando 900 Mbps em uma porta de 1 Gbps enquanto usuários relatam inacessibilidade total não é evidência contra um ataque. É a cara de um.

Depois, verifique se o tráfego simplesmente não é real. Rode tail -f no seu log de acesso por dez segundos: um caminho repetido por milhares de endereços distintos sem referrer é um ataque; uma variedade de caminhos normais vindos de navegadores normais é uma audiência, e aplicar rate limit nela vai fazer o trabalho do atacante por ele.

Encolha o alvo antes do ataque, não durante ele

Toda porta aberta é uma fila que um atacante pode encher, e todo pacote que seu guest precisa examinar custa CPU e uma entrada de conntrack mesmo quando você o descarta. O pacote mais barato é aquele que sua máquina nunca recebe, e é isso que faz filtrar acima do guest valer mais do que filtrar dentro dele.

Existem duas camadas aqui, e elas não são a mesma coisa. O filtro de borda é opcional, configurado por servidor, stateful e de bloqueio padrão, e roda no hypervisor — o tráfego que você rejeita ali nunca chega sequer à sua NIC virtual, então não custa CPU, memória nem entrada na tabela de estado. O firewall do guest — o seu ruleset de nftables — é inteiramente seu, e nós nunca tocamos nele. O padrão que sobrevive ao contato com um ataque é expressar a verdade grosseira e estável de camada 4 na borda (“esta máquina serve as portas 80, 443 e SSH a partir destes endereços, e mais nada existe”) e reservar o ruleset do guest para o trabalho fino que muda junto com a sua aplicação. Os dois estão descritos na documentação do firewall.

Depois, restrinja o que sobra. SSH restrito aos endereços a partir dos quais você de fato administra não é frescura de hardening aqui — isso remove uma classe inteira de ataque de exaustão de conexão da sua máquina. Um banco de dados vinculado a 127.0.0.1 ou a um endereço WireGuard não pode sofrer flood pela internet de jeito nenhum. E se você serve um plano de controle puramente interno, coloque-o atrás de uma interface WireGuard em vez de uma porta pública com senha.

SYN floods e a fila de aceitação

Um SYN flood explora o handshake: o atacante envia uma sequência de pedidos de conexão e nunca os completa, e cada um ocupa uma vaga na fila SYN do kernel até expirar. Encha a fila e handshakes legítimos são descartados — seu serviço está no ar, escutando, e inalcançável.

O Linux tem uma resposta limpa para isso desde os anos noventa, e ela vem ativada por padrão. SYN cookies permitem que o kernel pare de alocar memória para conexões semiabertas por completo: ele codifica o estado da conexão no número de sequência que devolve, e o reconstrói se o cliente completar o handshake. Confirme isso em vez de presumir, e dê às filas espaço suficiente para aguentar uma rajada:

# /etc/sysctl.d/99-flood.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_abort_on_overflow = 0

sysctl --system

tcp_synack_retries = 2 importa mais do que parece: o padrão de cinco faz um handshake forjado ocupar o kernel por cerca de três minutos, e dois derruba isso para cerca de sete segundos. Aumentar o somaxconn é só metade do trabalho — a profundidade da fila é o mínimo entre esse valor e o que a aplicação pediu, então o nginx precisa de listen 443 ssl backlog=8192; e de um reload antes que o ajuste do kernel signifique alguma coisa. Esse é o caso clássico de um sysctl que parece ter sido aplicado e não faz nada.

Se você quiser deter o flood antes mesmo que ele chegue à aplicação, o nftables consegue completar handshakes em nome do kernel e só repassar as conexões que se confirmarem reais:

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif lo accept
    tcp dport { 80, 443 } ct state new synproxy mss 1460 wscale 7 timestamp sack-perm
    tcp dport { 80, 443 } accept
    ip protocol icmp icmp type echo-request limit rate 5/second accept
  }
}

Teste o synproxy em uma máquina que você ainda consiga alcançar pelo console antes de confiar nele; malconfigurado, é uma excelente forma de se trancar para fora do próprio servidor com o firewall. Para a maioria das pessoas, o bloco de sysctl acima já basta, e a hierarquia honesta é: syncookies primeiro, backlog em segundo, synproxy só se você tiver medido que os dois primeiros não foram suficientes.

A tabela que ninguém olha até ela encher

Essa é a que pega gente experiente. Um firewall stateful precisa lembrar de todo fluxo que já viu, e essa memória é o nf_conntrack, uma tabela hash de tamanho fixo escolhido no boot. Quando ela enche, o kernel descarta conexões novas — todas elas, atacante e cliente igualmente — e registra uma única linha no dmesg que ninguém está observando:

nf_conntrack: table full, dropping packet

O motivo de ser tão eficaz é aritmética pura. Uma tabela padrão em um VPS pequeno guarda algo na casa das dezenas de milhares de entradas, e todo pacote de um endereço de origem novo cria uma — incluindo pacotes UDP, incluindo pacotes que você descarta, incluindo o próprio flood. Vinte mil pacotes por segundo vindos de fontes forjadas enchem a tabela em menos de dois segundos, em um link carregando poucos megabits. Seu gráfico de banda não vai mostrar absolutamente nada.

# /etc/sysctl.d/99-conntrack.conf
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_buckets = 65536
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20
net.netfilter.nf_conntrack_udp_timeout = 20

Os timeouts fazem mais bem do que o tamanho. O tempo de vida padrão de um fluxo TCP estabelecido é de cinco dias, o que significa que um servidor no ar há uma semana está guardando estado de conexões que terminaram na terça-feira; uma hora já é generoso para qualquer coisa que não seja uma sessão SSH ociosa, e net.ipv4.tcp_keepalive_time = 600 mantém essas vivas de propósito, em vez de por acidente. Ao dimensionar, calcule cerca de 300 bytes de memória do kernel por entrada: 262.144 entradas dão cerca de 80 MB, o que é tranquilo em uma máquina com 4 GB e nada tranquilo se você configurar dez milhões porque um fórum mandou.

Se você roda algo genuinamente stateless e de alto volume — um servidor DNS autoritativo, um servidor de jogo público, um relay Tor — a resposta melhor é parar de rastrear isso de vez. Rastreamento de conexão para um serviço que não tem conexões que façam sentido é custo puro:

table ip raw {
  chain prerouting {
    type filter hook prerouting priority raw; policy accept;
    udp dport 51820 notrack
    tcp dport 9001 notrack
  }
}

Lembre-se de que esse tráfego passa a ignorar suas regras de ct state established, então ele precisa de regras de aceitação explícitas na chain de filtro. Essa é a troca: você abre mão de statefulness para essa porta e ganha uma tabela que não pode ser enchida.

Camada 7: o flood que parece exatamente com seus clientes

O ataque mais eficiente contra um servidor pequeno não é um flood, no sentido estrito. São umas poucas centenas de requisições HTTP bem escolhidas por segundo, cada uma perfeitamente válida, cada uma acertando o único endpoint que roda uma consulta sem índice ou renderiza uma página sem cache. A economia é brutal: a requisição custa ao atacante algumas centenas de bytes e custa a você 200 milissegundos de CPU e uma conexão com o banco de dados. Você perde essa corrida em qualquer volume que consiga bancar servir.

O instinto é bloquear o atacante. Contra uma botnet espalhada por milhares de endereços residenciais, cada um enviando duas requisições por segundo, bloquear por IP é teatro — nenhum rate limit por endereço que permita seus usuários reais vai disparar, e você vai passar a queda adicionando regras enquanto o site continua fora do ar. A jogada vencedora é mudar o custo, não a contagem.

Faça cache primeiro, e faça cache do miss. Um cache de página inteira transforma uma requisição cara em uma leitura de memória, e a diretiva mais importante durante um ataque é a que impede mil misses simultâneos de virarem mil consultas simultâneas ao banco de dados:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app:64m max_size=2g inactive=60m;

server {
  location / {
    proxy_cache app;
    proxy_cache_valid 200 301 302 10m;
    proxy_cache_lock on;              # one origin request per key, not a thousand
    proxy_cache_lock_timeout 5s;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    proxy_cache_background_update on; # serve stale, refresh behind it
    add_header X-Cache $upstream_cache_status always;
    proxy_pass http://127.0.0.1:8080;
  }
}

proxy_cache_lock e use_stale são as duas linhas que decidem se um pico de tráfego é sobrevivível. Sem elas, no instante em que uma chave quente expira, toda requisição em andamento vira uma requisição à origem — a debandada que transforma um ataque administrável em uma queda. Com elas, seu backend atende uma requisição por chave a cada intervalo, e todo mundo mais recebe uma página um pouco desatualizada, o que é a troca certa em qualquer situação em que a alternativa é nenhuma página.

Depois, aplique rate limit especificamente nas rotas caras, com generosidade, e de um jeito que falhe visivelmente:

limit_req_zone $binary_remote_addr zone=general:16m rate=20r/s;
limit_req_zone $binary_remote_addr zone=costly:16m  rate=2r/s;
limit_conn_zone $binary_remote_addr zone=conn:16m;
limit_req_status 429;
limit_conn_status 429;

server {
  limit_req  zone=general burst=40 nodelay;
  limit_conn conn 24;

  location /search { limit_req zone=costly burst=5 nodelay; proxy_pass http://127.0.0.1:8080; }
  location /login  { limit_req zone=costly burst=3 nodelay; proxy_pass http://127.0.0.1:8080; }
}

burst sem nodelay enfileira o excesso de requisições em vez de rejeitá-las, o que sob ataque significa o nginx educadamente mantendo milhares de conexões abertas em seu nome — você converteu um flood de requisições em um flood de conexões. Use nodelay, devolva 429 imediatamente, e deixe o cliente lidar com isso. E atrás de qualquer proxy, lembre-se de que $binary_remote_addr é o proxy, a menos que você tenha configurado set_real_ip_from e real_ip_header corretamente; um rate limit baseado no endereço do seu próprio front-end vai bloquear todos os usuários de uma vez, na primeira vez que disparar.

Ataques estilo Slowloris — muitas conexões, cada uma pingando um byte de requisição por vez — mal incomodam o modelo de eventos do nginx, mas os timeouts merecem ser apertados mesmo assim: client_header_timeout 8s; client_body_timeout 8s; send_timeout 10s; reset_timedout_connection on;. Combinado com limit_conn, essa é a defesa inteira.

Por que o fail2ban não é uma ferramenta de DDoS

O fail2ban é genuinamente útil, e mira em um problema diferente. Ele lê arquivos de log, decide depois de N falhas em M minutos, e insere uma regra de firewall. Cada parte disso tem o formato errado para um flood.

É lento demais: uma rajada que dura quarenta segundos já acabou antes de a janela de banimento se fechar. Ele lê logs, então o ataque já custou a você o preço cheio de cada requisição — o parsing acontece depois do estrago. Ele bane endereços individuais, então contra dez mil fontes, ou não faz nada, ou insere dez mil regras lineares, ponto em que o próprio firewall vira o gargalo e você completou o ataque em nome do seu atacante. E ele depende dos seus logs, então um flood grande o bastante para encher um disco de linhas de log pode derrubar o servidor por um caminho que você nunca considerou.

Se você quer bloqueio dinâmico, faça isso no plano de dados, onde a busca é um hash e a expiração é automática. Um set dinâmico do nftables é O(1) independente do tamanho, e se esquece sozinho:

table inet filter {
  set flooders {
    type ipv4_addr
    flags dynamic, timeout
    timeout 10m
    size 65535
  }

  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif lo accept
    ip saddr @flooders drop
    tcp dport { 80, 443 } ct state new add @flooders { ip saddr limit rate over 50/second burst 100 packets } drop
    tcp dport { 80, 443 } accept
  }
}

Leia a regra com atenção, porque a negação engana as pessoas: o endereço é adicionado ao set e descartado só quando a taxa está acima do limite. Reserve o fail2ban para o que ele faz bem — adivinhação lenta de credenciais contra SSH e logins de aplicação, onde um banimento em escala humana de um endereço específico é exatamente certo.

Não seja uma arma: a amplificação que você está hospedando

Todo grande ataque volumétrico é alimentado por servidores cujos operadores não sabiam que estavam participando. O mecanismo é um serviço UDP que responde a um pedido pequeno e forjado com uma resposta grande: um resolver DNS aberto devolve 50x o que foi pedido, NTP e memcached malconfigurados são piores ainda, e qualquer protocolo que responda antes de conseguir verificar quem está perguntando é candidato.

As consequências caem sobre você antes de caírem sobre a vítima. Seu uplink enche com suas próprias respostas de saída, seu provedor vê tráfego abusivo sustentado saindo pela sua porta, e os fornecedores de trânsito que o recebem começam a fazer null-route das faixas de onde ele vem — por isso a política de uso aceitável traça uma linha dura em origem de ataque e amplificação, ao mesmo tempo em que permite essencialmente tudo o mais. Ser um refletor involuntário é a forma mais rápida de perder um endereço que não tem nada a ver com o que você pretendia rodar.

# bind a recursive resolver to localhost, never 0.0.0.0
# unbound: interface: 127.0.0.1  +  access-control: 0.0.0.0/0 refuse

# what is actually listening on a public address?
ss -ulpn
ss -tlpn

# from another machine, confirm you do not answer strangers
dig @your.server.ip example.com +short          # must time out
ntpq -c rv your.server.ip                        # must fail

Se você roda um serviço UDP público de propósito — um servidor de jogo, um nameserver autoritativo, um endpoint WireGuard — a regra é a mesma de sempre: limite a taxa da resposta, nunca do pedido. O bind e o knot implementam limitação de taxa de resposta; use isso, porque é a diferença entre servir seus usuários e despachar o ataque de outra pessoa.

A defesa mais eficaz é não ser encontrável

Tudo o que vimos até aqui é controle de danos para um ataque que já encontrou o seu endereço. Vale a pena fazer, e é a segunda melhor opção. Uma origem que ninguém consegue nomear simplesmente não é atacável em camada 3 ou 4, e separar o endereço que serve tráfego do endereço que o mundo conhece é a coisa de maior alavancagem nesta página.

Endereços de origem quase nunca vazam por ataques engenhosos. Eles vazam por história e por descuido, em uma lista curta e bem conhecida. Registros DNS antigos são o culpado de sempre: arquivos de passive DNS lembram para sempre do registro A que você tinha antes de colocar uma fachada no lugar. Logs de certificate transparency são públicos e permanentes, então um certificado emitido para um hostname que apontava direto para a origem é um registro assinado e datado de onde você morava. E-mails enviados carimbam o endereço do servidor remetente no cabeçalho de cada mensagem. E a discreta: seu servidor web respondendo no IP puro, o que deixa qualquer um que varra o espaço de endereços atrás do HTML do seu site te encontrar pelo conteúdo em uma tarde.

Essa última é uma correção de duas linhas e quase ninguém faz. Faça o servidor padrão recusar tudo que não chegou com um hostname que você serve:

server {
  listen 80 default_server;
  listen 443 ssl default_server;
  ssl_reject_handshake on;      # nginx 1.19.4+: no certificate, no fingerprint
  return 444;                   # close without a response
}

Depois, escolha uma fachada. Um CDN comercial é a resposta óbvia e vem com um custo real que importa aqui: você está adicionando uma empresa que termina seu TLS, vê seu tráfego em texto plano, conhece sua origem, e pode receber um processo legal em uma jurisdição que você não escolheu — o que desfaz boa parte do motivo de o servidor ter sido pago em cripto sem nome atrelado. Se mesmo assim você fizer isso, entenda que a origem nunca pode ter sido pública, e que o arranjo inteiro falha no instante em que alguém encontra um registro antigo.

A alternativa que preserva a propriedade pela qual você pagou é ser sua própria fachada. Uma instância de cinco dólares em uma segunda região rodando nginx como proxy reverso, com o filtro de borda da origem aceitando tráfego só daquele único endereço, te dá um endereço sacrificável que você pode renumerar em cinco minutos e um plano de controle que responde só a você. É a mesma arquitetura, menos o terceiro. E onde a audiência consegue usar, um serviço onion do Tor remove o IP da equação por completo — não existe endereço para sofrer flood, embora serviços onion tenham sua própria superfície de ataque nos pontos de introdução, e é por isso que o Tor moderno vem com uma defesa de prova de trabalho para exatamente isso.

Bloquear países, ASNs e o formato do tráfego

Mais cedo ou mais tarde alguém sugere bloquear um país. É bruto, ocasionalmente correto, e principalmente uma forma de parecer ocupado.

É defensável quando seu serviço tem uma audiência genuinamente limitada — um servidor de jogo regional, uma ferramenta interna, um painel de controle — e você está disposto a assumir os falsos positivos, que incluem seus próprios usuários viajando e qualquer um roteado de forma estranha. É quase inútil contra uma botnet moderna, que está distribuída por conexões residenciais em todo país, incluindo o seu, e é ativamente prejudicial para qualquer coisa pública: você vai perder gente de verdade silenciosamente e nunca vai ver quem foi embora.

Bloquear por ASN é mais afiado. Tráfego de ataque originado de um punhado de provedores de hospedagem — faixas de VPS baratos alugados por hora — pode ser descartado por prefixo com muito menos baixas do que um bloqueio de país, porque usuários comuns não navegam a partir de um datacenter. Faixas de datacenter também são, convenientemente, de onde vêm scraping e credential stuffing.

Mas a versão durável dessa ideia não é geografia de jeito nenhum: é filtrar pelo formato do tráfego, não pela origem. Um ataque geralmente compartilha algo estrutural — o mesmo user agent, o mesmo cabeçalho faltando, a mesma URL com o mesmo parâmetro de query, o mesmo fingerprint de TLS, uma ausência completa da segunda requisição que um navegador de verdade sempre faz. Encontre a propriedade compartilhada e você escreve uma regra que não custa nada e sobrevive ao atacante trocando de endereço, o que ele vai fazer mais rápido do que você consegue listá-los.

# in an attack: what do these requests have in common?
awk '{print $1}'  /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20   # addresses
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20   # user agents
awk '{print $7}'  /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20   # paths

Se um único user agent responde por 90% das requisições, você está a noventa segundos de uma correção. Se os vinte endereços do topo respondem por 2% do tráfego cada um, pare de procurar endereços — isso é um ataque distribuído, e a resposta é cache e rate limit.

Quando o endereço está queimado

Às vezes o ataque é direcionado, persistente e mira em você pessoalmente em vez de em um endereço aleatório, e a resposta correta é parar de defender o endereço e abandoná-lo. Isso não é derrota; para um serviço pequeno, costuma ser o desfecho mais barato possível, e só é doloroso se você não tiver ensaiado.

O que torna isso rápido é decidido com antecedência. Mantenha os TTLs de DNS em 300 segundos como postura permanente — o custo é insignificante e o benefício é que você consegue se mover em cinco minutos em vez de um dia. Mantenha seu deploy reprodutível, porque um servidor que você consegue reconstruir é um servidor que você consegue mover; se reconstruir significa lembrar o que você fez em março, você não tem um plano, tem um refém. Mantenha um backup restaurável em outra região, e saiba, a partir de um ensaio de verdade, quanto tempo uma restauração leva.

Depois, a movimentação em si é curta: faça o deploy em outra região, restaure, verifique no novo endereço por override de hostname antes de tocar no DNS, troque o registro, e mantenha o servidor antigo rodando tempo suficiente para os últimos resolvers se atualizarem. O guia de migração cobre o sequenciamento direito, e é a mesma sequência tanto para quem está se mudando por performance quanto para quem está se mudando porque alguém está bravo.

O modelo de cobrança torna o ensaio essencialmente gratuito: as cobranças são proporcionais ao dia e descontadas do seu saldo, então uma segunda máquina que você sobe, testa e destrói em uma tarde custa centavos, e não há contrato, cartão nem renovação para cancelar. Não há motivo para sua primeira renumeração acontecer durante um ataque.

O que nada disso te garante

Duas notas honestas para fechar, porque o resto desta página é otimista por construção.

A primeira é que nenhum host absorve tudo, e qualquer provedor que afirme o contrário está vendendo algo. Capacidade é finita, capacidade de scrubbing é ainda mais finita, e em escala suficientemente grande, o ato economicamente racional para uma rede que protege milhares de clientes é parar de anunciar um endereço. Nós tratamos isso como último recurso contra ataques sustentados que ameaçam o PoP mais amplo, e avisamos você em vez de deixar você descobrir sozinho depurando — mas uma promessa de que isso nunca vai acontecer seria mentira, e você deveria desconfiar de qualquer host que fizer essa promessa.

A segunda é que retaliação não está no cardápio. Serviços de booter e stresser são ataques por encomenda disfarçados de ferramentas de teste, são ilegais na maioria das jurisdições que importam, são extensamente monitorados, e vão fazer seu servidor ser encerrado aqui, sob uma política que permite quase tudo o mais. A assimetria que torna o DDoS atraente para um atacante o torna inútil para um defensor: você não consegue vencer no flood alguém que não tem nada a perder. Absorva, faça cache, mude de endereço, e deixe isso ficar chato — o que, para quem paga pelo tráfego que envia, eventualmente acontece.

  1. Nomeie a camada em noventa segundos

    Antes de qualquer mudança de configuração, pegue os dois eixos e os dois contadores. Bits alto e pacotes baixo é volumétrico; pacotes alto e bits baixo é um ataque de estado; os dois modestos com a CPU saturada é camada 7.

    sar -n DEV 1 5                       # bits/s and packets/s per interface
    ss -s                                # socket summary; watch synrecv
    nstat -az | grep -E 'ListenDrops|ListenOverflows|SyncookiesSent'
    cat /proc/sys/net/netfilter/nf_conntrack_count
    dmesg -T | grep -i conntrack | tail

    Depois, descarte sucesso e autolesão: passe o olho no log de acesso por dez segundos. Se os caminhos parecem um site sendo usado, você tem uma audiência ou um bug, não um atacante.

  2. Faça o kernel absorver floods a baixo custo

    Dois arquivos, um reload. Isso é seguro em qualquer servidor de propósito geral e remove as duas formas mais comuns de uma máquina pequena cair.

    cat > /etc/sysctl.d/99-flood.conf <<'EOF'
    net.ipv4.tcp_syncookies = 1
    net.ipv4.tcp_max_syn_backlog = 8192
    net.core.somaxconn = 8192
    net.ipv4.tcp_synack_retries = 2
    net.ipv4.tcp_keepalive_time = 600
    net.netfilter.nf_conntrack_max = 262144
    net.netfilter.nf_conntrack_buckets = 65536
    net.netfilter.nf_conntrack_tcp_timeout_established = 3600
    net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 20
    EOF
    
    sysctl --system
    sysctl net.ipv4.tcp_syncookies net.netfilter.nf_conntrack_max   # verify, do not assume

    Depois, faça a aplicação acompanhar: o nginx precisa de listen 443 ssl backlog=8192;, ou a fila do kernel que você acabou de aumentar fica limitada pelo número menor que o processo pediu.

  3. Feche tudo que você não serve, acima do guest

    Enumere o que realmente está escutando, decida o que pertence a um endereço público, e empurre as regras grosseiras para cima, até o filtro de borda do hypervisor, para que o tráfego nunca alcance sua NIC virtual.

    ss -tlpn; ss -ulpn        # every public listener, with the process that owns it

    Em Server → Network → Firewall, configure o filtro de borda para bloquear por padrão e permitir só as portas que você serve, com o SSH restrito aos endereços a partir dos quais você administra. Mantenha o ruleset de nftables do guest como sua segunda camada — reforço, não substituição — e vincule bancos de dados e interfaces de administração a 127.0.0.1 ou a um endereço WireGuard, em vez de colocar um firewall em uma porta pública.

  4. Coloque um cache na frente da rota cara

    Essa é a mudança de maior valor isolado para qualquer coisa que renderiza páginas. Adicione proxy_cache com proxy_cache_lock on e proxy_cache_use_stale, para que mil misses simultâneos virem uma única requisição à origem e todo mundo mais receba uma página um pouco mais antiga.

    nginx -t && systemctl reload nginx
    curl -sI https://example.com/ | grep -i x-cache     # MISS, then HIT

    Verifique a taxa de acerto antes de acreditar nela. Um cache que nunca acerta por causa de um Set-Cookie em toda resposta é a falsa sensação de segurança mais comum de todo esse exercício — confira com curl -sI duas vezes e leia o X-Cache.

  5. Aplique rate limit nas rotas que custam a você, não no visitante

    Aplique um limite global generoso e um limite apertado nos endpoints que tocam um banco de dados. Sempre nodelay, sempre com um status code, nunca uma fila silenciosa.

    limit_req_zone $binary_remote_addr zone=general:16m rate=20r/s;
    limit_req_zone $binary_remote_addr zone=costly:16m  rate=2r/s;
    limit_req_status 429;

    Se o nginx está atrás de algum proxy, configure primeiro set_real_ip_from e real_ip_header. Um limite baseado no endereço do seu front-end não limita a taxa de um atacante — limita a taxa de todo mundo ao mesmo tempo, na primeira vez que disparar.

  6. Tire o endereço de origem da vista pública

    Torne o IP puro inútil, depois decida o que vai servir de fachada para ele. A recusa tem duas linhas e fecha de vez o caminho de varrer a internet atrás do HTML do seu site.

    server {
      listen 80 default_server;
      listen 443 ssl default_server;
      ssl_reject_handshake on;
      return 444;
    }

    Depois, audite os vazamentos na ordem em que costumam acontecer: registros A históricos em arquivos de passive DNS, hostnames em logs de certificate transparency que já apontaram para a origem, e cabeçalhos de e-mails enviados. Se algum deles ainda nomeia o endereço, uma fachada não vai te salvar — renumere primeiro, fachada depois.

  7. Avise o operador de rede, com números

    A mitigação volumétrica é automática e não precisa de chamado, mas um relatório com evidências permite que um humano confirme o que a automação fez e capture os modos de falha que ela não consegue ver — um scrubber resetando sessões legítimas, por exemplo.

    Envie o endereço de destino, o horário de início em UTC, o protocolo e as portas de destino, a taxa nos dois eixos conforme você mediu, e se os seus próprios contadores mostram pacotes chegando ou desaparecendo upstream. Cinquenta linhas de tcpdump -ni eth0 -c 200 valem mais do que um parágrafo de descrição. “O site está lento” não é acionável; “185.x.x.x, 14:02 UTC, UDP para a 443, ~1,2 Mpps, interface mostra 40 Kpps chegando” é.

  8. Ensaie a renumeração enquanto nada está errado

    Configure os TTLs de DNS para 300 segundos hoje e deixe assim. Depois, faça o ensaio uma vez, do início ao fim, em uma máquina que você vai destruir uma hora depois.

    # deploy a second instance in another region, restore, verify by hostname override
    curl --resolve example.com:443:<new-ip> https://example.com/ -sI

    Anote quanto isso custou a você, em minutos. Esse número é o seu plano de DDoS de verdade — mais do que qualquer regra deste guia — porque é ele que diz por quanto tempo um ataque direcionado consegue te deixar fora do ar. As cobranças são proporcionais ao dia e descontadas do seu saldo, então o ensaio inteiro custa centavos.

Comparação

Onde cada ataque pode de fato ser detido

Toda camada detém alguma coisa e é cega para o resto. O erro que custa o fim de semana das pessoas é defender em uma camada onde o ataque nunca foi visível, para começar.
CamadaDetémNão detémO que custa a vocêQuem controla
Scrubbing upstreamFloods volumétricos — absorvidos abaixo de 10 Gbps, passam por scrubbing até 100, desviados via BGP acima dissoQualquer coisa que caiba dentro dos volumes normais de tráfego: ataques de estado, camada 7Nada. Sempre ativo, sem chamado, latência ocasional durante um desvioNós, automaticamente
Filtro de borda do hypervisorTudo que vai para uma porta fechada, antes de alcançar sua NIC virtual — sem CPU, sem entrada de estadoAtaques às portas que você precisa manter abertasNada além das regras que você escreve; stateful, de bloqueio padrãoVocê, por servidor
Firewall do guest (nftables)SYN floods com synproxy, taxas de pacotes por origem, protocolos indesejadosTráfego que já encheu o link acima de você — ele nunca chegaCPU e uma entrada de conntrack por pacote, incluindo os descartadosVocê, inteiramente
Ajuste de kernel (sysctl)Exaustão da fila de aceitação e do conntrack — o clássico matador de servidor pequenoQualquer coisa que seja uma conexão válida e completaCerca de 80 MB de RAM em um tamanho sensato de conntrack. Dois arquivosVocê, inteiramente
Aplicação (cache + rate limit)Floods de camada 7, debandadas de requisições, endpoints carosAtaques em nível de pacote — eles nunca chegam ao servidor webPáginas um pouco desatualizadas, e 429s para os usuários que você julgou erradoVocê, inteiramente
Fachada de proxy reversoAtaques diretos à origem — não existe endereço público para mirarAtaques à própria fachada, e qualquer coisa depois que o seu IP de origem vazar$5/mês e mais uma máquina para manter atualizadaVocê, se você mesmo administrar
CDN comercialGrandes ataques de camada 3/4 e camada 7, em escala, com uma fila de suporteUma origem que já foi pública em algum momento, ou que responde no IP puroTerminação de TLS por um terceiro que sabe quem você é e pode receber um processo legalEles
Serviço onion do TorTodo ataque baseado em IP — não existe endereço no protocoloFlood nos pontos de introdução, motivo pelo qual o Tor moderno vem com uma defesa de prova de trabalhoLatência, e uma audiência disposta a usar TorVocê e a rede
FAQ

Perguntas que merecem resposta

Eu preciso habilitar a proteção contra DDoS no meu servidor?

Não. A mitigação volumétrica fica upstream dos PoPs e está sempre ativa — não existe produto para comprar nem interruptor para acionar. Abaixo de 10 Gbps é absorvido sem efeito visível; de 10 a 100 Gbps passa por scrubbing no provedor de trânsito, onde você pode ver um breve aumento de latência; acima de 100 Gbps o prefixo é anunciado em um caminho de scrubbing dedicado, a latência sobe de forma mais perceptível e o serviço continua alcançável. Os limiares estão publicados na documentação. O que não é automático é tudo acima da camada 4: exaustão de tabela de estado e floods de camada de aplicação parecem tráfego comum vistos de upstream e precisam ser tratados na sua máquina.

Vocês vão fazer null-route do meu IP se eu for atacado?

Só como último recurso, e só para ataques sustentados que ameaçam o PoP mais amplo, não apenas o seu servidor — e avisamos você em minutos se isso acontecer, em vez de te deixar descobrir sozinho. Qualquer host que promete que isso nunca vai acontecer está descrevendo marketing, não uma rede: em escala suficiente, proteger milhares de clientes significa eventualmente retirar um endereço. A proteção realista é se tornar um alvo pouco atraente — mantenha o endereço de origem não publicado, mantenha um plano de renumeração ensaiado, e saiba o seu tempo de restauração.

Meu site está fora do ar mas o gráfico de banda parece normal. O que está acontecendo?

Quase certamente exaustão de estado — o caso clássico sendo uma tabela nf_conntrack cheia. Vinte mil pacotes pequenos por segundo vindos de fontes forjadas enchem uma tabela padrão em segundos usando poucos megabits, então o gráfico não mostra nada e toda conexão nova é descartada. Verifique dmesg -T | grep conntrack em busca de “table full, dropping packet” e compare nf_conntrack_count com nf_conntrack_max. A outra possibilidade é a leitura oposta: o link acima de você já está cheio, então sua interface está te mostrando os sobreviventes, não o ataque.

Um plano maior sobrevive a um ataque que um pequeno não sobrevive?

Às vezes, e não pelo motivo que as pessoas esperam. Mais vCPU e RAM ajudam genuinamente contra floods de camada 7 e exaustão de estado, porque esses ataques esgotam CPU, memória e espaço de tabela. Contra um flood volumétrico, o plano é quase irrelevante — os pacotes são descartados upstream da sua porta, seja lá o que estiver atrás dela, e uma porta de 2,5 Gbps não te salva de 40 Gbps. Resolva o cache e o dimensionamento do conntrack antes de fazer upgrade; é mais barato e geralmente se revela ser o problema de verdade.

Posso colocar o Cloudflare ou outro CDN na frente de um VPS sem KYC?

Tecnicamente sim, e funciona bem. Entenda a troca antes de fazê-la: o CDN termina seu TLS e vê seu tráfego em texto plano, conhece seu endereço de origem, mantém uma conta que te identifica por e-mail e muitas vezes por forma de pagamento, e pode receber um processo legal em uma jurisdição que você não escolheu. Para um servidor comprado sem nome atrelado, isso reintroduz exatamente a parte que o arranjo pretendia evitar. Se o seu modelo de ameaça é downtime, e não exposição, é uma escolha razoável — mas a origem nunca pode ter sido pública, ou um registro DNS antigo derruba tudo. Se o seu modelo de ameaça inclui quem sabe onde você está, rode seu próprio proxy reverso em uma segunda instância, em vez disso, e deixe a origem aceitar tráfego só dele.

O fail2ban é suficiente para deter um DDoS?

Não, e é a ferramenta errada, não uma ferramenta fraca. Ele reage em escala humana depois de fazer parsing dos logs, então as requisições já custaram a você tudo o que iam custar; ele bane um endereço de cada vez, o que não faz nada contra dez mil fontes e transforma seu firewall em uma varredura linear se você deixar ele tentar; e ele depende de logs que um atacante consegue inundar. Use-o para o que ele faz excepcionalmente bem — adivinhação lenta de credenciais contra SSH e formulários de login — e use um set dinâmico do nftables com timeout para qualquer coisa que aconteça na velocidade de um flood.

Eu deveria bloquear países ou ASNs inteiros?

Bloqueio por país só é defensável quando sua audiência é genuinamente limitada e você aceita perder usuários viajando e outros roteados de forma estranha. É quase inútil contra uma botnet moderna, que está espalhada por conexões residenciais em todo lugar, incluindo o seu próprio país. Bloquear ASNs de datacenter é mais afiado — pessoas comuns não navegam a partir de uma faixa de hospedagem — e pega scraping e credential stuffing de bônus. O instinto melhor é filtrar pelo formato do tráfego: um user agent compartilhado, um cabeçalho faltando, um caminho repetido. Essa regra continua funcionando depois que o atacante troca de endereço, o que ele vai fazer em minutos.

Rodar um serviço onion do Tor me deixa imune?

Imune a ataques baseados em IP, sim — não existe endereço no protocolo para mirar um flood, o que é uma postura de segurança genuinamente diferente, não uma melhoria incremental. Não é imune de forma geral: serviços onion podem ser atacados nos seus pontos de introdução, motivo pelo qual o Tor moderno vem com uma defesa de prova de trabalho que torna o flood caro para o cliente. Também custa latência e te limita a uma audiência disposta a usar Tor. Muita gente roda os dois — uma fachada clearnet para alcance, um endereço onion que continua funcionando quando o clearnet está sob ataque.

Deploy your offshore server.

Escolha uma região. Escolha um plano. Cole uma chave. Pague. Os próximos 47 segundos são por nossa conta.