blog.thms.uk

Smart Routing with Nginx: Serving Different Sites Based on IP and Cookies

Recently, I had to replace an old static site with a new WordPress-powered blog. As part of that deployment, I needed staff to access the WordPress blog to seed it with valid posts and approve the design, while normal visitors continued to see the old static site until the blog was ready. This way, visitors wouldn’t encounter an incomplete “under construction” site.

You could achieve this by setting up the new blog under a subdomain and then switching it back to the production domain later. However, changing the domain with WordPress can be tricky, so I wanted to avoid that.

Thankfully, Nginx makes this quite straightforward.

Using Nginx to Serve a Different Site Based on Visitor’s IP Address

My initial plan was to show the new site based on the office IP address. In our example, we’ll assume that the old site is in the directory /var/www/old-site and the new one is in /var/www/new-site.

We can use a simple Nginx map to set the correct root directory for the site like this:

map $remote_addr $site_root {
    default old-site;
    # office 1
    172.217.16.238 new-site; 
    # office 2
    163.70.151.35 new-site;
}

This map will set a variable called $site_root based on the visitor’s IP ($remote_addr), assigning it the value new-site if accessed from the office (or other specified IP addresses), and old-site otherwise.

We can then use this variable in the server block to set the root of our site:

server {
    server_name example.com
    listen 443 ssl http2;

    root /var/www/$site_root;

    # everything else goes here.
}

Pretty simple, but there was a catch:

Some contractors worked outside the office with dynamic IPs that changed too frequently to update the map above. To address this, we introduced a second map block. Since we want to combine this cookie method with the IP-based method, we won’t set the $site_root variable directly inside this map statement:

map $cookie_new_site $new_site_cookie {
    default 0;
    1 1;
}

This map looks for a cookie called new_site (in Nginx, a cookie value is accessible by prefixing the cookie name with cookie_). When that cookie is present and has the value 1, we’ll set the variable $new_site_cookie to 1, otherwise 0.

Next, we rework the first map slightly so that instead of setting the $site_root directly, it sets a variable ($new_site_ip) to either 1 or 0:

map $remote_addr $new_site_ip {
    default 0;
    # office 1
    172.217.16.238 1; 
    # office 2
    163.70.151.35 1;
}

Finally, we use Nginx’s ability to evaluate variables with regular expressions in a third map block:

map $new_site_ip$new_site_cookie $site_root {
    default old-site;
    "~.*1.*" new-site;
}

This evaluates the concatenated variable $new_site_ip$new_site_cookie, which can be 10, 01, 11, or 00, depending on whether the new_site cookie is present, the request comes from the office, or both.

If this string contains a 1 ("~.*1.*"), the $site_root variable is set to new-site; otherwise, it’s set to old-site. As before, we can then use this variable in the server block:

server {
    server_name example.com
    listen 443 ssl http2;

    root /var/www/$site_root;

    # everything else goes here.
}

The easiest way to set this would be to navigate to the site, execute the following in the console, and refresh:

document.cookie = 'new_site=1;max-age=31536000;path=/'

This method might not be suitable for non-technical staff, but fortunately, our editorial staff worked from static IP addresses that we could list in the $new_site_ip map block.

Conclusion

Now, staff and contractors could see the new WordPress blog on its proper domain, either by visiting the site from specified static IPs or by setting a cookie in their browser. This approach removed the need to set up a temporary domain or subdomain and perform extensive search and replace operations in the WordPress database to go live.