Cookie-based instance routing with Nginx
I recently found myself in need of a way to serve slightly different instances of a web application depending on the user visiting the site. I wanted all of them to be served on the same address so that it would be as transparent as possible to the user. The solution I came up with was to use a URL parameter and a cookie to route the user to the correct instance.
The problem
You might want to do this for a lot of different reasons. The most common one is probably to serve different languages or themes. Another reason could be to test alternative versions of a feature (A/B testing). In my case, I wanted to serve different versions of my personal website to different potential employers depending on the job offer I was applying for. The real question was how to do this without creating a new subdomain or a subpath for each instance.
The solution
Let’s say you have two different versions of your website, version A and version
B. You want to serve version A by default and version B when the user visits
https://yoursite.com/?version=B
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Version A</title>
</head>
<body>
<h1>Welcome to Version A</h1>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Version B</title>
</head>
<body>
<h1>Welcome to Version B</h1>
</body>
</html>
They are both running on the same machine on different ports, and you are using Nginx as a reverse proxy. Here is how you can do it:
upstream versionA {
server 127.0.0.1:4000;
}
upstream versionB {
server 127.0.0.1:4001;
}
# Map to determine backend based on the version
map $backend_version $upstream {
A versionA;
B versionB;
default versionA;
}
server {
listen 80;
server_name yoursite.com;
location / {
# Default backend version
set $backend_version A;
# Check if the "version" parameter is in the URL
if ($arg_version) {
set $backend_version $arg_version;
add_header Set-Cookie "version=$arg_version; Path=/";
# Redirect to remove the URL parameter
return 302 $uri;
}
# Check if the "version" cookie exists and override backend version
if ($http_cookie ~* "version=(\d)") {
set $backend_version $1;
}
# If the cookie does not exist, set it to the backend version
if ($http_cookie !~* "version=(\d)") {
add_header Set-Cookie "version=$backend_version; Path=/";
}
# Route based on the determined backend version using the map
proxy_pass http://$upstream;
}
}
The ports, version names and domain are just examples. You should replace them with your actual configuration.
This configuration should be placed in the nginx /etc/nginx/sites-available
directory and symlinked to /etc/nginx/sites-enabled
. It will route the user to
the correct instance based on the version
URL parameter. If the parameter is
not present, it will check if the version
cookie is set. If it is not, it will
set it to the default version. The cookie will be set for the root path of the
site so that it is available on all pages.
Conclusion
When the user visits https://yoursite.com/?version=B
, they will be redirected
to https://yoursite.com/
and served version B. The cookie will be set, and
they will continue to see version B until they clear their cookies. The routing
is both transparent and persistent.
This solution is simple and can be easily extended to serve more versions or different types of content. It is also a good way to test new features or versions of your website without affecting all users. It is a powerful tool to have in your toolbox.