Como resolver perda de sessão PHP em containers Docker com Redis como session store e sem sticky‑sessions

Por · Publicado em · 8 min de leitura

Pátio com vários contêineres empilhados e empilhadeira transportando um contêiner laranja
Metáfora para containers Docker e gestão de sessões em ambientes distribuídos Foto: Aan Amrin / Pexels

Perda de sessão PHP em ambientes containerizados geralmente vem de desalinhamento entre cookie, PHP e Redis, somado a proxy/load balancer sem sticky‑sessions. Aqui você diagnostica em minutos e corrige de forma estável: ajustes de PHP (session handler), TTL no Redis, flags do cookie, cabeçalhos no proxy e armadilhas como regenerate_id em cada requisição. O objetivo é ter sessões consistentes, mesmo mudando de container a cada request.

Sinais do problema e como confirmar rapidamente

Antes de mexer na configuração, valide o sintoma. O problema típico: o usuário faz login, navega e perde a autenticação ao trocar de rota, abrir nova aba ou após alguns minutos. Em cenários sem sticky‑sessions isso aparece com mais frequência.

Confirmações úteis:

- O cookie de sessão muda entre requisições?

- O session_id existe no Redis e expira cedo?

- O proxy está sinalizando HTTPS ao app para que o cookie "Secure" funcione?

Use o navegador (Aba Network) para ver Set‑Cookie e se o cookie volta na próxima requisição. Em linha de comando, verifique o cabeçalho Set‑Cookie:

  • Cookie de sessão ausente ou trocado a cada request
  • TTL baixo no Redis (expira antes do esperado)
  • Cookies com SameSite inadequado em fluxos cross‑site
  • Proxy sem X‑Forwarded‑Proto em HTTPS
curl -sI https://app.seu-dominio.com | grep -i -E 'set-cookie|location'

Por que isso acontece sem sticky‑sessions

Sem sticky‑sessions, qualquer request pode cair em um container diferente. Isso é saudável quando a sessão vive fora do pod/container (Redis), mas exige consistência total:

- Cookie estável: domínio, path, SameSite e Secure corretos.

- PHP alinhado: session.save_handler=redis, TTL (gc_maxlifetime) e modo estrito para não reaproveitar IDs inválidos.

- Redis previsível: TTL ≥ tempo de sessão e política de memória que não evite chaves ativas indevidamente.

- Proxy honesto: cabeçalhos X‑Forwarded‑Proto/Host corretos; caso contrário, o app pensa que está em HTTP e não seta cookie Secure.

Erros comuns incluem chamar session_regenerate_id() em toda requisição, TTL baixo no Redis, serializer/conexão errados e cookie com SameSite inapropriado para fluxos com redirecionamentos entre domínios.

PHP + Redis: configuração essencial para sessões consistentes

Defina o Redis como session handler e ative práticas que evitam colisões e reaproveito de IDs. Ajuste no php.ini (ou ini personalizado do PHP‑FPM).

Dicas: use prefix por aplicação, modo estrito, TTL coerente com seu negócio e cookie seguro quando trafegando sobre HTTPS via proxy. Veja a referência oficial de diretivas de sessão do PHP na documentação.

Em Docker, injete essas diretivas via arquivo .ini montado como volume ou variáveis de ambiente conforme sua imagem base.

  • Prefix único por app (evita colisão entre serviços)
  • session.use_strict_mode=1 impede assumir IDs não existentes
  • gc_maxlifetime ≥ tempo esperado de sessão do usuário
  • Cookie Secure + SameSite adequados ao fluxo de login
; php.ini
session.save_handler = redis
session.save_path = "tcp://redis:6379?database=2&prefix=sess_&timeout=2&read_timeout=2&persistent=1"
session.use_strict_mode = 1
session.gc_maxlifetime = 14400
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = Lax
session.sid_length = 48
session.sid_bits_per_character = 6
session.lazy_write = 1
session.serialize_handler = php_serialize

Sessões quebram quando tempos não batem. Garanta que o TTL do Redis não expire antes do que o app espera, e que o cookie não seja descartado cedo. Evite políticas de memória que removam chaves de sessão ativas.

Use este quadro como referência rápida:

Componente O que ajustar Referência prática
PHP (sessions) gc_maxlifetime Iguale ao tempo máximo de sessão (ex.: horas, não minutos)
Cookie cookie_lifetime / SameSite / Secure 0 (sessão do navegador) ou valor ≥ gc_maxlifetime
Redis TTL por chave de sessão Deve ser ≥ gc_maxlifetime; se possível, alguns minutos a mais
Redis (memória) maxmemory / policy Evite allkeys-* em stores compartilhados; prefira volatile-*
Proxy/LB idle timeout e headers Timeout ≥ requests longas; envie X-Forwarded-Proto/Host

Reverse proxy sem sticky‑sessions: headers e TLS corretos

Quando o proxy não mantém afinidade, os headers precisam ser impecáveis para o app escrever cookies corretos. Sem X‑Forwarded‑Proto=HTTPS, muitos apps deixam de marcar Secure e o navegador não envia o cookie na próxima navegação.

