Sticky Sessions
A load balancer normally spreads incoming requests across all healthy targets, so two requests from the same user can land on two different servers. Sticky sessions (also called session affinity) change that: once a user’s first request hits a target, the load balancer keeps sending that same user to that same target. This matters when your application stores per-user data in the memory of one server — like a shopping cart or a logged-in session — because losing that pin would lose the data. Stickiness is handy as a quick fix, but as you will see, it is usually a sign that your app needs a better design.
What stickiness actually does
When stickiness is on, the load balancer issues a cookie (a small piece of data the browser stores and sends back on every request). On each new request, the load balancer reads that cookie and routes the user to the target the cookie points to. Without stickiness, the balancer picks a target using its normal algorithm (round robin or least outstanding requests) for every single request.
There are two ways an Application Load Balancer (ALB — a layer 7 load balancer that understands HTTP) can do this:
| Stickiness type | Who creates the cookie | Cookie name | When to use it |
|---|---|---|---|
| Duration-based | The load balancer | AWSALB | Simplest option. You just pick how long the pin lasts (1 second to 7 days). The app needs no changes. |
| Application-based | Your application | AWSALBAPP + your own cookie | You control when the session is valid (e.g. tied to your login cookie). The app must set the cookie. |
Note: A Network Load Balancer (NLB — a layer 4 balancer that works at the TCP/UDP level) supports stickiness too, but only per target group based on the client’s source IP address. It has no cookies because it does not read HTTP.
When to use this (and when not to)
Use stickiness when your app keeps session state in the local memory of one instance and you cannot change that right now. A classic example: a legacy app that stores the logged-in user and cart in an in-process session object. Without stickiness, the user would appear logged out the moment a later request hit a different server.
Avoid stickiness when you can make your app stateless instead. A stateless app stores session data in a shared place that every server can read — such as Amazon ElastiCache (a managed Redis or Memcached cache) or Amazon DynamoDB (a managed NoSQL key-value database). Then any server can serve any request, and stickiness becomes unnecessary.
The reason this matters is in the next section.
The gotchas
Stickiness has two real costs you must understand before turning it on.
- It defeats even load distribution. The load balancer can no longer freely pick the least-busy target — it is locked to the user’s pinned target. If a few “whale” users (heavy traffic) all land on one instance, that instance gets hammered while others sit idle.
- It breaks ungracefully when a target dies. If the pinned instance fails its health check and is removed, the load balancer reroutes the user to a new target. But that new target has none of the in-memory session data, so the user is suddenly logged out or loses their cart. There is no smooth failover — the session is simply gone.
Warning: Treat stickiness as a stopgap, not a strategy. The durable fix is a stateless app with shared session storage. Then a dying instance causes zero user impact, and load spreads evenly.
How to enable duration-based stickiness
Stickiness is configured on the target group, not the load balancer itself.
Console steps
- Open the EC2 console and choose Target Groups in the left menu.
- Select your target group (for example
tg-web-app). - Open the Attributes tab and choose Edit.
- Toggle Stickiness on.
- Set Stickiness type to Load balancer generated cookie (duration-based).
- Set Stickiness duration (for example
3600seconds = 1 hour). - Choose Save changes.
AWS CLI
aws elbv2 modify-target-group-attributes \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/tg-web-app/0a1b2c3d4e5f6789 \
--attributes \
Key=stickiness.enabled,Value=true \
Key=stickiness.type,Value=lb_cookie \
Key=stickiness.lb_cookie.duration_seconds,Value=3600
Output:
{
"Attributes": [
{ "Key": "stickiness.enabled", "Value": "true" },
{ "Key": "stickiness.type", "Value": "lb_cookie" },
{ "Key": "stickiness.lb_cookie.duration_seconds", "Value": "3600" }
]
}
For application-based stickiness, set stickiness.type to app_cookie and provide your cookie name:
aws elbv2 modify-target-group-attributes \
--target-group-arn arn:aws:elasticloadbalancing:us-east-1:111122223333:targetgroup/tg-web-app/0a1b2c3d4e5f6789 \
--attributes \
Key=stickiness.enabled,Value=true \
Key=stickiness.type,Value=app_cookie \
Key=stickiness.app_cookie.cookie_name,Value=MYSESSION \
Key=stickiness.app_cookie.duration_seconds,Value=86400
Infrastructure as code
Defining it in Terraform keeps the setting versioned and repeatable:
resource "aws_lb_target_group" "web_app" {
name = "tg-web-app"
port = 80
protocol = "HTTP"
vpc_id = "vpc-0a1b2c3d"
stickiness {
type = "lb_cookie"
cookie_duration = 3600
enabled = true
}
}
Verifying it works
After enabling, make a request and inspect the response headers. The load balancer adds a Set-Cookie header:
curl -i http://my-alb-1234567890.us-east-1.elb.amazonaws.com/
Output:
HTTP/1.1 200 OK
Set-Cookie: AWSALB=Xy9kQ2vR...; Expires=Sun, 15 Jun 2026 12:00:00 GMT; Path=/
Set-Cookie: AWSALBCORS=Xy9kQ2vR...; Expires=Sun, 15 Jun 2026 12:00:00 GMT; Path=/; SameSite=None; Secure
Content-Type: text/html
Send that cookie back on the next request and you will keep hitting the same instance.
Cost note: Stickiness itself is free — there is no extra ELB charge for enabling it. The hidden cost is operational: uneven load can force you to run larger or more instances than a balanced, stateless design would need.
Best practices
- Prefer a stateless app with shared session storage (ElastiCache or DynamoDB) over stickiness whenever you can change the code.
- If you must use stickiness, keep the duration short so a failed instance affects each user for the least time.
- Combine stickiness with cross-zone load balancing so pinned users are still spread across Availability Zones.
- Use application-based stickiness when you need the session pin to expire exactly with your own login session.
- Make sure health checks are tuned correctly — a slow check means users keep getting routed to a dead, pinned target.
- Treat stickiness as a temporary bridge while you migrate toward a stateless architecture, and track it as technical debt.