bpierce's avatar

Send error body on nginx

Moving our Laravel 5.2 application from an Apache2 server to an NGINX server. The last issue we've encountered is getting the HTTP 300+ response body to the client. It seems NGINX doesn't want to send the body our laravel application generates to the client.

My NGINX config for the application:

    server {
        listen 80 default_server;
        listen [::]:80 default_server ipv6only=on;

        root /var/www/api/current/public;
        index index.php index.html index.htm;

        # Make site accessible from http://localhost/
        server_name xxx.xxx.xxx.xxx api.mydomain.com;

        location / {
            # First attempt to serve request as file, then
            try_files $uri $uri/ /api.php?$query_string;
        }

        client_max_body_size 100m;


        #error_page 400 401 402 403 404 500 502 503 504 /index.php;

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        location ~ \.php$ {
            try_files $uri /index.php =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_intercept_errors on;
            fastcgi_buffer_size 16k;
            fastcgi_buffers 4 16k;
            include fastcgi_params;


            if ($request_method = POST) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
                add_header 'Access-Control-Expose-Headers' "X-TemporaryPassword";

            }

            if ($request_method = GET) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = QUERY) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = DELETE) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = PATCH) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = OPTIONS) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';

                add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, QUERY, PATCH';
                add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-AuthToken';

                add_header 'Content-Length' 0;
                add_header 'Content-Type' 'text/plain charset=UTF-8';
                return 204;
            }

        }

    }

We also have display_errors forced on. But still nginx doesn't seem to want to pass the body of our error messages to the clients.

0 likes
13 replies
syaiful6's avatar

300+ http response mostly used as redirection, there are no realy response body to display on web browser, and for 304 Not Modified the server doesnt need to send the body to client, because the client already have that a previously-downloaded..

you seem use php-fpm, so you should provide the configuration change in the fpm pool config associated with your web application. the configuration located:

/etc/php-fpm.d/mydomain.conf (just for this domain)
/etc/php-fpm.conf (one conf for php-fpm)

when you want to use this on production make sure this turned off, i reccommended it because everyone can see this error that maybe contain sensitive information.

bpierce's avatar

When I said 300+ I meant that all status codes (300 - 5xx) above the 200 block. So my 400s and 500s (which are particularly useful during development as Laravel sends most of its errors as 500s) are sending no body content.

We do turn off our 5xx error message bodies in production, but on some 400 and 403 errors we send json body data to the client to differentiate possible responses within these codes.

syaiful6's avatar

If you set this to on then nginx will intercept status codes that are explicitly handled by an error_page directive. And you dont have the error_page directive, set them to off instead. after change this you should restart the nginx..

willvincent's avatar

add /index.php?$query_string; to the end of your try_files for location /

bpierce's avatar

@willvincent I currently have /api.php?$query_string in the try_files clause. We have renamed index.php in the public/ folder to api.php, does this accomplish the same task?

syaiful6's avatar

sorry i mean fastcgi_intercept_errors on; set this to off.

bpierce's avatar

@syaiful6 I've updated the config to the following affected lines with unfortunately no change:

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        location ~ \.php$ {
            try_files $uri /api.php?query_string =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_buffer_size 16k;
            fastcgi_buffers 4 16k;
            fastcgi_intercept_errors off;
            include fastcgi_params;


            if ($request_method = POST) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
                add_header 'Access-Control-Expose-Headers' "X-TemporaryPassword";

            }

            if ($request_method = GET) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = QUERY) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = DELETE) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = PATCH) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Expose-Headers' "X-AuthToken";
            }

            if ($request_method = OPTIONS) {
                add_header 'Access-Control-Allow-Origin' "$http_origin";
                add_header 'Access-Control-Allow-Credentials' 'true';

                add_header 'Access-Control-Max-Age' 1728000; # cache preflight value for 20 days
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, QUERY, PATCH';
                add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-AuthToken';

                add_header 'Content-Length' 0;
                add_header 'Content-Type' 'text/plain charset=UTF-8';
                return 204;
            }

        }
syaiful6's avatar

you need to restart the nginx process.. type sudo service nginx restart and maybe the php-fpm

bpierce's avatar

@syaiful6 Did so multiple times (I've set my script to automatically cycle both nginx and php7.0-fpm each time I update the config files).

syaiful6's avatar

hmm, here the default laravel configs:

server {
    listen 80;
    listen 443 ssl;
    server_name yourdoman.com;
    root \root\to\project\";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files \$uri \$uri/ /index.php?\$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/your-app-error.log error;
    
    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
    }

    location ~ /\.ht {
        deny all;
    }
}
bpierce's avatar

One thing I've just discovered is that this only seems to be happening on CORS requests. I created a route that would send a specified error code, and everything comes through fine on that one. It'll return all expected page contents.

bpierce's avatar

Finally figured it out!

Nginx by default does not send custom headers on http error codes. I updated my nginx server to 1.8.x (was on 1.4.x). Then for my add_header config entries, I added the third argument always:

add_header 'Access-Control-Allow-Origin' "$http_origin" always;

That did the trick!

Thanks @syaiful6 and @willvincent

1 like

Please or to participate in this conversation.