How to Fix Nginx Soft 404: When a 404 Error Page Returns 200

Looking at Google Webmaster's Search Console, I noticed a crawl error:

The target URL doesn't exist, but your server is not returning a 404 (file not found) error.

Opening the URL, I got my custom (boring) 404 error page, and sure enough the status code of the HTTP response was 200. That's weird, getting a 200 for a 404. Visitors won't care, machines do.

To investigate, I looked at the Nginx config, whose relevant part looks like so:

error_page 404 = /404.html;

location =/404.html {

location / {
        try_files $uri $uri/ $uri.html =404;

I googled, read a few StackOverflow articles, but did not find anything that seemed relevant to exactly my setup. I read the try_files docs and the error_page docs and tested various hypotheses, but came up with nothing.

Finally, I re-read one of the earlier articles and noticed the equal sign in the error_page statement. From the docs:

 Furthermore, it is possible to change the response code to another using the “=response” syntax, for example:

    error_page 404 =200 /empty.gif;

If an error response is processed by a proxied server or a FastCGI/uwsgi/SCGI server, and the server may return different response codes (e.g., 200, 302, 401 or 404), it is possible to respond with the code it returns:

    error_page 404 = /404.php;

For some reason, my error handling was configured to respond with the upstream error code. The catch is: There is no upstream. My NGINX is purely serving static files. I suppose, the upstream in this case is the static file handler, which found the referenced file /404.html, and considered everything to be 200 OK. Which was then, as the = demands, returned to the client.

The fix, of course, is to remove the =. So, changing this line:

error_page 404 = /404.html;

to this one:

error_page 404 /404.html;

was all that was needed.