How to Set Up a Fallback Error Page in Nginx

nginx

I'm configuring nginx's handling of some error pages and other "default" media files (such as favicon.ico and robots.txt) at the moment and I ran into a minor problem getting things to work the way I want for certain error pages.

Basically, what I'm trying to do is serve certain files for a server under the root for that server, e.g. /var/www/someserver.com/robots.txt. If that file does not exist, I want nginx to go to the "default", i.e. /var/www/default/robots.txt. This is the basic gist of how I have that (successfully) configured:

server {
    ...
    root /var/www/someserver.com;

    location ~* ^/(robots\.txt)$ {
        error_page 404 = @default;
    }

    location @default {
        root /var/www/default;
    }
}

That works great.

I'm trying to do the same for error pages, and I'm not able to make that happen though:

server {
    ...
    root /var/www/someserver.com;

    error_page 404   /404.html;

    location ~* ^/(404\.html)$ {
        error_page 404 = @default;
    }

    location @default {
        root /var/www/default;
    }
}

Note that this "works" in the sense that if you visit someserver.com/404.html, it will first try to load /var/www/someserver.com/404.html and then fall back to /var/www/default/404.html if that's not found. However, if you visit someserver.com/blahblah, it only shows the 404 page if it's set in /var/www/someserver.com/. It does not fall back to the default directory if that file doesn't exist.

Anyhow, you can probably what I was trying to accomplish (that's why I included the first working example).

Any ideas?

Edit:

Based on Martin F's answer, this is what I ended up putting together:

# Doesn't work when error page is returned on a POST request
server {
    ...
    root /var/www/someserver.com;

    error_page  404         = @notfound;
    error_page  500 502 504 = @server_error;
    error_page  503         = @maintenance;

    location @notfound {
        try_files /404.html /../default/404.html =404;
    }

    location @server_error {
        try_files /500.html /../default/500.html =500;
    }

    location @maintenance {
        try_files /503.html /../default/503.html =503;
    }
}

This works awesome. The actual block of error_pages and locations above is in a server_defaults.conf file that gets included with every virtual host, which is why I didn't hard code the path into each location and used a relative path for the defaults.

Edit 2:

This approach has a problem. If you POST to a URL that returns an error, the POST request method gets sent with the try_files attempts. This (for me) results in a 405 Not Allowed error, because nginx is essentially trying to POST to e.g. /default/500.html instead of just getting that page.

Edit 3:

I posted a solution that works that's a lot closer to my original idea.

Best Answer

I ended up going with something a lot closer to my original idea. The key I was missing turned out to be the directive recursive_error_pages. All I really had to do was turn this "on" and my original idea worked. Here's what the relevant part of my conf looks like now:

server {
    ...

    root /var/www/someserver.com/;

    error_page 400 404      /404.html;
    error_page 500 502 504  /500.html;
    error_page 503          /503.html;

    recursive_error_pages   on;

    location ~* ^/(404\.html|500\.html|503\.html)$ {
        log_not_found off;
        error_page 404 = @default;
    }

    location @default {
        log_not_found on;
        root /var/www/default;
    }
}

I included other error types that were not part of my original question here because this is what ended up causing me difficulties with Martin F's approach, which was otherwise excellent. The log_not_found directive just ensures that I don't get 404s in my log when the error page isn't found in the original root.