This episode covers sticky sessions: cookie, IP, and header-based affinity, cookie configuration with the secure and httponly attributes, when sticky sessions are needed for stateful applications, and stateless alternatives like Redis and JWT.

Episode 14 showed load balancing that distributes requests evenly. But there's a problem: applications that store sessions in memory. If the first request goes to instance A and the next one to instance B, the session is lost — the user suddenly gets logged out. The solution is sticky sessions: making requests from the same client always reach the same instance.
Episode 15 covers how Caddy maintains session persistence: cookie, IP, and header-based affinity; secure cookie configuration; when sticky sessions are truly needed; and the stateless alternatives that are better in the long run.
A sticky session is the right tool at the right time — but not a cure-all. This episode teaches you to choose wisely.
Stateful applications store user session data in local memory: shopping carts, login tokens, multi-step form data. Even load balancing actually becomes a problem because sessions get scattered across instances.
A sticky session ensures one user is always connected to the same instance, so in-memory state is always found.
Need it:
Don't need it:
When possible, a stateless architecture is always better — we'll cover the alternatives at the end of this episode.
Caddy provides the cookie policy, which attaches a cookie to the response and reads it again on the next request:
app.example.com {
reverse_proxy {
to localhost:8080 localhost:8081
lb_policy cookie
}
}lb_policy cookie tells Caddy to use a cookie for affinity. The cookie is set on the first response and used to pick the backend on subsequent requests.
The cookie can be customized:
app.example.com {
reverse_proxy {
to localhost:8080 localhost:8081
lb_policy cookie {
name caddy_session
domain example.com
path /
max_age 3600
secure
httponly
}
}
}Important options:
name — the cookie name, default lb_cookie.secure — the cookie is only sent over HTTPS.httponly — the cookie can't be read by JavaScript.max_age — the cookie lifetime in seconds.domain and path — the cookie scope.secure and httponly must be enabled in production to prevent XSS attacks and cookie exfiltration over HTTP.
Without cookies, Caddy can pin clients by IP:
app.example.com {
reverse_proxy {
to localhost:8080 localhost:8081
lb_policy ip_hash
}
}lb_policy ip_hash uses the IP hash to pick a backend. Simple, but all users behind the same NAT (for example, an office) will be pinned to the same instance — uneven distribution.
For finer control, hash based on a header:
app.example.com {
reverse_proxy {
to localhost:8080 localhost:8081
lb_policy header {
field X-User-ID
}
}
}lb_policy header { field X-User-ID } pins based on the X-User-ID header value. A good fit if the application already provides a user identifier in a header.
For distributed cache storage, hash consistency matters: when a backend is added or removed, only a small fraction of mappings changes. Caddy supports this pattern through uri_hash and header — the same request always goes to the same backend as long as the backend set is stable.
When the pinned backend fails, Caddy routes the request to another backend and resets the cookie. Users aren't stuck on a dead instance forever — combined with health checks (episode 16), this is handled automatically.
The best way to eliminate the need for stickiness:
With shared storage, a load balancer can distribute requests evenly without caring about state.
A stateless architecture puts all state in the token:
app.example.com {
reverse_proxy {
to localhost:8080 localhost:8081
lb_policy round_robin
}
}With JWT, each request carries all the information the application needs — there's no session on the server. Backends can be replaced, added, or removed without affecting users. That's the main reason round_robin remains a healthy default choice.
Episode 15 covered session persistence: the sticky session concept and when it's needed, cookie-based affinity with secure and httponly options, IP and header-based affinity, consistent hashing, and stateless alternatives like shared storage and JWT.
Key takeaways:
lb_policy cookie pins clients via a cookie.secure and httponly on cookies in production.ip_hash is simple but uneven behind NAT.In the next episode, episode 16, we'll cover failover & circuit breaking — passive health checks, active health checks with custom endpoints, retry policies, the circuit breaker pattern, and high availability with multiple Caddy instances and an external load balancer. Your production readiness will level up.