Buzeli
buzeliSoluções Digitais
WordPress

wp-login demorando 1 minuto: como auth_basic atrás de CDN cria um loop de 401 invisível no nginx

Publicado em 23 de abril de 2026

O sintoma que não fazia sentido

Durante uma auditoria de performance em um portal de notícias de alto tráfego, a lentidão no wp-login.php foi a primeira anomalia encontrada. O TTFB (Time To First Byte) via navegador marcava cerca de 1 minuto. O mesmo request via curl direto ao servidor retornava em 2 milissegundos.

Quando localhost responde em 2ms e o navegador espera 1 minuto, o problema não está no servidor. Está no caminho entre o CDN e o servidor — ou na forma como o servidor responde quando a requisição vem pelo CDN.

A stack do servidor: nginx + PHP-FPM em containers Docker, sem WAF, com CDN GoCache na frente. O nginx fazia real IP passthrough via set_real_ip_from e real_ip_header X-Forwarded-For para extrair o IP real do visitante a partir dos cabeçalhos do CDN.

O acl.conf e o satisfy any

A investigação levou ao arquivo de configuração do WordPress no nginx. O location do wp-login.php incluía um arquivo compartilhado de controle de acesso:

Copiar
# /home/developer/webserver/common/wpcommon.conf
location = /wp-login.php {
  include common/acl.conf;
  limit_req zone=one burst=1 nodelay;
  include fastcgi_params;
  fastcgi_pass php;
}

O arquivo acl.conf continha:

Copiar
# /home/developer/webserver/common/acl.conf
satisfy any;
auth_basic "Restricted Area";
auth_basic_user_file htpasswd-ee;
allow 127.0.0.1;
allow 172.28.5.0/24;
allow 198.51.100.0/24;
allow 10.0.0.0/8;
deny all;

À primeira vista, a configuração parece razoável: ou o IP está na whitelist (allow), ou o visitante apresenta credenciais válidas (auth_basic). Qualquer um dos dois é suficiente — é isso que o satisfy any faz.

O que o satisfy any faz na prática

A diretiva satisfy any do nginx define que um location pode ser acessado se qualquer dos mecanismos de autenticação/autorização configurados for satisfeito. No contexto desse acl.conf:

Se o IP estiver na whitelist (allow): acesso liberado sem precisar de senha.

Se o IP não estiver na whitelist: nginx retorna 401 e solicita as credenciais básicas (auth_basic).

Se o visitante fornecer credenciais válidas no 401: acesso liberado.

O design pressupõe que IPs conhecidos (admins, escritório, VPN) entram direto, e IPs desconhecidos podem entrar com usuário e senha. Funciona perfeitamente quando o nginx vê o IP real do visitante.

O problema: o CDN não estava na whitelist

O nginx estava configurado com set_real_ip_from e real_ip_header X-Forwarded-For para extrair o IP real do cabeçalho enviado pelo CDN. Quando a configuração funciona corretamente, o nginx substitui o IP de origem pelo IP real do visitante antes de avaliar as regras de acesso.

Mas havia uma diferença sutil entre os dois testes:

curl local (127.0.0.1): Conexão direta ao nginx sem passar pelo CDN. O IP de origem é 127.0.0.1, que está na whitelist. Resultado: 200 em 2ms.

Navegador via CDN: CDN encaminha a requisição para o servidor. O nginx recebe o IP real do visitante via X-Forwarded-For — e esse IP não está na whitelist.

O nginx fazia a coisa certa: identificava o IP real do visitante. O problema é que o IP real do visitante não estava na whitelist. Então retornava 401.

Um 401 em texto puro não deveria causar 1 minuto de espera. O timeout aparecia porque o CDN, ao receber um 401 com WWW-Authenticate, tentava negociar a autenticação — esperando por uma resposta que nunca vinha no formato esperado. O resultado visível para o usuário era TTFB de ~60 segundos até o CDN desistir ou repassar o erro.

Comparação: antes e depois

O comportamento pode ser verificado diretamente comparando os dois caminhos:

Copiar
# Teste direto ao servidor (bypassa CDN)
curl -sv -o /dev/null -w "TTFB: %{time_starttransfer}s\n" \
  http://127.0.0.1/wp-login.php
# Resultado: TTFB: 0.002s — HTTP 200

# Teste via CDN (simula o navegador)
curl -sv -o /dev/null -w "TTFB: %{time_starttransfer}s\n" \
  -H "Host: cliente-exemplo.com.br" \
  https://cliente-exemplo.com.br/wp-login.php
# Resultado: TTFB: ~1.083s — HTTP 401

O 1.083s no teste via CDN já é mais rápido que o 1 minuto no navegador porque o curl não espera pela negociação de autenticação — ele retorna imediatamente ao receber o 401. O navegador, por outro lado, tenta renderizar o popup de autenticação e aguarda enquanto o CDN processa a resposta.

