http状态代码404-Nginx-将所有404错误传回PHP-FPM进行自定义错误页面处理


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

我知道这个问题已经被问了一千次了,但我找到的所有答案都不起作用(对我来说,或者通常是这些问题的原始OP)。。。所以,我会尽我所能解释这个问题,希望我们能为我和其他曾经问过的人解决这个问题。

我的Nginx配置(去掉了很多其他不相关的东西)如下:

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

我所要做的就是让Nginx捕获所有404,并通过location ~ '.php$将它们发送回PHP-FPM,以便向用户显示自定义错误页面,但我总是得到标准的Nginx错误页面。

以下URL应全部显示mydomain.co.uk/error404.php:的输出

  • mydomain.co.uk/someNonExistantFile(与任何位置块都不匹配)
  • mydomain.co.uk/someMissingFile.php(与.php文件位置块匹配,但该文件不存在)

但它们实际上显示了标准的Nginx 404页面。如果location ~ '.php$向404返回不同的错误代码(例如5xx),那么我们不想参与其中,只需首先返回FastCGI返回的内容和标头即可。

我希望这是有道理的,希望有人能帮忙。提前谢谢。

编辑:我尝试将recursive_error_pages on;添加到# Custom error pages之后的行中,但这实际上会导致所有Nginx 404 Not Found错误变为Nginx 500 Internal Server Error错误。

编辑:添加其他文件:/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;

我想我可能根本不需要这两个!;-)

我终于解决了大部分问题。感谢大家的建议,并花时间写下答案。

问题是,我们的error404.php也返回了带有404 Not Found标头的错误页面(使用header('HTTP/1.0 404 Not Found');),然后Nginx在recursive_error_pages关闭(默认)时拦截了这个错误,并且显示了自己的404页面。关闭fastcgi_intercept_errors是解决方案。如果我们从错误文件中删除header('HTTP/1.0 404 Not Found');,那么我们会得到错误,但200显然不是我们想要的。

然而,解决了访问以.php结尾的缺失页面的问题(因此它与位置块匹配,因为我们现在正在将标准的PHP-FPM响应返回到具有主体File not found.的404标头的响应。我可以使用Nate的答案来解决这个问题,但我不想在那里指定所有文件名。我会为此寻找另一个解决方案,并在获得解决方案时将其发布在此处。

编辑:更完整的解决方案:

您需要在主php位置块中拦截错误(fastcgi_intercept_errorson),然后为您的错误页面设置另一个不拦截错误的块。请参阅以下配置示例:

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

这意味着,任何返回HTTP状态代码404的PHP页面都将忽略它们自己的内容(如果有的话),而是使用/http_errors/404.php文件的内容。

我相信你所要求的是单用nginx是不可能的,除非你手动配置每个已知的.php文件。如果您可以列出已知的.php文件,那么您可以根据这些信息检测到预期的404。不是用正则表达式捕获所有的.php文件,而是指定已知的php文件:

用类似的东西替换location ~ '.php$ {}

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

然而,如果对一个所谓已知的php文件(如index.php)的请求返回404,即使你已经告诉nginx它应该存在,这也不会捕获你的机器外php-fpm生成的404。

完整的解决方案可能需要在您的nginx服务器前面有一个额外的服务器,该服务器检测到来自php-fpm/nginx的404响应,然后将另一个往返请求代理到您的后端以获取error404.php文件。您仍然会遇到error404.php文件本身也返回404的问题。我查看了Varnish,看看检测到的404是否可以向原始服务器生成第二个错误页面请求,但不幸的是,vcl_backend_error只能为404服务或重试请求。清漆或类似的东西可能有办法实现你想要的,但这并不容易。

我只能说,这就是为什么大多数人不在不同的机器上设置nginx和php-fpm的原因之一——这会引起这样的头痛。如果可能的话,您应该考虑将nginx和php-fpm放在同一台机器上,并对这些服务器进行负载平衡。无论你认为从分离它们中得到什么好处,都可能不值得额外的问题。

location ~ '.php$块需要try_files来检查文件是否存在。如果不存在,则返回404,您的error404.php将接受它。

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

您的"root/var/www/xxxxxx;"应在"location~.php$"块的外部且在"error_page 404=/error404.php;"之前

如果您仍然无法从error404.php中获得所需的404页面,我建议您使用nginx.conf的标准副本开始调试,然后从那里自定义您的error_page,因为我们建议的方法有效。我对自己的服务器使用了相同的配置。

如果它仍然不起作用,那应该是一个上游问题。

您还需要检查

包括/etc/nginx/fastcgi.conf;包括fastcgi_params;

看看是否有任何流氓变量。那里可能有一些重复的变量。

调试的一般经验法则。减少你的变量。从一个简单的配置开始,然后按照自己的方式进行自定义。

的正确方式

error_page 404 /path/to/404.php;

我知道它是有效的,因为我最近构建了一个应用程序,这是错误处理的设置。请注意,我使用url重写从每个目录的请求中删除index.php。通过这种方式,您可以设置错误脚本,将信息记录到数据库中,这对于难以调试的ajax错误非常有用:

    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