O carrinho que esvaziava sozinho: o prefixo de cookie no CloudFront que zerou as vendas do WooCommerce
Publicado em 18 de junho de 2026

O sintoma: carrinho cheio no popup, vazio na página
Um e-commerce WooCommerce rodando atrás de CloudFront, com nginx + WAF em uma instância EC2 como origin. O cliente adicionava um produto: o mini-cart lateral, atualizado via AJAX, mostrava o item normalmente. Mas ao abrir /carrinho/ ou /finalizar-compra/, o WooCommerce respondia "Seu carrinho está vazio".
O impacto era direto na receita: nenhum pedido conseguia ser finalizado. Para uma loja transacional, isso não é um bug de UX — é a operação parada.
O diagnóstico parcial: o lugar certo, pela razão errada
O primeiro diagnóstico apontou para o cache do CloudFront — e estava certo em apontar para lá. Mas a leitura dos detalhes estava errada em dois pontos que mudavam totalmente a solução:
Cache policy errada: assumiu-se que a distribution usava a policy gerenciada Managed-CachingOptimized. Na veridade era a UseOriginCacheControlHeaders-QueryStrings — que respeita o Cache-Control que o origin envia. A diferença é crucial, como veremos.
Behaviors dedicados por path: a proposta foi criar behaviors separados para /carrinho/, /finalizar-compra/ e /minha-conta/, marcando-os como não-cacheáveis. Inviável: o plano tinha limite de 5 behaviors e todos já estavam em uso.
O cache do CloudFront estava no caminho do problema. Mas a solução não era criar mais behaviors — era fazer o origin falar a língua que a cache policy já estava ouvindo.
Causa raiz, camada 1: o origin não enviava Cache-Control
A policy UseOriginCacheControlHeaders respeita o header Cache-Control da resposta do origin para decidir o que cachear e por quanto tempo. O problema: o nginx não enviava Cache-Control em resposta nenhuma — nem para assets, nem para páginas. WordPress, por padrão, também não envia headers de cache adequados para um CDN.
Sem o header, o comportamento ficava indefinido: o CDN podia decidir cachear uma página dinâmica de carrinho, servindo a mesma resposta para usuários diferentes. Mas, sozinho, isso ainda não explicava o carrinho vazio. Faltava a segunda camada.
Causa raiz, camada 2: a CloudFront Function comia o cookie de sessão
Havia uma CloudFront Function rodando no evento viewer-request, cuja função era normalizar a cache key filtrando cookies — mantendo apenas os que importam para o cache e descartando o resto. A whitelist mantinha cookies com prefixo woocommerce_.
O detalhe que quebrava tudo: o cookie de sessão do WooCommerce não se chama woocommerce_session — ele é wp_woocommerce_session_HASH, com prefixo wp_. Ele não casava com a whitelist. Resultado: a Function removia o cookie HttpOnly de sessão antes da request chegar ao origin. Mesmo quando o nginx recebia a request, o WordPress não via o cookie de sessão e criava uma sessão nova e vazia. Por isso o carrinho "esvaziava" ao trocar de página.
// CloudFront Function (viewer-request) — whitelist de cookies para a cache key
// ANTES: o cookie de sessão wp_woocommerce_session_ NÃO casava
function keepCookie(name) {
return name.startsWith('woocommerce_');
}
// O cookie de sessão real era: wp_woocommerce_session_1a2b3c... (prefixo wp_)
// → removido antes de chegar ao origin → WordPress recriava sessão vaziaO fix, camada 1: Cache-Control correto no nginx
No location / do server block do WAF, passamos a definir o Cache-Control explicitamente — com no-store para as rotas transacionais e bypass quando há cookie de sessão/login:
# nginx (origin) — Cache-Control por rota + bypass por cookie
location / {
proxy_hide_header Cache-Control;
set $page_cache "public, s-maxage=3600, max-age=300";
# Rotas transacionais nunca são cacheadas
if ($request_uri ~* "^/(carrinho|finalizar-compra|minha-conta)") {
set $page_cache "no-store, no-cache, must-revalidate";
}
# Qualquer sessão ativa (login ou carrinho) faz bypass
if ($http_cookie ~* "wordpress_logged_in_|woocommerce_session_|woocommerce_cart_hash") {
set $page_cache "no-store, no-cache, must-revalidate";
}
add_header Cache-Control $page_cache;
}Para assets estáticos, o oposto — cache longo e imutável, removendo o Vary que atrapalha a eficiência do CDN:
# nginx (origin) — assets estáticos
location ~* \.(css|js|jpg|jpeg|png|gif|svg|woff2?|ico)$ {
proxy_hide_header Vary;
proxy_hide_header Cache-Control;
add_header Cache-Control "public, max-age=31536000, immutable";
}Por que detecção por path E por cookie?
A detecção por path (/carrinho/, /finalizar-compra/, /minha-conta/) é a mais robusta: garante que essas rotas nunca sejam cacheadas, independente de o cliente ter um cookie ou não. A detecção por cookie cobre o resto — qualquer página vista por um usuário com sessão ativa não deve servir conteúdo cacheado de outra pessoa.
O fix, camada 2: incluir o prefixo wp_woocommerce_session_ na whitelist
A correção na CloudFront Function foi uma linha — adicionar o prefixo wp_woocommerce_session_ à whitelist de cookies preservados:
// CloudFront Function (viewer-request) — whitelist corrigida
function keepCookie(name) {
return name.startsWith('woocommerce_') ||
name.startsWith('wp_woocommerce_session_'); // <-- o cookie de sessão real
}Atenção: uma CloudFront Function publicada é global — afeta todas as distributions que a usam. A alteração precisa ser revisada e validada antes de ir para LIVE, não testada em produção no susto.
Validação
Teste de configuração e reload do nginx (rodando em container):
sudo docker exec waf nginx -t
sudo docker exec waf nginx -s reloadInvalidação do CloudFront — primeiro um purge das rotas afetadas, para não esperar o TTL antigo expirar:
aws cloudfront create-invalidation \
--distribution-id EXXXXXXXXXXXXX \
--paths "/carrinho/*" "/finalizar-compra/*" "/minha-conta/*"O resultado, camada por camada:
Antes → Depois:
/carrinho/ Cache-Control : (nenhum) -> no-store, no-cache, must-revalidate
/finalizar-compra/ Cache-Control : (nenhum) -> no-store, no-cache, must-revalidate
/minha-conta/ Cache-Control : (nenhum) -> no-store, no-cache, must-revalidate
Páginas normais Cache-Control : (nenhum) -> public, s-maxage=3600, max-age=300
Assets Cache-Control : (nenhum) -> public, max-age=31536000, immutable
Cookie wp_woocommerce_session_ : removido -> passado ao origin
Carrinho funcional : NÃO -> SIMFluxo completo validado de ponta a ponta: adicionar produto → /carrinho/ mostra os itens. Nos response headers, a confirmação: cache-control: no-store e x-cache: Miss from cloudfront nas rotas de carrinho.
Lições
1. O cookie de sessão do WooCommerce usa prefixo wp_. Ele é wp_woocommerce_session_HASH, não woocommerce_session. Qualquer CloudFront Function (ou regra de WAF/CDN) que filtre cookies por prefixo precisa incluir wp_woocommerce_session_ explicitamente.
2. Rotas transacionais devem ser no-store por path. /carrinho/, /finalizar-compra/ e /minha-conta/ nunca devem ser cacheadas. Detecção por path é mais robusta que por cookie — não depende de o cliente já ter uma sessão.
3. Dois problemas sobrepostos exigem dois fixes. Mesmo corrigindo o Cache-Control no origin, se a Function remove o cookie de sessão o WordPress recria sessão vazia. As duas camadas precisavam estar corretas ao mesmo tempo — corrigir só uma daria a falsa sensação de que o fix "não funcionou".
4. Limite de behaviors? Controle pelo origin. Quando o plano do CDN limita o número de behaviors, a saída não é criar behaviors dedicados por rota — é controlar o cache via headers Cache-Control no origin, que a cache policy correta já respeita.
Esse mesmo tema de cookie atrás de CDN já apareceu por aqui — em um loop de 401 invisível com auth_basic no wp-login. A diferença é que lá o problema era um header sendo exigido; aqui era um cookie sendo descartado.
Conclusão
Quando infra, CDN e aplicação interagem, o bug raramente mora em uma camada só. O carrinho vazio não era "culpa do WooCommerce" nem "culpa do CloudFront" isoladamente: era a soma de um origin silencioso (sem Cache-Control) com uma Function zelosa demais (descartando o cookie certo). O fix foi pequeno em cada ponta — mas só funcionou porque as duas pontas foram tratadas juntas.