Exemplo de Nginx como proxy reverso. Ajuste conforme sua stack. Para comparação de proxies em Docker, veja este conteúdo aprofundado.

  • Replique Host e Proto reais para o app
  • Ative healthchecks no balanceador para tirar nós ruins do pool
  • Evite reescrever cookies indevidamente no proxy
location / {
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_pass http://app_upstream;
}

Debug prático: veja o session_id e o que grava no Redis

Logue o session_id por request para confirmar estabilidade e detectar regenerações inesperadas. Em produção, reduza verbosidade e centralize logs. No Redis, confira TTL da chave (prefix + ID) para ver se está renovando.

Evite usar MONITOR em ambientes de alto tráfego; prefira checagens direcionadas a uma chave de sessão específica.

  • Correlacione session_id com request_id nos logs
  • Verifique TTL com comandos específicos apenas para a chave
  • Confirme que múltiplos containers acessam o mesmo Redis/prefix
// middleware simples para depuração
session_start();
error_log('sid_before=' . session_id());
// ... sua lógica de autenticação ...
// Evite chamar session_regenerate_id() em toda requisição
session_write_close();

Regeneração de ID, concorrência e writes perdidos

session_regenerate_id(true) é útil no pós‑login, mas chamá‑lo a cada request causa perda de sessão sob concorrência. Duas abas podem alternar IDs e sobrescrever dados.

Boas práticas: regenere apenas em eventos de segurança (login, mudança de privilégio), mantenha uma única escrita por request e feche a sessão cedo quando não precisar escrever (session_write_close).

  • Regenerar somente em eventos críticos
  • Evitar múltiplos writes na mesma request
  • Fechar a sessão cedo quando só for leitura

Cookies, domínios e SameSite em fluxos com redirecionamento

SameSite=Lax resolve a maioria dos casos; para fluxos SSO/iframe, talvez precise SameSite=None e Secure. Ajuste o domínio do cookie quando usar subdomínios (ex.: .exemplo.com) e garanta path consistente (/).

Cuidado com ambientes mistos HTTP/HTTPS: se o app marca Secure e o usuário acessa via HTTP em algum ponto, o cookie não será enviado. Centralize o acesso via HTTPS no proxy.

  • SameSite=Lax para navegação normal; None+Secure para SSO/iframe
  • Defina cookie_domain quando usar subdomínios
  • Padronize HTTPS ponta a ponta (proxy sinaliza via X‑Forwarded‑Proto)

Checklist final de validação (sem sticky‑sessions)

Use esta lista para encerrar o incidente com confiança. Teste em dois containers distintos e monitore por algumas horas antes de encerrar o tópico.

  • Cookie estável (nome, domínio, path, Secure, SameSite)
  • session.save_handler=redis com prefix e modo estrito
  • gc_maxlifetime ≥ TTL do Redis; TTL se renova no acesso
  • Proxy envia X‑Forwarded‑Proto/Host corretamente
  • Nada de session_regenerate_id em toda request
  • Sem política de Redis que evapore chaves ativas
  • Readiness/healthchecks removem nós do pool

Perguntas frequentes

Como configurar PHP para usar Redis como session handler em Docker?

Carregue o módulo redis, defina session.save_handler=redis e session.save_path apontando para seu serviço Redis (host, porta, database e prefix). Ajuste gc_maxlifetime, cookie_secure e SameSite conforme seu fluxo. Monte um .ini no container PHP‑FPM ou use variáveis de ambiente suportadas pela sua imagem.

Por que perder sessão ao chamar session_regenerate_id em cada request?

Porque cada request cria um novo ID e, em concorrência (duas abas), um request pode invalidar o ID do outro. Sem sticky‑sessions isso aparece mais. Regenerar somente após login ou mudança de privilégios e feche a sessão cedo reduz colisões.

Redis pode remover minhas sessões mesmo com TTL configurado?

Sim, se a política de memória permitir remoção de chaves (ex.: allkeys-* com alta pressão). Prefira políticas baseadas em chaves com TTL (volatile-*) e garanta memória suficiente para o pico. Alinhe o TTL ao gc_maxlifetime do PHP e monitore uso de memória.

Preciso de sticky‑sessions para estabilidade?

Não. Se a sessão estiver no Redis com cookie e headers corretos, você pode balancear livremente. Sticky‑sessions mascaram problemas de configuração e reduzem distribuição de carga. Priorize correções em cookie, PHP/Redis e proxy.

Como testar se o proxy está quebrando cookies?

Verifique Set‑Cookie e Cookie na aba Network do navegador. Compare com curl para confirmar headers. O proxy deve enviar X‑Forwarded‑Proto=HTTPS, manter Host e não reescrever Set‑Cookie. Ative healthchecks para retirar nós com relógio errado ou problemas temporários.

Precisa estabilizar sessões PHP em produção?

Se quiser uma revisão rápida da sua stack (PHP‑FPM, Docker, Redis e proxy) e um plano de correção com baixo risco, vamos conversar.

Quero ajuda prática

Veja também