title: Nginx - Pass all 404 errors back to PHP-FPM for custom error page processing
body: <p>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.</p>
  
  <p>My Nginx config (with lots of other irrelevant stuff removed) is as follows:</p>
  
  <pre><code>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;
      }
  }
  </code></pre>
  
  <p>All I'm trying to do is get Nginx to catch all 404s and send them back to PHP-FPM via <code>location ~ \.php$</code> to have a custom error page shown to the user, but I always get the standard Nginx error page.</p>
  
  <p>The following urls should all show the output of <code>mydomain.co.uk/error404.php</code>:</p>
  
  <ul>
  <li>mydomain.co.uk/someNonExistantFile (didn't match any location blocks)</li>
  <li>mydomain.co.uk/someMissingFile.php (matched the .php file location block but the file doesn't exist)</li>
  </ul>
  
  <p>But they actually show the standard Nginx 404 page. If the <code>location ~ \.php$</code> 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.</p>
  
  <p>I hope that makes sense and that someone can help. Thank you in advance.</p>
  
  <p><strong>EDIT:</strong> I have tried adding <code>recursive_error_pages on;</code> to the line after <code># Custom error pages</code> but this actually causes all Nginx <code>404 Not Found</code> errors to become Nginx <code>500 Internal Server Error</code> errors.</p>
  
  <p><strong>EDIT:</strong> Adding other files:
  <strong>/etc/nginx/fastcgi.conf</strong></p>
  
  <pre><code>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;
  </code></pre>
  
  <p><strong>fastcgi_params</strong></p>
  
  <pre><code>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;
  </code></pre>
  
  <p>I guess I probably don't need both of these anyway! ;-)</p>