Cómo resolver pérdida de sesión PHP en contenedores Docker usando Redis como session store y sin sticky‑sessions
Por Kevin Marcondes · Publicado el · 8 min de lectura
La pérdida de sesión en entornos conteinerizados suele venir de desalineos entre la cookie, PHP y Redis, además de proxies/balanceadores sin sticky‑sessions. Aquí diagnosticás en minutos y corregís de forma estable: ajustes de PHP (session handler), TTL en Redis, flags de cookie, headers en el proxy y trampas como llamar a regenerate_id en cada petición. El objetivo: sesiones consistentes aun si la request cae en otro contenedor.
Señales del problema y cómo confirmar rápido
Antes de tocar configuración, validá el síntoma. Lo típico: el usuario inicia sesión, navega y pierde la autenticación al cambiar de ruta, abrir una nueva pestaña o tras unos minutos. En entornos sin sticky‑sessions esto ocurre con más frecuencia.
Confirmaciones útiles:
- ¿La cookie de sesión cambia entre peticiones?
- ¿El session_id existe en Redis y expira pronto?
- ¿El proxy está indicando HTTPS a la app para que la cookie 'Secure' funcione?
Usá las herramientas del navegador (pestaña Network) para ver Set‑Cookie y comprobar si la cookie vuelve en la siguiente petición. En línea de comandos, verificá el encabezado Set‑Cookie:
- Cookie de sesión ausente o cambiada en cada request
- TTL bajo en Redis (expira antes de lo esperado)
- Cookies con SameSite inapropiado en flujos cross‑site
- Proxy sin X‑Forwarded‑Proto en HTTPS
curl -sI https://app.seu-dominio.com | grep -i -E 'set-cookie|location'
Por qué ocurre sin sticky‑sessions
Sin sticky‑sessions, cualquier request puede llegar a un contenedor distinto. Esto está bien cuando la sesión vive fuera del pod/contenedor (Redis), pero exige consistencia total:
- Cookie estable: dominio, path, SameSite y Secure correctos.
- PHP alineado: session.save_handler=redis, TTL (gc_maxlifetime) y modo estricto para no reutilizar IDs inválidos.
- Redis predecible: TTL ≥ tiempo de sesión y política de memoria que no elimine claves activas indebidamente.
- Proxy honesto: headers X‑Forwarded‑Proto/Host correctos; si no, la app puede pensar que está en HTTP y no marcará la cookie como Secure.
Errores comunes incluyen llamar session_regenerate_id() en cada petición, TTL bajo en Redis, serializer/conexión incorrectos y cookie con SameSite inadecuado para flujos con redirecciones entre dominios.
PHP + Redis: configuración esencial para sesiones consistentes
Definí Redis como session handler y activá prácticas que eviten colisiones y reutilización de IDs. Ajustá en php.ini (o un ini personalizado de PHP‑FPM).
Consejos: usá un prefijo por aplicación, modo estricto, TTL coherente con tu negocio y cookie segura cuando el tráfico pase por HTTPS vía proxy. Consultá la documentación oficial de directivas de sesión de PHP para referencia.
En Docker, inyectá estas directivas mediante un archivo .ini montado como volumen o mediante variables de entorno si la imagen lo soporta.
- Prefijo único por app (evita colisiones entre servicios)
- session.use_strict_mode=1 impide asumir IDs inexistentes
- gc_maxlifetime ≥ tiempo esperado de sesión del usuario
- Cookie Secure + SameSite adecuados al flujo 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
Alineá tiempos y políticas: PHP, cookie, Redis y proxy
Las sesiones se rompen cuando los tiempos no coinciden. Asegurate de que el TTL de Redis no expire antes de lo que la app espera, y que la cookie no se descarte prematuramente. Evitá políticas de memoria que eliminen claves de sesión activas.
Usá esta tabla como referencia rápida:
| Componente | Qué ajustar | Referencia práctica |
|---|---|---|
| PHP (sessions) | gc_maxlifetime | Igualarlo al tiempo máximo de sesión (ej.: horas, no minutos) |
| Cookie | cookie_lifetime / SameSite / Secure | 0 (sesión del navegador) o valor ≥ gc_maxlifetime |
| Redis | TTL por clave de sesión | Debe ser ≥ gc_maxlifetime; si es posible, unos minutos extra |
| Redis (memoria) | maxmemory / policy | Evitá allkeys-* en stores compartidos; preferí volatile-* |
| Proxy/LB | idle timeout y headers | Timeout ≥ requests largas; enviá X-Forwarded-Proto/Host |
Reverse proxy sin sticky‑sessions: headers y TLS correctos
Cuando el proxy no mantiene afinidad, los headers deben ser impecables para que la app escriba cookies correctas. Sin X‑Forwarded‑Proto=HTTPS, muchas apps dejan de marcar Secure y el navegador no enviará la cookie en la siguiente navegación.
Ejemplo de Nginx como proxy reverso. Ajustá según tu stack.
- Reproducí Host y Proto reales para la app
- Activá healthchecks en el balanceador para sacar nodos malos del pool
- Evitá reescribir cookies indebidamente en el 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áctico: ver el session_id y qué se guarda en Redis
Logueá el session_id por petición para confirmar estabilidad y detectar regeneraciones inesperadas. En producción, reducí la verbosidad y centralizá logs. En Redis, comprobá el TTL de la clave (prefijo + ID) para ver si se renueva.
Evitá usar MONITOR en entornos de alto tráfico; preferí chequear claves puntuales.
- Correlacioná session_id con request_id en los logs
- Verificá el TTL con comandos específicos solo para la clave
- Confirmá que múltiples contenedores acceden al mismo Redis/prefijo
// middleware simple para depuración
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();
Regeneración de ID, concurrencia y escrituras perdidas
session_regenerate_id(true) es útil después del login, pero hacerlo en cada petición provoca pérdida de sesión bajo concurrencia. Dos pestañas pueden alternar IDs y sobrescribir datos.
Buenas prácticas: regenerar solo en eventos de seguridad (login, cambio de privilegios), mantener una sola escritura por petición y cerrar la sesión pronto cuando no necesites escribir (session_write_close).
- Regenerar solo en eventos críticos
- Evitar múltiples writes en la misma request
- Cerrar la sesión pronto cuando solo haya lectura
Cookies, dominios y SameSite en flujos con redirección
SameSite=Lax resuelve la mayoría de los casos; para flujos SSO/iframe puede ser necesario SameSite=None y Secure. Ajustá el dominio de la cookie al usar subdominios (ej.: .ejemplo.com) y asegurá path consistente (/).
Cuidado con entornos mixtos HTTP/HTTPS: si la app marca Secure y el usuario accede por HTTP en algún punto, la cookie no será enviada. Centralizá el acceso vía HTTPS en el proxy.
- SameSite=Lax para navegación normal; None+Secure para SSO/iframe
- Definí cookie_domain cuando uses subdominios
- Estandarizá HTTPS de punta a punta (proxy indica via X‑Forwarded‑Proto)
Checklist final de validación (sin sticky‑sessions)
Usá esta lista para cerrar el incidente con confianza. Probá en dos contenedores distintos y monitoreá durante unas horas antes de dar por cerrado el tema.
- Cookie estable (nombre, dominio, path, Secure, SameSite)
- session.save_handler=redis con prefijo y modo estricto
- gc_maxlifetime ≥ TTL de Redis; TTL se renueva en acceso
- Proxy envía X‑Forwarded‑Proto/Host correctamente
- No llamar session_regenerate_id en cada request
- Sin política de Redis que elimine claves activas
- Readiness/healthchecks sacan nodos del pool
Preguntas frecuentes
¿Cómo configurar PHP para usar Redis como session handler en Docker?
Cargá el módulo redis, definí session.save_handler=redis y session.save_path apuntando a tu servicio Redis (host, puerto, database y prefijo). Ajustá gc_maxlifetime, cookie_secure y SameSite según tu flujo. Montá un .ini en el contenedor PHP‑FPM o usá variables de entorno si la imagen las soporta.
¿Por qué se pierde la sesión al llamar session_regenerate_id en cada request?
Porque cada request crea un nuevo ID y, en concurrencia (dos pestañas), una petición puede invalidar el ID de la otra. En ausencia de sticky‑sessions esto se nota más. Regenerá solo después del login o cambios de privilegios y cerrá la sesión pronto para reducir colisiones.
¿Redis puede eliminar mis sesiones aunque tenga TTL configurado?
Sí, si la política de memoria permite eliminación de claves (ej.: allkeys-* bajo presión). Preferí políticas basadas en claves con TTL (volatile-*) y asegurá suficiente memoria para picos. Alineá el TTL con gc_maxlifetime de PHP y monitoreá el uso de memoria.
¿Necesito sticky‑sessions para estabilidad?
No. Si la sesión está en Redis con cookie y headers correctos, podés balancear libremente. Las sticky‑sessions enmascaran problemas de configuración y reducen la distribución de carga. Priorizar correcciones en cookie, PHP/Redis y proxy.
¿Cómo probar si el proxy está rompiendo cookies?
Verificá Set‑Cookie y Cookie en la pestaña Network del navegador. Compará con curl para confirmar encabezados. El proxy debe enviar X‑Forwarded‑Proto=HTTPS, conservar Host y no reescribir Set‑Cookie. Activá healthchecks para retirar nodos con reloj o problemas temporales.
¿Necesitás estabilizar sesiones PHP en producción?
Si querés una revisión rápida de tu stack (PHP‑FPM, Docker, Redis y proxy) y un plan de corrección de bajo riesgo, hablemos.
Quiero ayuda prácticaVer también
- 502 Bad Gateway intermitente en Docker Compose con Nginx y PHP‑FPM: cómo diagnosticar y corregir timeouts y exceso de conexiones
- Cómo diagnosticar y capturar paquetes por intermitencias de red entre contenedores Docker en Debian
- Cómo detectar y corregir inconsistencias de SKUs duplicados entre WooCommerce y ERP sin perder pedidos
- Cómo resolver el error 'Too many open files' en Linux en servidores PHP‑FPM durante picos de carga