0

This is using nginx 1.6.3 and PHP 7.0.7 via PHP-FPM in CentOS 7.2.

I have run many sites using LAMP and have been trying to switch to LEMP, but a sticking point that keeps coming up is that my page handler keeps showing 404 errors in the status, even though I have set a different status in PHP. It is as if nginx is completely ignoring the headers sent from PHP for the 404 error page.

/etc/nginx/nginx.conf looks like:

user web web;
worker_processes auto;
error_log /var/web/Logs/WebServer/nginx-error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/web/Logs/WebServer/nginx-access.log  main;

    fastcgi_buffers 8 1024k;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;
}

Each domain's configuration looks like:

server {
    listen       80;
    server_name  www.something.com;

    root /var/web/www.something.com/;
    index index.php index.html;

    error_page 404 /PageHandler;

    location / {
        try_files $uri $uri/ /PageHandler =404;
        location ~ \.php$ {
            try_files $uri =404;
            fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
            fastcgi_index index.php;
            include fastcgi.conf;
        }
        location /PageHandler {
            try_files /PageHandler.php =500;
            fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
            include fastcgi.conf;
            fastcgi_param REDIRECT_STATUS 404;
        }
    }
}

The very simple PHP script is (and yes, I know the headers are redundant, and still it does nothing):

<?php
  http_response_code(200);
  header("HTTP/1.1 200 OK");
  header("Status: 200", true, 200);
?>
Test <?= $_SERVER["REQUEST_URI"] ?>, code <?= $_SERVER["REDIRECT_STATUS"] ?>

I have searched fruitlessly for hours on how to fix this. I have tried at least a hundred different .conf formats, and none of them work. What I have above at least sets REDIRECT_STATUS to 404, but I have found no way to be able to return a 200 status code if a page was found. I cannot just have nginx always return a 200, because it may actually be a genuine 404 since the actual script tests the current URL in a database.

How do I get nginx to obey PHP's HTTP status header?

Mark Ormston
  • 111
  • 1
  • 6

3 Answers3

2

To me these nested location blocks look troublesome, and they are not needed. Try this:

server {
    listen       80;
    server_name  www.something.com;

    root /var/web/www.something.com/;
    index index.php index.html;

    try_files $uri $uri/ /PageHandler.php =404;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }
}
Tero Kilkanen
  • 36,796
  • 3
  • 41
  • 63
  • I tried a variation of that. Both that and what you have here causes nginx to send /PageHandler.php with a MIME type of application/octet-stream so then the user downloads it. – Mark Ormston Aug 02 '16 at 16:57
  • @MarkOrmston I suspect you are omitting some details which you think aren't important, but actually are. Please review everything and add any missing details. – Michael Hampton Aug 02 '16 at 18:57
  • @Michael Hampton♦ The only other configuration I could think to add was nginx.conf which I thought was very standard (aside from fixing the user and changing where the logs go). I have added that to the question if it makes any difference. – Mark Ormston Aug 02 '16 at 19:39
  • Did you try exactly my version or one that uses nested `location` blocks in your question? Since the download behavior could very well be caused by the nested locations.. – Tero Kilkanen Aug 02 '16 at 22:21
  • I tried the exact version you have above. It tried to download /PageHandler.php - It seems to be a behavior issue with try_files. I have finally found a solution that merges data from several sources. Will write that up shortly. – Mark Ormston Aug 02 '16 at 22:35
0

The obvious thing to do is to remove this:

    error_page 404 /PageHandler;

It's completely redundant, since any path that isn't found as a static file will be directed there by your first try_files anyway. It's only necessary or useful to use error_page to serve static error pages.

Michael Hampton
  • 244,070
  • 43
  • 506
  • 972
  • If I comment that out, the /PageHandler is ignored and I get nginx's 404 error page instead. Probably because /PageHandler doesn't actually exist so it is skipped with try_files I would assume. I tried creating a blank /PageHandler file and that gets returned as an application/octet-stream so even if I tried to use PHP to handle it, the user would just get the source... – Mark Ormston Aug 02 '16 at 17:05
0

By combining the lessons I have learned through many failures, the two other answers I received (thank you Michael Hampton and Tero Kilkanen for pointing me in the right direction), and a hunch to look further into try_files, I found out that the problem was centered around how try_files is working in my installation. There are far more details here: How does try_files work? and here: http://nginx.org/en/docs/http/ngx_http_core_module.html#try_files

The final working server configuration is:

server {
    listen       80;
    server_name  www.something.com;

    root /var/web/www.something.com/;
    index index.php index.html;

    try_files $uri $uri/ @PageHandler;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }

    location @PageHandler {
        try_files /PageHandler.php =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        include fastcgi.conf;
        fastcgi_param REDIRECT_STATUS 404;
    }
}

As you can see, I am using a named location of @PageHandler and tying in that. It is using a default HTTP status of 200 instead of 404, but since I can control that from PHP, I set it to 404 explicitly when necessary.

Using any one of the following PHP commands changes the status as seen by my browser (all three are not necessary):

http_response_code(404);
header("HTTP/1.1 404 Not Found");
header("Status: 404", true, 404);
Mark Ormston
  • 111
  • 1
  • 6