When a client of yours is moving a web site between two servers, of course the best option is to plan some time during which modification is prohibited and everything is moved as fast as possible. This ensures that you do only one operation and that everything follows.

However, in some cases, the client can desire to move more slowly in order to rearrange the new site or to filter out content prior to making it available on the new server, or there might be some transition issues that she doesn't want to revisit. In such cases, the web site will need to be available from two locations at once for some time.

In that case, you can help out by reverse proxying to the new server.

disclaimer: yeah, I know this stuff is kinda ugly, but it lets your client have the control over what is happening, and in some cases1 this'll make them really happy.

Add a subdomain

First, add a subdomain, either to the client's domain or to your own. For the purpose of examples, let's say the domain that needs to transition slowly is example.com. We'll add rproxy.example.com and point it to the new server's IP.

Do the reverse proxy dance

This is no magic trick. And since we're talking about Nginx here, it's very easy to setup.

Add a server block to your Nginx config (in debian, it could be in the file under /etc/nginx/sites-available in which you configured the server block for the regular site -- e.g. example.com). Your new server block should look somewhat like the following:

server {
    server_name rproxy.example.com;
    root /var/www/example.com;  ## whatever..

    location / {
        proxy_pass         http://127.0.0.1/;
        proxy_redirect     off;

        proxy_set_header   Host             example.com;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

Notice that you're actually forcing the value of the Host header to a different value than what you're accessing (the usual technique with reverse proxying is to set that header to $host, by which you're hopefully accessing the site. So in more practical terms, the domain name by which you're accessing the reverse proxy is actually the one that should be the web site's "official" domain name. Here, that's not what we want to do, we want to setup a temporary alternate domain name.)

This'll get your server to hand you the content. But something's off: every link inside the content will point back to the original domain name, on the server that is in production. So, if your client wants to change anything other than the html, they won't see the results. Also, the client won't be able to directly navigate the site since all URLs will point back to the old server.

Search and replace (filter)

Enter the HttpSubModule Nginx module. Hopefully your Nginx instance will have been compiled with support for it. In debian the packaged binary does come with it.

To ensure that every link (anchors, css, js, images, other media files) is points to the new server, we'll have to make Nginx replace all occurrences of the old domain name into the temporary subdomain. Add those lines into the server block:

        sub_filter         example.com rproxy.example.com;
        sub_filter_once    off;

Now you can see the result that really comes from the new server, plus you should be able to navigate the new site without being thrown off on the old server.

It's not all advantages, though

There is a drawback with this method, though: you can only have one sub_filter directive per location block inside a server block. So if the site involves multiple domains or subdomains at once, you'll only be able to modify one of them (most likely the one on which the site currently lives). This is the reason for the disclaimer above, it is very ugly and shouldn't be used as a permanent solution.

Let's recapitulate

Here's the full server block, for a better overview of the result:

server {
    server_name rproxy.example.com;
    root /var/www/example.com;  ## whatever..

    location / {
        proxy_pass         http://127.0.0.1/;
        proxy_redirect     off;

        proxy_set_header   Host             example.com;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

        sub_filter         example.com rproxy.example.com;
        sub_filter_once    off;
    }
}