Nginx - Pass all 404 errors back to PHP-FPM for custom error page processing Nginx - Pass all 404 errors back to PHP-FPM for custom error page processing nginx nginx

Nginx - Pass all 404 errors back to PHP-FPM for custom error page processing


I've finally worked most of it out. Thanks everyone for your tips and putting the time in to write answer.

The issue is that our error404.php was returning our error page with an 404 Not Found header too (using header('HTTP/1.0 404 Not Found');) and Nginx was then intercepting this error as recursive_error_pages is off (default) and it was showing it's own 404 page. Switching off fastcgi_intercept_errors is the solution. If we remove the header('HTTP/1.0 404 Not Found'); from the error file then we'd get the error but with a 200 which is obviously not what we want.

This does not, however, solve the problem of accessing a missing page which ends with .php (so it matches the location block as we are now getting back the standard PHP-FPM response to those of a 404 header with the body File not found.. I could use Nate's answer to get around this, but I'd rather not need to specify all the file names there. I'll look for another solution for this and post it here when I get one.

EDIT: A more full solution:

You need to intercept errors in your main php location block (fastcgi_intercept_errors is on) and then have another block for your error pages where you don't intercept them. See this config example:

    server {            listen 80;            listen 443 ssl;            server_name mydomain.co.uk;            ssl_certificate /xxxxxxx.crt;            ssl_certificate_key /xxxxxxx.key;            # Custom error pages            recursive_error_pages off;            error_page 404 = /http_errors/404.php;            # error_page 500 501 502 503 504 = /error5xx.php; # Not sure about this yet!            # Any simple .php page            location ~ \.php$ {                    root /var/www/xxxxx;                    include /etc/nginx/fastcgi.conf;                    fastcgi_pass phpfastcgiservers;                    include fastcgi_params;                    fastcgi_intercept_errors on;            }            # Handling error pages            location ^~ /http_errors/ {                    internal;                    root /var/www/xxxxx;                    include /etc/nginx/fastcgi.conf;                    fastcgi_pass phpfastcgiservers;                    include fastcgi_params;                    fastcgi_intercept_errors off;            }}

This will mean that any of your PHP pages which return an HTTP status code of 404 will have their own content (if any) ignored and the content of your /http_errors/404.php file will be used instead.


I believe what you are asking for is not possible with nginx alone - unless you configure each and every known .php file manually. If you can list the known .php files, you can detect an expected 404 based on this information. Instead of capturing all .php files with a regular expression, you specify known php files:

Replace location ~ \.php$ {} with something like:

location ~ ^/(index|foo|bar|admin/index).php$ {}

This will however not capture a 404 generated by your off-machine php-fpm if a request for a supposedly-known php file like index.php returns 404 even though you've told nginx it should exist.

A full solution would likely require an additional server in front of your nginx server that detects a 404 response from php-fpm/nginx, which would then proxy another round-trip request to your backend for the error404.php file. You'd still run into the problem of your error404.php file also returning a 404 itself. I looked into Varnish to see if a detected 404 could generate a second request to the origin server for an error page, but unfortunately the vcl_backend_error can only serve the 404 or retry the request. Varnish - or something similar - might have some way to accomplish what you want, but it won't be easy.

All I can say is this is one reason why most people don't set up nginx and php-fpm on different machines - it causes headaches like this. If possible, you should really consider keeping nginx and php-fpm on the same machine and load balancing those servers. Whatever benefit you believe you're getting from separating them is probably not worth the extra problems.


The location ~ \.php$ block requires a try_files to check for file existence. If doesn't exist, return 404, and your error404.php will take it.

http {    # Config from here removed    server {        listen 80;        listen 443 ssl;        server_name mydomain.co.uk;        ssl_certificate /xxxxxxx.crt;        ssl_certificate_key /xxxxxxx.key;        root /var/www/xxxxxx;        # Custom error pages        error_page 404 = /error404.php;        # Any simple .php page        location ~ \.php$ {                try_files $uri =404;                include /etc/nginx/fastcgi.conf;                fastcgi_pass phpfastcgiservers;                include fastcgi_params;                fastcgi_intercept_errors on;        }        # Lots more config and re-write rules here removed    }    upstream phpfastcgiservers {        server xxxxx1:9001;        server xxxxx2:9001;        server xxxxx3:9001;        fair;    }}