ModSecurity bloqueou o próprio CDN: quando o WAF não sabe que está atrás da Akamai e bane os edge nodes
Publicado em 2 de maio de 2026
O problema: ERR_READ_ERROR no CDN com WAF saudável
A Akamai reportava ERR_READ_ERROR|no_resp_hdrs — conexão TCP quebrada antes de receber os headers da origin. Do ponto de vista do usuário final, o site carregava intermitentemente: às vezes 200, às vezes timeout sem resposta. O WAF estava funcionando, o nginx backend estava respondendo para outros domínios. A falha era específica para este domínio.
A arquitetura do servidor concentra múltiplas camadas em um único host OCI:
# Topologia de rede
Internet → Akamai (CDN puro, sem WAF/segurança)
→ WAF container (network_mode: host, porta 443)
ModSecurity 3.0.14 + OWASP CRS 4.14.0-dev + CrowdSec bouncer
→ proxy_pass https://172.28.5.61
→ nginx backend (bridge cdn-bridge0, IP 172.28.5.61)
→ fastcgi → PHP-FPM (network_mode: host)O detalhe crítico: o WAF está em network_mode host. Isso significa que ele enxerga diretamente os IPs dos edge nodes da Akamai — sem NAT, sem camada intermediária. O $remote_addr para todas as requisições do site é um IP da Akamai, não o IP do visitante real.
A evidência: 928.219 bloqueios em um único log
O primeiro sinal concreto foi o tamanho do error.log: 839 MB. Sem rotação desde 9 de fevereiro — mais de cinco semanas acumulando. O modsec_audit.log estava em 6,9 GB. O access.log em 1,3 GB.
Ao filtrar os bloqueios no error.log, o padrão ficou imediato:
# Contar bloqueios ModSecurity no error.log
grep -c 'ModSecurity' /var/log/nginx/error.log
# 928219
# Identificar os IPs mais bloqueados
grep 'ModSecurity.*blocked' /var/log/nginx/error.log | grep -oP 'client: K[0-9.]+' | sort | uniq -c | sort -rn | head -10Os IPs bloqueados apareciam sempre os mesmos: 192.0.2.12, 192.0.2.13, 198.51.100.14, 198.51.100.15. Uma verificação rápida contra os ranges publicados pela Akamai confirmou a origem:
# Verificar se IPs pertencem aos ranges Akamai (publicados em https://techdocs.akamai.com/...)
# Range 192.0.2.0/24 cobre 192.0.2.0 até 192.0.2.255
# Range 198.51.100.0/24 cobre 198.51.100.0 até 198.51.100.255
python3 -c "
import ipaddress
akamai_ranges = ['192.0.2.0/24', '198.51.100.0/24']
test_ips = ['192.0.2.12', '192.0.2.13', '198.51.100.14', '198.51.100.15']
for ip in test_ips:
for cidr in akamai_ranges:
if ipaddress.ip_address(ip) in ipaddress.ip_network(cidr):
print(f'{ip} pertence a {cidr}')
"
# 192.0.2.12 pertence a 198.51.100.0/24
# 192.0.2.13 pertence a 198.51.100.0/24
# 198.51.100.14 pertence a 192.0.2.0/24
# 198.51.100.15 pertence a 192.0.2.0/24O WAF estava bloqueando os edge nodes da própria Akamai. Toda requisição que passava pela CDN tinha anomaly score calculado contra o IP do edge node, não contra o IP do visitante real.
Por que o OWASP CRS bloqueava a Akamai
O OWASP CRS calcula um anomaly score acumulado por requisição. Quando o score passa de um threshold (padrão: 5 para bloquear), a requisição é negada. Nas requisições vindas da Akamai, o score estava chegando a 40-60.
O caso mais revelador foi o bloqueio do wp-cron.php. A Akamai envia probes periódicas para validar que a origin está respondendo. Essas probes incluem um referrer com um pattern XSS que é parte do comportamento padrão do edge node:
# Trecho do modsec_audit.log — probe da Akamai ao wp-cron.php
--abc123--A--
[13/Mar/2026:14:23:01 +0000] "GET /wp-cron.php?doing_wp_cron HTTP/1.1"
--abc123--B--
Referer: https://blog.cliente-exemplo.com.br/<script>alert(81)</script>
User-Agent: Mozilla/5.0 (compatible; AkamaiGHost/...)
--abc123--H--
ModSecurity: Warning. XSS Attack Detected via libinjection.
[file "REQUEST-941-APPLICATION-ATTACK-XSS.conf"]
[id "941100"] [rev ""] [msg "XSS Attack Detected via libinjection"]
[data "Matched Data: <script> found within ARGS:doing_wp_cron"] ...
[severity "CRITICAL"] [ver "OWASP_CRS/4.14.0-dev"]
[tag "application-multi"]
[anomaly-score 15]
ModSecurity: Access denied with code 403 (phase 2).
[score: 40] [threshold: 5]O referrer com pattern XSS disparava a regra 941100 (XSS Detection via libinjection) com score 15. Somado a outras regras que pontuavam pelo User-Agent incomum e pelo header pattern, o score total chegava a 40. Com threshold de 5, o bloqueio era inevitável.
POSTs legítimos de usuários também eram afetados — qualquer POST que incluísse campos com caracteres especiais acumulava score suficiente para bloquear, porque o $remote_addr era da Akamai e a reputação do IP não ajudava. A ausência de real_ip correto também afetava as regras de rate limiting do WAF, que agrupavam todo o tráfego de um edge node em um único bucket.
O arquivo common/akamai.conf já existia — só não estava incluído
Ao inspecionar a estrutura de configuração do servidor, o arquivo de real_ip para a Akamai estava presente:
# Arquivo /home/developer/webserver/common/akamai.conf
set_real_ip_from 192.0.2.0/24;
set_real_ip_from 198.51.100.0/24;
# ... outros ranges Akamai
real_ip_header X-Forwarded-For;
real_ip_recursive on;O problema era que o vhost do WAF (waf-enabled/blog.cliente-exemplo.com.br) incluía o arquivo errado: common/gocache.conf em vez de common/akamai.conf. O servidor havia sido configurado originalmente com GoCache como CDN, e quando a Akamai substituiu o GoCache, apenas o DNS foi alterado — os vhosts ficaram com os includes antigos.
# O que estava no vhost WAF (incorreto)
server {
listen 443 ssl;
server_name blog.cliente-exemplo.com.br;
include common/gocache.conf; # <-- ERRADO: ranges GoCache, não Akamai
...
}
# O que o vhost nginx backend também tinha (incorreto)
server {
listen 443;
server_name blog.cliente-exemplo.com.br;
include common/gocache.conf; # <-- ERRADO: mesma linha no backend
...
}A correção: dois vhosts, dois reloads
A correção precisou ser aplicada em ambas as camadas — WAF e nginx backend — porque cada uma tem sua própria configuração de real_ip:
# Backup dos vhosts antes de alterar
cp /home/developer/webserver/waf-enabled/blog.cliente-exemplo.com.br /home/developer/webserver/blog.cliente-exemplo.com.br.waf-bak.20260319
cp /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br /home/developer/webserver/blog.cliente-exemplo.com.br.bak.20260319
# Corrigir vhost do WAF: trocar gocache.conf por akamai.conf
sed 's|include common/gocache.conf;|include common/akamai.conf;|g' /home/developer/webserver/waf-enabled/blog.cliente-exemplo.com.br > /tmp/waf-new
cp /tmp/waf-new /home/developer/webserver/waf-enabled/blog.cliente-exemplo.com.br
# Corrigir vhost do nginx backend: mesmo ajuste
sed 's|include common/gocache.conf;|include common/akamai.conf;|g' /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br > /tmp/nginx-new
cp /tmp/nginx-new /home/developer/webserver/sites-enabled/blog.cliente-exemplo.com.br
# Testar e recarregar o WAF primeiro
docker exec waf nginx -t && docker exec waf nginx -s reload
# Testar e recarregar o nginx backend
docker exec nginx nginx -t && docker exec nginx nginx -s reloadOs resultados
Após o reload do WAF, os bloqueios zeraram imediatamente. Os edge nodes da Akamai passaram a ser reconhecidos como proxies confiáveis — o $remote_addr do ModSecurity passou a ser o IP real do visitante, calculado a partir do X-Forwarded-For.
O modsec_audit.log parou de crescer. Para os 10,5 GB de logs acumulados, foi feita rotação e compressão manual:
# Rotação manual dos logs acumulados
cd /var/log/nginx
mv error.log error.log.20260319 # 839 MB
mv modsec_audit.log modsec_audit.log.20260319 # 6.9 GB
mv access.log access.log.20260319 # 1.3 GB
# Recarregar para criar novos arquivos de log
docker exec nginx nginx -s reload
# Comprimir os logs antigos (~10.5 GB → ~296 MB)
gzip error.log.20260319
gzip modsec_audit.log.20260319
gzip access.log.20260319A lição: WAF atrás de CDN exige real_ip antes de qualquer regra
Esse é um erro de configuração clássico em arquiteturas WAF + CDN. O ModSecurity — como qualquer sistema de detecção baseado em IP — precisa saber quais endereços são proxies confiáveis. Sem isso, qualquer requisição que passe pelo CDN tem o IP do edge node como endereço de origem.
A ordem importa: o set_real_ip_from deve ser processado antes das regras do ModSecurity. No nginx, isso significa que o include do arquivo de real_ip deve vir antes das diretivas do ModSecurity no server block.
Outros sistemas de WAF lidam com isso de forma diferente. Soluções WAF gerenciadas por cloud (como o WAF da OCI integrado ao Load Balancer, descrito em outro post) recebem o tráfego após o CDN já ter feito o unwrap do IP real — o problema não existe porque a camada de CDN é parte da mesma plataforma. Quando o WAF é self-managed e fica na origin, a configuração de real_ip é responsabilidade do operador.
928.219 bloqueios, 839 MB de error.log, disco em 70% — tudo causado por uma única linha incorreta em dois arquivos de vhost. include common/gocache.conf onde deveria estar include common/akamai.conf.
Checklist para WAF em network_mode host atrás de CDN
1. Identificar o CDN em uso: Akamai, Cloudflare, CloudFront, GoCache — cada um tem ranges de IPs distintos.
2. Verificar real_ip_header: Akamai envia X-Forwarded-For e True-Client-IP. Cloudflare usa CF-Connecting-IP. Confirmar qual header o CDN popula.
3. Aplicar em AMBAS as camadas: WAF e nginx backend precisam do set_real_ip_from. Corrigir só o WAF deixa rate limiting e logs do backend com IP errado.
4. Configurar logrotate: error.log e modsec_audit.log crescem rapidamente em caso de falsos positivos. Sem rotação, o disco enche silenciosamente.

