Nginx maintenance page with 503 HTTP status code

Conspiracy Keanu

The other day I had to do some maintenance on a server with Nginx configured as a reverse proxy for Apache. The configuration was very simple - Nginx was in front of Apache and it served static content. Apache in the back was responsible for PHP stuff.

During the maintenance period, I wanted to redirect all requests to a custom maintenance page. Also, to avoid search engines indexing my maintenance page, I wanted that maintenance page returns proper HTTP status code - i.e. 503 service unavailable.

Quick fix

As it turned out, the solution was very simple. My original Nginx location blocks looked like this:

location / {
    index index.php index.html index.htm;
}
 
# Pass all PHP requests to Apache backend
location ~* \.php$ {
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:8080;
}

I believe that everything is self-explaining. So, to redirect all requests to a custom maintenance page with 503 HTTP status code I altered location blocks like it’s shown bellow:

location / {
    index index.php index.html index.htm;
    return 503;
}
 
# Define maintenance directory so that it doesn't return 503 HTTP code
location /maintenance {
}
 
# Define 503 error/maintenance page
error_page 503 @maintenance;
location @maintenance {
    rewrite ^(.*)$ /maintenance/maintenance.html break;
}
 
# Pass all PHP requests to Apache backend
#location ~* \.php$ {
#    proxy_set_header X-Real-IP  $remote_addr;
#    proxy_set_header Host $host;
#    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#    proxy_pass http://127.0.0.1:8080;
#}

As you can see, in first location block Nginx responds with 503 HTTP status code to all requests, which ultimately leads to redirection to maintenance page. The second (/maintenance) location block is there just to exclude maintenance directory from redirection. Proxy pass of PHP requests to Apache is disabled so that PHP requests are redirected to maintenance page too.

Making things more complicated

Although this solution works just fine I needed to allow myself access to the website during maintenance period. So I made some more changes to location blocks and ended up with the following:

location / {
    index index.php index.html index.htm;
 
    # Check user's IP address, return 503 if they don't match
    if ($remote_addr != "XXX.XXX.XXX.XXX") {
        return 503;
    }
}
 
# Define maintenance directory so that it doesn't return 503 HTTP code
location /maintenance {
}
 
# Define 503 error/maintenance page
error_page 503 @maintenance;
location @maintenance {
    rewrite ^(.*)$ /maintenance/maintenance.html break;
}
 
# Pass all PHP requests to Apache backend
location ~* \.php$ {
    # Checking user's IP address
    if ($remote_addr != "XXX.XXX.XXX.XXX") {
        return 503;
    }
 
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:8080;
}

Basically, with if directive, user’s IP address is compared with the one defined in configuration. If IP addresses match, the website is displayed normally. However, if they don’t match, the user is redirected to maintenance page.

Beware how you use the if directive. In some cases, you can experience segfaults!