Michael Trojanek (relativkreativ) — Bootstrapper and creator of things

This article was published on June 22nd 2014 and takes about 3 minutes to read.

Use it with caution — it is probably still valid, but it has not been updated for over a year.

Graceful error pages with nginx

With a state-of-the art website or web application, it just makes sense for your error pages to be polished as well. No-one likes staring at a naked default page when something goes wrong.

Personally, I'm not a big fan of release-based deployment.

From a Rails developer's perspective, shuffling symlinks when deploying is often more pain than gain. With Unicorn and the paid version of Passenger both supporting zero-downtime-deploys, deploying a new release is basically just fetching the latest codebase, compile assets and tell your application server to restart.

Databases need to be migrated in either scenario and unless you use a really complex deployment strategy, these migrations will cause downtime anyway.

So instead of manually switching to your maintenance page, why not let your favourite webserver do it for you?

A basic environment

Assuming you have a Unicorn master process running, you probably have some kind of upstream definition in your nginx configuration:

upstream unicorn_relativkreativ {
  server unix:/home/app/relativkreativ.at/shared/tmp/sockets/unicorn.sock fail_timeout=0;
}

In a server block, you forward requests to this backend:

server {
  …

  location / {
    try_files $uri @app;
  }

  location @app {
    …
    proxy_pass http://unicorn_relativkreativ;
  }
}

However, when your Unicorns are not running, nginx will serve the default error page for 502 Bad Gateway - not very sexy.

Implement a custom error page

Using nginx's error_page-directive, you can tell the webserver which page to display in case of a specific HTTP error. This is pretty straight forward (so I will not go into detail) but it has one major drawback:

The error page is just one HTML-file, so while you can embed CSS, there is no way to use images or custom fonts.

Time for a more elaborated approach.

Using an internal location

Instead of telling nginx to serve a single file in case of error, you can redirect to an internal location:

server {
  …

  error_page 502 = @maintenance;

  location @maintenance {
    root /home/app/relativkreativ.at-maintenance;

    try_files $uri /index.html =502;
  }
}

Internal locations (starting with an @) cannot be requested from the outside so you do not risk some visitor stumbling upon your error page accidentally.

Please note the = following the error code: It makes nginx leave the error code unchanged, so it renders the error page with status code 502 Bad Gateway - you would not want Google to index your error page because your webserver masked it as a redirect.

Now, if you want to display your application's maintenance page, all you have to do is shut down your application server. And (opposed to the manual symlink-approach) it adds the benefit of switching to your maintenance page automatically if your application server goes down unexpectedly.

If you want to serve a custom error page when your Rails application errors out with a 500 Internal Server Error (the famous "Something went wrong …"-page) then the procedure is nearly the same. You just have to take a look at nginx's proxy_intercept_errors-directive - without it, nginx will serve your backend's error page. Of course you won't need that because we are all testing our code thoroughly before deploying, aren't we?

Expand your DevOps skills!

Join hundreds of Rails developers and operators on my email list and get my ebook Build Your Own Rails Server as a free welcome gift.

No spam — guaranteed. You can leave at any time.