Nginx – Controlling Nginx proxy target using a cookie

cookiemod-rewritenginxreverse-proxy

I'm trying to convert a reverse proxy using an interesting Apache mod_rewrite setup to use Nginx instead (due to external concerns we are moving from Apache to Nginx, and most everything works fine except this part).

My original setup was to read an HTTP cookie (set by some application) and depending on its value, direct the reverse proxy to different backends. It went something like this:

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]

I'm trying to achieve the same using Nginx, and my initial configuration was something like this (where "proxy_override" is the name of the cookie):

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;
}

But it didn't. I've tried to see if Nginx can read my cookie by writing the primary proxy to redirect to something based on ${cookie_proxy_override} and I can see that it reads the content fine, but the ifs seem to always fail.

My next try, according to Rikih's answer was this:

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;
}

And now I can see that the if block gets activated, but instead of proxying the request (like I thought it would do) it returns a 302 redirect to the URL specified – which is not what I'm trying to do: I need the server to transparently relay the request to the backends and pipe the response to the original client.

What am I doing wrong?

Best Answer

Similar to this answer. Nginx's idiomatic approach to this kind of problems is via map.

Basically, you define a map in http section

map $cookie_proxy_override $my_upstream {
  default default-server-or-upstream;
  ~^(?P<name>[\w-]+) $name;
}

Then you simply use $my_upstream in location section(s):

location /original-request {
  proxy_pass http://$my_upstream$uri;
}

Nginx evaluates map variables lazily, only once (per request) and when you are using them.