Pregunta

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! ;-)

¿Fue útil?

Solución

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.

Otros consejos

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

Your "root /var/www/xxxxxx;" should be outside of the "location ~ .php$" block and before "error_page 404 = /error404.php;"

If you still cannot get the desired 404 page from error404.php, I suggest you start to debug by using a standard copy of nginx.conf and then custom your error_page from there because what we suggested works. I have used the same configurations for own my server.

If it still doesn't work, it should be an upstream issue.

You also want to check your

include /etc/nginx/fastcgi.conf; include fastcgi_params;

to see if there are any rogue variables. There might be some duplicate variables there.

General rule of thumb on debugging. Reduce your variables. Start with a simple configuration and work your way up to custom it.

The correct way

error_page 404 /path/to/404.php;

I know it works because i built an application recently and this was the setup for error handling. please note i use url rewriting to remove index.php from requests for each directory. In this way you can setup your error script to log information to your database, which can be useful for difficult to debug ajax errors, among other things:

    error_page 500 /error/?t=500;
    error_page 501 /error/?t=501;
    error_page 502 /error/?t=502;
    error_page 503 /error/?t=503;
    error_page 400 /error/?t=400;
    error_page 401 /error/?t=401;
    error_page 403 /error/?t=403;
    error_page 404 /error/?t=404;
    error_page 405 /error/?t=405;
    error_page 406 /error/?t=406;
    error_page 413 /error/?t=413;
    error_page 414 /error/?t=414;
    error_page 418 /error/?t=418; # i'm a teapot
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top