Skip to main content

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.


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.

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.

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 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.