Skip to content

Session Persistence

Session persistence (also called sticky sessions) ensures that all requests belonging to the same user session are routed to the same backend instance. This is required when a backend stores state locally, such as a shopping cart in memory or a WebSocket connection tied to a specific process.

The first time a user makes a request, the gateway routes it to a backend instance using the standard load balancing algorithm. If session persistence is configured for that route, the gateway attaches a session identifier to the response. On subsequent requests from the same user, the gateway reads the identifier and routes the request to the same backend instance that handled the original session.

The session identifier is a signed token that encodes the backend name, endpoint address, and optional expiry times. The signing prevents tampering: if a client modifies the identifier, the signature fails to verify and the gateway treats it as a new session, routing to a fresh backend instance.

When sessionType is Cookie (the default), the gateway sets a cookie on the first response. The cookie holds the signed session token. The cookie name is configured through the sessionName field. If the cookie already exists on an incoming request, the gateway verifies its signature and routes to the recorded backend.

The cookie carries an optional absoluteTimeout that limits the lifetime of the session. When the timeout is reached, the session is discarded and the next request is routed to a new backend instance. If no absolute timeout is set, the session persists indefinitely.

Cookie lifetime is controlled by cookieConfig.lifetimeType:

Lifetime TypeCookie ExpiryDescription
SessionBrowser session (no Expires/Max-Age)The cookie is deleted when the browser closes. This is the default.
PermanentSet by absoluteTimeoutThe cookie includes a Max-Age attribute matching the configured timeout. Requires absoluteTimeout to be set.

When sessionType is Header, the gateway does not set a cookie. Instead it sets a response header with the name configured in sessionName. The client is expected to echo the header value back on subsequent requests. This mode is useful for API clients that do not handle cookies, or for gRPC services where cookies are uncommon.

Header-based sessions have the same signing, timeout, and routing guarantees as cookie-based sessions. The only difference is the transport mechanism.

Session persistence is configured through the BackendLBPolicy CRD under the session_persistence field:

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: BackendLBPolicy
metadata:
name: cart-sticky
namespace: nantian-demo
spec:
targetRefs:
- group: ""
kind: Service
name: shopping-cart-service
session_persistence:
sessionName: ROUTEID
sessionType: Cookie
absoluteTimeout: 3600s
idleTimeout: 600s
cookieConfig:
lifetimeType: Session

This policy targets the shopping-cart-service backend. Requests are routed to a backend instance and the gateway sets a ROUTEID cookie with a signed session token. The session cookie lasts for the browser session. The absolute timeout of 3600 seconds (1 hour) caps the maximum session lifetime even if the browser does not close. If the user is idle for 600 seconds (10 minutes), the session is eligible for expiry.

When targetRefs contains multiple Services, each Service gets its own persistence configuration. A single BackendLBPolicy can apply cookie-based persistence to one Service and header-based persistence to another, as long as each has a separate policy.

The session token is signed with a shared secret for integrity. The gateway supports three methods to configure the secret:

MethodConfigurationUse Case
Inline secretshared_secret in data plane configSmall deployments with a pre-shared key
File-based secretsharedSecretFile pointing to a file pathDeployments with secret rotation (the file is re-read every 250ms)
Auto-generatedNo configurationDevelopment and single-replica deployments only

For multi-replica data plane deployments, configure a consistent shared_secret or sharedSecretFile across all replicas. If each replica auto-generates a different key, a session token signed by replica A will fail validation on replica B, causing the user to lose their session when traffic shifts between replicas.

Session persistence is the right choice when:

  • A backend stores state in local memory instead of a shared database or cache.
  • You are running a WebSocket or long-polling service that must stay pinned to one instance.
  • A backend relies on filesystem-local data that is not replicated across instances.
  • You are gradually migrating services and need to pin users to specific backend versions.

Session persistence is tradeoff. It reduces the effectiveness of load balancing by concentrating traffic from the same session onto a single backend. If a backend instance goes down, all sessions pinned to it are lost until new sessions are established. Use it only for backends that genuinely need sticky routing.

Session persistence does not survive data plane restarts. The gateway stores session-to-backend mappings in memory. When a data plane process restarts, all session state is lost. Incoming requests that carry a valid session token from before the restart are rejected as unknown and routed to a new backend instance. The user will be assigned a new session automatically, but any state tied to the old backend instance is gone.

Session tokens are opaque to the client. The client should not attempt to parse or modify the cookie value. Any modification will invalidate the signature and cause the gateway to create a new session.

If users report being unexpectedly logged out or losing session state, the most common cause is missing session persistence configuration. Unlike nginx-ingress which can enable sticky sessions with an annotation (nginx.ingress.kubernetes.io/affinity: cookie), nantian-gw requires an explicit BackendLBPolicy.

  1. Check if you have a BackendLBPolicy. List policies in your namespace:

    Terminal window
    kubectl get backendlbpolicies -n your-namespace

    If you don’t see one targeting your backend Service, sessions are not sticky and every request may land on a different backend Pod.

  2. Verify the policy targets the correct Service. The targetRefs.name must match your Service name exactly:

    Terminal window
    kubectl describe backendlbpolicy <name> -n your-namespace
  3. Check for multi-replica deployments. If you run multiple dataplane replicas, configure a shared secret:

    # dataplane config
    session_persistence:
    shared_secret: "your-32-byte-secret"

    Without this, a session cookie signed by replica A will be rejected by replica B.

  4. Verify the cookie is being set. Check response headers:

    Terminal window
    curl -v https://your-app.example.com 2>&1 | grep -i set-cookie

    You should see a Set-Cookie header with the sessionName you configured.

  5. Check absoluteTimeout and idleTimeout. If either is too short, sessions expire prematurely. Start with absoluteTimeout: 1h and idleTimeout: 15m and adjust based on your application’s needs.

nginx-ingress annotationnantian-gw equivalent
nginx.ingress.kubernetes.io/affinity: cookieCreate a BackendLBPolicy with session_persistence.type: Cookie
nginx.ingress.kubernetes.io/session-cookie-namesession_persistence.sessionName
nginx.ingress.kubernetes.io/session-cookie-expiressession_persistence.absoluteTimeout
nginx.ingress.kubernetes.io/session-cookie-max-agesession_persistence.cookieConfig.lifetimeType: Permanent
nginx.ingress.kubernetes.io/session-cookie-pathNot applicable (cookie path is / by default)