chr4

Devops. I've never asked for this.

Nested if workaround for Nginx to allow a specific ip address access to a disabled site

When doing maintenance on a web application, you probably have a custom 503 site, showing your customers that the servers are currently lying on the operating table.

At the dynamic ridesharing service flinc, we touch a certian file on our reverse proxies (e.g. using capistrano deploy:web:disable) when maintenance begins. Nginx then serves a static “we’ve disabled the site for maintenance” site, instead of the actual content.

But wouldn’t it be nice to test your web application before going live for your customers? It sure would. Unfortunately, this is not as simple as a task as you might think, because you cannot nest if directives in an Nginx location and if is evil.

Because of hose limitations, we need another creative approach. As try_files is also forbidden in if directives and only return and rewrite are safe commands to use let’s abuse the unused HTTP status code 418 to work around this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location / {
    chunked_transfer_encoding off;
    error_page 418 = @try_files;
    error_page 503 = @maintenance;

    # allow out office ip even when in maintenance mode
    if ($remote_addr = 10.11.12.13) {
        return 418;
    }

    # we are in maintenance mode when this file is present
    if (-f $document_root/maintenance.html) {
        return 503;
    }

    return 418;
}

Basically, as long as the maintenance.html is not present or we are sitting in our office, we return the 418 status code. For customers, we’re serving 503 in case the site is disabled.

So let’s define the locations for normal operation (try serving the static files directly, and redirect all other requests to our upstream)

1
2
3
4
5
6
7
location @try_files {
    try_files $uri @proxy;
}

location @proxy {
    proxy_pass https://flinc;
}

And finally, our maintenance site. We’re serving maintenance.html instead of the requested site. As POST requests to static websites are not allowed and therefore all our disabled forms will return a 405 instead of the wanted 503, let’s correct those as well.

1
2
3
4
5
6
7
8
9
location @maintenance {
    # return 503 for non-GET requests
    # (returns 405 by default, because POST/etc to a static website is not allowed)
    if ($request_method != GET) {
        return 503;
    }

    try_files $uri /maintenance.html;
}

The last location directive makes sure that the static assets required for the maintenance page (images, stylesheets, etc) stored in /maintenance are served normally.

1
2
# don't hand out 503 headers when accessing maintenance assets
location /maintenance {}