When implementing sticky sessions using ip_hash
with local servers (127.0.0.1), everything works as expected. However, the same configuration fails when applied to external AWS instances. The root cause often lies in how Nginx processes the client IP when requests pass through multiple network layers.
The ip_hash
directive in Nginx uses the client's IP address to determine which backend server should handle the request. With external servers, three common issues occur:
- Nginx sees the load balancer's IP instead of the original client IP
- AWS NAT modifications alter the source IP
- Proxy protocols may strip original IP information
Here's how to modify your Nginx configuration to properly handle sticky sessions for external servers:
upstream my_app {
ip_hash;
# external servers with proxy protocol
server 111.11.11.11:3001 weight=100 max_fails=5 fail_timeout=300;
server 222.22.22.22:3002 weight=100 max_fails=5 fail_timeout=300;
keepalive 8;
}
server {
listen 80;
server_name example.com;
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location / {
proxy_pass http://my_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
If ip_hash
still doesn't work in your environment, consider these alternatives:
# Using sticky cookie (requires nginx-sticky-module)
upstream my_app {
sticky name=srv_id expires=1h domain=.example.com path=/;
server 111.11.11.11:3001;
server 222.22.22.22:3002;
}
To verify your sticky sessions are working:
- Run
curl -I http://yourdomain.com
multiple times from same client - Check your application logs to confirm requests hit the same backend
- Monitor the
X-Forwarded-For
header in your backend servers
While ip_hash
works perfectly for local upstream servers, you're encountering a common pitfall when applying it to external servers. The root cause lies in how Nginx processes client IP addresses behind proxies and across network boundaries.
The ip_hash
directive computes a hash based on the first three octets of the client IP address (IPv4) or the entire IPv6 address. For local configurations, this works because:
- Nginx sees the original client IP
- All requests are processed within the same network context
# Working local configuration example
upstream app_cluster {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
When dealing with external servers, several factors disrupt the ip_hash
mechanism:
- NAT translation between your proxy and external servers
- Possible X-Forwarded-For headers altering IP perception
- Network hops modifying the source address
Option 1: Using the sticky Directive (Nginx Plus)
For commercial Nginx Plus users, the sticky
directive provides more reliable persistence:
upstream external_app {
sticky cookie srv_id expires=1h domain=.yourdomain.com path=/;
server 111.11.11.11:3001;
server 222.22.22.22:3002;
}
Option 2: Cookie-Based Session Tracking (Open Source Nginx)
For standard Nginx, implement cookie-based persistence:
upstream external_app {
server 111.11.11.11:3001;
server 222.22.22.22:3002;
}
server {
location / {
proxy_pass http://external_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=Strict";
}
}
Option 3: Custom Hash Key with $remote_addr
Create a custom persistence mechanism using the Lua module:
upstream external_app {
hash $remote_addr consistent;
server 111.11.11.11:3001;
server 222.22.22.22:3002;
}
For WebSocket connections, add these critical proxy settings:
location /ws/ {
proxy_pass http://external_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400; # 24h timeout for WS
}
When network-level solutions prove unreliable, implement session tracking in your application:
- Generate a session cookie on first request
- Include server identification in the cookie
- Configure Nginx to read and route based on this cookie