When migrating from Apache to Nginx, one common pain point is replicating cookie-based routing behavior. In Apache, we could easily use mod_rewrite with RewriteCond to check cookie values and route requests accordingly. However, Nginx requires a different approach.
The first approach using $cookie_proxy_override
often fails because:
location /original-request {
if ($cookie_proxy_override = "proxy-target-A") {
# This may not work as expected
rewrite . http://backend-a/some-application;
break;
}
# ... other conditions
}
The rewrite
directive with an absolute URL causes a 302 redirect rather than internal proxy routing. This is fundamentally different behavior from Apache's [P] flag.
Here's the correct way to implement cookie-based routing in Nginx:
map $cookie_proxy_override $backend {
default "http://primary-backend/original-application";
"proxy-target-A" "http://backend-a/some-application";
"proxy-target-B" "http://backend-b/another-application";
}
server {
# ... other server config
location /original-request {
proxy_pass $backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
1. The map
directive provides clean, maintainable routing logic
2. No conditional blocks that can cause unexpected behavior
3. Pure proxy behavior without unwanted redirects
4. All proxy-related headers are properly preserved
For more complex cookie matching (regex, partial matches), use:
map $http_cookie $backend {
default "http://primary-backend/original-application";
~*proxy-target-A "http://backend-a/some-application";
~*proxy-target-B "http://backend-b/another-application";
}
1. Verify cookie values reach Nginx with:
add_header X-Debug-Cookie $cookie_proxy_override;
2. Check which backend was selected:
add_header X-Backend-Selected $backend;
3. Use curl to test:
curl -v --cookie "proxy_override=proxy-target-A" http://yourserver/original-request
The map directive is evaluated during the rewrite phase, making it very efficient. For high-traffic sites, consider:
map_hash_bucket_size 128; # For longer cookie values
map_hash_max_size 2048; # For many routing rules
For extremely dynamic routing needs, OpenResty's Lua integration provides more flexibility:
location /original-request {
access_by_lua_block {
local cookie = ngx.var.cookie_proxy_override
if cookie == "proxy-target-A" then
ngx.var.backend = "http://backend-a/some-application"
elseif cookie == "proxy-target-B" then
ngx.var.backend = "http://backend-b/another-application"
end
}
proxy_pass $backend;
}
When transitioning from Apache to Nginx, one of the trickiest parts is converting complex rewrite rules that rely on HTTP cookies for backend routing. The original Apache setup worked perfectly:
RewriteCond %{HTTP_COOKIE} proxy-target-A
RewriteRule ^/original-request/ http://backend-a/some-application [P,QSA]
RewriteCond %{HTTP_COOKIE} proxy-target-B
RewriteRule ^/original-request http://backend-b/another-application [P,QSA]
RewriteRule ^/original-request http://primary-backend/original-application [P,QSA]
The first Nginx attempt using $cookie_proxy_override
didn't work as expected. While Nginx could read the cookie value, the conditional blocks never triggered. Here's what didn't work:
location /original-request {
if ($cookie_proxy_override = "proxy-target-A") {
rewrite . http://backend-a/some-application;
break;
}
if ($cookie_proxy_override = "proxy-target-B") {
rewrite . http://backend-b/another-application;
break;
}
proxy_pass http://primary-backend/original-application;
}
Switching to $http_cookie
with regex matching solved the condition triggering, but introduced a new issue - unwanted 302 redirects:
location /original-request {
if ($http_cookie ~ "proxy-target-A") {
rewrite . http://backend-a/some-application;
break;
}
if ($http_cookie ~ "proxy-target-B") {
rewrite . http://backend-b/another-application;
break;
}
proxy_pass http://primary-backend/original-application;
}
The key realization was that rewrite
causes redirects, while we want internal proxying. Here's the working configuration:
location /original-request {
set $backend "http://primary-backend/original-application";
if ($http_cookie ~ "proxy-target-A") {
set $backend "http://backend-a/some-application";
}
if ($http_cookie ~ "proxy-target-B") {
set $backend "http://backend-b/another-application";
}
proxy_pass $backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
For more complex scenarios, consider these enhancements:
map $cookie_proxy_override $backend {
"proxy-target-A" "http://backend-a/some-application";
"proxy-target-B" "http://backend-b/another-application";
default "http://primary-backend/original-application";
}
server {
location /original-request {
proxy_pass $backend;
# Additional proxy settings...
}
}
To verify your configuration:
# Check cookie value extraction
add_header X-Debug-Cookie $cookie_proxy_override always;
# Log backend selection
log_format backend_log '$remote_addr - $cookie_proxy_override - "$request" - $backend';
access_log /var/log/nginx/backend.log backend_log;
Remember that Nginx processes if
directives with some limitations. The map
directive often provides cleaner solutions for complex routing logic.