As opções de correção

Existem três abordagens, dependendo de como o cliente quer gerenciar o acesso ao wp-login.php:

Opção 1: adicionar o IP do operador na whitelist

Copiar
# acl.conf — adicionar IP específico para acesso imediato
satisfy any;
auth_basic "Restricted Area";
auth_basic_user_file htpasswd-ee;
allow 127.0.0.1;
allow 172.28.5.0/24;
allow 203.0.113.45;   # IP do operador/escritório — exemplo RFC5737
deny all;

Funciona para quem acessa de IPs fixos conhecidos. Não resolve para usuários com IP dinâmico ou acesso remoto sem VPN.

Opção 2: remover o acl.conf do wp-login se a proteção é por plugin

Copiar
# wpcommon.conf — sem acl.conf, proteção delegada ao WordPress
location = /wp-login.php {
  limit_req zone=one burst=1 nodelay;
  include fastcgi_params;
  fastcgi_pass php;
}

Adequado quando o WordPress já tem proteção por plugin (Limit Login Attempts, Wordfence, etc.). Elimina a dupla camada de autenticação que causava o conflito.

Opção 3: validar que htpasswd-ee tem credenciais e que o cliente usa intencionalmente

Em alguns casos, o auth_basic é intencional — o cliente quer que qualquer acesso ao wp-login.php exija senha antes de chegar ao PHP. Nesse caso, a solução é confirmar que o arquivo htpasswd tem credenciais válidas e que o CDN está configurado para repassar autenticação básica ao origin.

O anti-pattern: satisfy any com CDN

O padrão satisfy any + auth_basic + whitelist de IPs é eficaz em servidores com acesso direto. Ele quebra silenciosamente quando há um CDN na frente por um motivo específico: o CDN centraliza o acesso ao origin em um conjunto pequeno de IPs de saída. O visitante real tem um IP, mas o nginx vê o IP do CDN — ou o IP real extraído do X-Forwarded-For, que raramente coincide com a whitelist criada para IPs internos.

Toda vez que você coloca um CDN na frente de uma configuração que usa allow/deny por IP, você precisa revisar as regras de acesso. O que funcionava com acesso direto pode se tornar invisível ou inacessível via CDN.

Esse padrão é especialmente perigoso porque não gera erro explícito. O servidor responde com 401, o CDN processa normalmente, e o TTFB alto parece um problema de performance — não de configuração. Sem comparar localhost vs CDN, a causa real permanece invisível.

Como diagnosticar em outros servidores

Copiar
# 1. Comparar TTFB direto vs via CDN
curl -sv -w "\nTTFB: %{time_starttransfer}s\n" http://127.0.0.1/wp-login.php
curl -sv -w "\nTTFB: %{time_starttransfer}s\n" https://seu-site.com.br/wp-login.php

# 2. Verificar código HTTP retornado via CDN
curl -sv -o /dev/null https://seu-site.com.br/wp-login.php 2>&1 | grep "< HTTP"

# 3. Encontrar includes de acl em configurações nginx
grep -r "include.*acl" /home/developer/webserver/ --include="*.conf"
grep -r "satisfy" /home/developer/webserver/ --include="*.conf"

# 4. Verificar qual IP o nginx enxerga nas requisições CDN
grep "wp-login" /var/log/nginx/access.log | tail -20
# O IP no log deve ser o IP real do visitante, não o IP do CDN

Se o código HTTP via CDN for 401 e o TTFB for alto, o problema é o satisfy any aguardando resposta do CDN. Se o IP no log do nginx for o IP do CDN (não o IP real do visitante), o set_real_ip_from não está configurado — e o IP real nunca chegará à avaliação das regras de allow/deny.

O que ficou de lição

TTFB alto no wp-login.php é frequentemente tratado como problema de servidor sobrecarregado, banco de dados lento ou plugin pesado. Esses são os culpados habituais — e é por isso que a causa real demora para aparecer. Quando localhost responde em 2ms e o CDN responde em 1 minuto, o servidor está inocente. A investigação começa na camada de configuração do proxy.

O satisfy any não é um bug. É uma ferramenta poderosa que pressupõe que o nginx vê o IP real do cliente. Com CDN na frente, essa premissa pode quebrar — e a proteção se torna um bloqueio silencioso.
Custos3 de maio de 2026

R$1.800/mês em egress desnecessário: como identificar clientes bypassando o CDN com auditoria de Security Groups

Em fevereiro, um único cliente de uma plataforma WordPress multi-tenant foi responsável por 31% de todo o egress da infraestrutura — $342 de $1.088 totais. O domínio estava configurado no GoCache CDN, mas 3.803 GB saíram diretamente do EC2. O Security Group revelou o motivo: o SG World (0.0.0.0/0) estava anexado junto com o SG CDN, mantendo a origin acessível diretamente.

Ler artigo