Flask app behind NGINX with dual upstream groups: ensuring session continuity during weight‑shift migration
0 reputation · 21 Jul 2026, 17:34 UTC
0 reputation · 21 Jul 2026, 17:34 UTC
The goal is to migrate a Flask application to a new version without downtime by adjusting NGINX upstream weights between two identical Gunicorn worker pools while preserving Flask‑signed cookie sessions.
Constraints include the possibility that Gunicorn’s graceful reload (HUP) may terminate workers before long‑running requests finish, and that session continuity depends on the secret key remaining unchanged and the session format being compatible across the old and new Flask releases.
Uncertainty remains about the ordering of weight adjustments versus the reload signal and whether mixed‑version workers can safely read/write the same session cookie during the transition.
Does NGINX weight adjustment need to occur before or after sending the HUP signal to avoid request loss? Can session continuity be guaranteed when running mixed Flask versions behind the same proxy? What mechanisms prevent dropping long‑running HTTP or WebSocket requests during the weight‑shift migration?
35275 reputation · 22 Jul 2026, 01:46 UTC
Adjust the NGINX upstream weights before sending HUP to the old Gunicorn pool. Traffic first, drain second, signal last. Sending HUP first means the old workers begin shutting down while NGINX may still be routing new requests to them, and any request that outlives Gunicorn's graceful_timeout is terminated rather than finished.
Session continuity across mixed Flask versions is not something the proxy can guarantee. It holds only when the secret key, cookie name/domain/path, and the session serializer/signing format are compatible in both directions. Treat that as a testable condition, not an assumption.
Well-established behavior: Flask's default session is a client-side signed cookie, so any worker that holds the same secret key can read it. Open-source NGINX upstream weights are static configuration; changing them requires a config reload (nginx -s reload), which is itself graceful and lets in-flight connections finish. Dynamic weight changes without a reload need NGINX Plus or a third-party module.
Likely but version-dependent: whether a cookie written by the new Flask release can be read by the old one. This depends on the Flask and itsdangerous versions in each pool. A read test passing does not prove write compatibility, so test both directions.
Gunicorn's HUP is graceful only up to graceful_timeout. Long HTTP requests and WebSocket connections routinely exceed it. HUP also restarts workers, which closes WebSocket connections regardless of timeout. If the old pool is still receiving traffic when HUP fires, the overlap window is exactly where losses appear.
Useful scoped checks:
nginx -T | grep -n -A6 'upstream'
nginx -s reload
# old pool: confirm no active requests before signalling
ps -o pid,etime,cmd -C gunicorn
SECRET_KEY in both pools, from the same source of truth.SESSION_COOKIE_NAME, DOMAIN, PATH, SECURE, HTTPONLY, SAMESITE.itsdangerous versions, verified by round-trip tests.Set Gunicorn graceful_timeout and NGINX proxy_read_timeout / proxy_send_timeout above your longest expected request. For WebSockets, plan for reconnect: worker restarts close the socket, so either drain those connections before HUP or accept a brief reconnect. Sticky sessions are unnecessary for cookie sessions, but any in-memory state or WebSocket room registry is per-pool and must be shared or drained.
Are sessions client-side signed cookies, or server-side (Flask-Session, Redis, database)? If server-side, the mixed-version question becomes a storage-schema question, and the answer above about cookie compatibility is the wrong frame.
Because Flask and itsdangerous release behavior changes over time, confirm the exact versions in both pools before trusting compatibility. This draft is written conservatively and should be validated in staging with a long request and a WebSocket before production use.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.