Sticky Session: Session Affinity Complete Guide
Sticky Session (also called Session Affinity) is a technique where the load balancer guarantees that requests from the same client always go to the same server. It requires only load balancer configuration with no application code changes, making it especially useful for legacy systems.
How Sticky Session Worksβ
First request:
Client β LB β App2 (session created, JSESSIONID=XYZ.server2)
Subsequent requests:
Client (Cookie: JSESSIONID=XYZ.server2)
β LB: reads ".server2" β routes to App2
β App2: looks up session XYZ β success
The load balancer reads the routing suffix in the session cookie (.server2), or injects its own cookie, to decide which server to route to.
Nginx: Cookie-based Sticky Sessionβ
Open source Nginx does not support cookie-based Sticky Session natively. Two alternatives are available:
Option 1: ip_hash (IP-based affinity)β
Hashes the client IP to always route to the same server.
upstream backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
Advantage: Simple configuration. Disadvantage: Multiple users behind the same NAT may concentrate on one server.
Option 2: hash $cookie_JSESSIONID (cookie value hash)β
Hashes the JSESSIONID cookie value returned by Tomcat for routing.
upstream backend {
hash $cookie_JSESSIONID consistent;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
The first request (no cookie) acts like random routing; subsequent requests with the cookie are pinned to the same server.
Option 3: Nginx Plus sticky cookie (commercial)β
Nginx Plus offers full cookie-based Sticky Session.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
Apache mod_proxy_balancer: Cookie-based Sticky Sessionβ
Apache supports cookie-based Sticky Session out of the box in the open source version.
Tomcat jvmRoute Configuration (Required)β
Set a unique jvmRoute in each Tomcat's server.xml:
<!-- Tomcat 1 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server1">
<!-- Tomcat 2 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server2">
Tomcat then automatically appends the server identifier to session IDs:
Set-Cookie: JSESSIONID=1A2B3C4D.server1; Path=/; HttpOnly
Apache Configurationβ
<Proxy "balancer://mycluster">
BalancerMember "http://10.0.0.1:8080" route=server1
BalancerMember "http://10.0.0.2:8080" route=server2
BalancerMember "http://10.0.0.3:8080" route=server3
ProxySet lbmethod=byrequests
ProxySet stickysession=JSESSIONID
</Proxy>
ProxyPass "/" "balancer://mycluster/"
ProxyPassReverse "/" "balancer://mycluster/"
Apache reads .server1 from JSESSIONID=1A2B3C4D.server1 and routes to the BalancerMember with route=server1.
mod_jk: Automatic jvmRoute Integrationβ
mod_jk automatically maps jvmRoute to worker names in workers.properties.
worker.loadbalancer.sticky_session=True
worker.loadbalancer.sticky_session_force=False # Allow failover if session server goes down
Setting sticky_session_force=True returns an error instead of failing over to another server when the session server is down. False (default) is safer.
Production Setup: Apache + Tomcat Sticky Sessionβ
[Client]
β
[Apache] β stickysession=JSESSIONID
βββ Tomcat1 (jvmRoute=server1) β port 8080
βββ Tomcat2 (jvmRoute=server2) β port 8081
βββ Tomcat3 (jvmRoute=server3) β port 8082
1. Each Tomcat server.xmlβ
<!-- Tomcat 1 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server1">
<!-- Tomcat 2 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server2">
<!-- Tomcat 3 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server3">
2. Apache Configurationβ
<Proxy "balancer://appcluster">
BalancerMember "http://127.0.0.1:8080" route=server1 max_fails=3 retry=30
BalancerMember "http://127.0.0.1:8081" route=server2 max_fails=3 retry=30
BalancerMember "http://127.0.0.1:8082" route=server3 max_fails=3 retry=30
ProxySet lbmethod=bybusyness
ProxySet stickysession=JSESSIONID
ProxySet nofailover=Off
</Proxy>
3. Verify Behaviorβ
# First request: check session creation
curl -c cookies.txt -v http://example.com/app/login \
-d "username=admin&password=pass" 2>&1 | grep "JSESSIONID"
# Set-Cookie: JSESSIONID=1A2B3C4D.server2
# Subsequent requests: always go to server2
for i in {1..5}; do
curl -b cookies.txt http://example.com/app/current-server
done
Limitations of Sticky Sessionβ
Limitation 1: Session Loss on Server Failureβ
If App1 goes down, sessions pinned to App1 are lost. Even with failover (nofailover=Off), the session data is gone.
Limitation 2: Load Imbalanceβ
Heavy users pinned to App1 can overload it while App2 and App3 sit idle.
Limitation 3: Scale-out Inefficiencyβ
New servers receive almost no traffic initially because existing users are already pinned to old servers.
When to Use Sticky Sessionβ
Good fit:
- Legacy environments where session sharing infrastructure (Redis) is hard to set up
- Small deployments (2~3 servers)
- Services where session loss is acceptable (re-login on session expiry)
Avoid when:
- High availability is the top priority (finance, healthcare)
- Cloud/container environments with frequent server adds/removes
- Highly variable traffic patterns with some very heavy users
The next page covers Tomcat in-memory clustering to replicate sessions across all servers.