Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I know this has been asked a thousand times, but all of the answers I'm found simply don't work (for me or usually the original OP of those questions)... So, I'll try to explain the problem as best as I possibly can and hopefully we can get it working for me and for others who have asked before.

My Nginx config (with lots of other irrelevant stuff removed) is as follows:

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;

            # Custom error pages
            root /var/www/viovet_frontend;
            error_page 404 = /error404.php;

            # Any simple .php page
            location ~ \.php$ {
                    root /var/www/xxxxxx;
                    #index index.php index.html;

                    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;
    }
}

All I'm trying to do is get Nginx to catch all 404s and send them back to PHP-FPM via location ~ \.php$ to have a custom error page shown to the user, but I always get the standard Nginx error page.

The following urls should all show the output of mydomain.co.uk/error404.php:

  • mydomain.co.uk/someNonExistantFile (didn't match any location blocks)
  • mydomain.co.uk/someMissingFile.php (matched the .php file location block but the file doesn't exist)

But they actually show the standard Nginx 404 page. If the location ~ \.php$ returns a different error code to 404 (e.g 5xx) then we don't want to get involved, just return the content and headers that FastCGI returned in the first place.

I hope that makes sense and that someone can help. Thank you in advance.

EDIT: I have tried adding recursive_error_pages on; to the line after # Custom error pages but this actually causes all Nginx 404 Not Found errors to become Nginx 500 Internal Server Error errors.

EDIT: Adding other files: /etc/nginx/fastcgi.conf

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

fastcgi_params

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

I guess I probably don't need both of these anyway! ;-)

like image 732
Luke Cousins Avatar asked Apr 16 '14 10:04

Luke Cousins


1 Answers

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 its 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.

like image 127
Luke Cousins Avatar answered Sep 16 '22 13:09

Luke Cousins