Nginx / PHP-FPM random post body content in response - php

I have a problem with my NginX / php-fpm / Laravel stack.
The content of post body randomly appear in the response, producing invalid JSON.
Request example :
POST / HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: host
Connection: close
User-Agent: Paw/3.2.1 (Macintosh; OS X/11.2.1) GCDHTTPRequest
Content-Length: 9
test=test
The Response :
HTTP/1.1 500 Internal Server Error
Server: nginx/1.14.0 (Ubuntu)
Content-Type: application/json
Transfer-Encoding: chunked
Connection: close
Cache-Control: no-cache, private
Date: Tue, 16 Feb 2021 07:46:25 GMT
test=test{"error_message":"The POST method is not supported for this route."}
The error is normal (there is no POST route for this uri). But as you can see, the post body appear in the response. (randomly, 1 out of 5 times the request works fine)
It doesn't come from the Laravel stack, I tested it by setting a var_dump / die on top of the index.php (before Laravel loads) and the same problem happens.
Any insights ?
Thank you.
Ah maybe my NginX config :
server {
access_log /var/log/nginx/access;
error_log /var/log/nginx/error;
root /root/devs/peps/www/public;
index index.php index.html index.htm index.nginx-debian.html;
server_name server_name;
underscores_in_headers on;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass 127.0.0.1:9002;
}
listen [::]:443 ssl;
listen 443 ssl;
ssl_certificate [path to cert]
ssl_certificate_key [path to privkey]
}

I had the same issue and resolved it by disabling nginx caching:
location / {
proxy_no_cache 1;
proxy_cache_bypass 1;
#.......
}

Related

PHP-FPM Always Returns 200 Regardless Of NGINX Status Code

I have a PHP-based error page configuration with NGINX and PHP-FPM. However, when I request, for example, example.com/nothing (non-existent page), PHP-FPM returns a 200 status code, and not the correct 404 status code that NGINX returns. This also happens with other errors (ex: example.com/assets returning 200 with PHP-FPM when the status is 403 with NGINX). Essentially what I want PHP-FPM to do is mirror the status code shown by NGINX (override the 200 status code with the one shown by NGINX), so my error pages show the correct information. I am aware that you can change the status code by specifying it when using http_response_code();, but I would rather have the server do this without having me hard-code the proper status code.
Error page: <? echo http_response_code(); ?>
NGINX error page config:
set $errorDocs "/var/www/GLOBAL_RESOURCES/error";
recursive_error_pages on;
location ^~ $errorDocs {
internal;
alias $errorDocs;
}
#Resolve error asset location 404s
location /errorAssets {
root $errorDocs;
}
error_page 404 /404.php;
location = /404.php {
root $errorDocs;
include /etc/nginx/xenon-conf/headers/fpm-params.conf;
}
PHP-FPM settings:
include /etc/nginx/fastcgi_params;
include /etc/nginx/fastcgi.conf;
fastcgi_intercept_errors on;
proxy_intercept_errors on;
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
Fast-CGI Config:
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 REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
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;
Website Config:
server {
listen 80;
server_name example.com www.example.com;
access_log /var/log/nginx/example.com.access.log;
include /etc/nginx/xenon-conf/headers/php-fpm-enable.conf;
include /etc/nginx/xenon-conf/headers/master-failover.conf;
set $webRoot "/var/www/example.com";
root $webRoot;
}
NGINX Config:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
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/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
### CUSTOM HTTP SERVER MASS IMPORTS ###
include /etc/nginx/xenon-conf/websites/*.web;
include /etc/nginx/xenon-conf/mapping/*.map;
}
### CUSTOM GENERIC STREAM MASS IMPORTS ###
include /etc/nginx/xenon-conf/stream/*.conf;
Thanks in advance!
If nginx is detecting output from the FastCGI upstream it will
consider it as a valid response, even if the upstream (in this case,
php-fpm) triggered an error.
Disabling display_errors in the PHP-FPM pool fixes this.
php_admin_value[display_errors] = Off It prevents the PHP-script from
showing error output to the screen, which in turn causes nginx to
correctly throw an HTTP 500 Internal Server Error.
$ curl -i localhost:8080/test.php?time=`date +%s` HTTP/1.1 500
Internal Server Error Server: nginx ...
(no output is shown, empty response) You can still log all errors to a
file, with the error_log directive.
php_admin_value[error_log] = /var/log/php-fpm/error.log
php_admin_flag[log_errors] = on
-- Source
In order to pass HTTP status codes from nginx to PHP-FPM, you also need to put the following in your PHP handling location:
fastcgi_intercept_errors on;
According to the manual, this directive:
Determines whether FastCGI server responses with codes greater than or equal to 300 should be passed to a client or be intercepted and redirected to nginx for processing with the error_page directive.
The main problem here is that by default php-fpm does not return status code to
nginx when exception occurs and display_errors is enabled.
TLDR
Use some global error handler that set error status code like this:
http_response_code(500);
This will be passed from php-fpm to nginx and attached to nginx response.
How to test this behaviour
Create index.php file:
<?php
// report all errors
error_reporting(E_ALL);
// do not display errors
ini_set("display_errors", 0);
// set http response code
// http_response_code(500);
// throw exception
throw new \Exception('TEST UNHANDLED EXCEPTION STATUS CODE');
Create default.conf for nginx
server {
listen 80;
server_name localhost;
location / {
root /var/www/html;
index index.php index.html index.htm;
}
# pass the PHP scripts to FastCGI server listening on php:9000
location ~ \.php$ {
root /var/www/html;
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;
include fastcgi_params;
}
}
Create docker-compose.yml
version: "3.5"
services:
php:
image: php:fpm
volumes:
- ./index.php:/var/www/html/index.php
nginx:
depends_on:
- php
image: nginx:latest
volumes:
- ./index.php:/var/www/html/index.php
- ./default.conf:/etc/nginx/conf.d/default.conf
Run project
docker-compose up -d
Install additional tools for testing php-fpm inside php container
To test direct responses from php-fpm wee need to install cgi-fcgi binary
docker-compose exec php bash -c 'apt update && apt install -y libfcgi0ldbl'
Test responses with display_errors disabled
Inside php container, make direct request to php-fpm
SCRIPT_NAME=/var/www/html/index.php SCRIPT_FILENAME=/var/www/html/index.php REQUEST_METHOD=GET cgi-fcgi -bind -connect localhost:9000
Response contains status code 500 that is handled by nginx correctly:
Status: 500 Internal Server Error
X-Powered-By: PHP/8.0.2
Content-type: text/html; charset=UTF-8
Test response from nginx
docker-compose exec nginx curl -i localhost
Response:
HTTP/1.1 500 Internal Server Error
Server: nginx/1.19.6
Date: Mon, 22 Feb 2021 11:45:56 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/8.0.2
php-fpm response contains status code 500 and has no body. Nginx use this code in response.
Test responses with display_errors enabled
Let's enable display_errors in index.php:
// do not display errors
ini_set("display_errors", 1);
Inside php container, make direct request to php-fpm
SCRIPT_NAME=/var/www/html/index.php SCRIPT_FILENAME=/var/www/html/index.php REQUEST_METHOD=GET cgi-fcgi -bind -connect localhost:9000
Response:
X-Powered-By: PHP/8.0.2
Content-type: text/html; charset=UTF-8
<br />
<b>Fatal error</b>: Uncaught Exception: TEST UNHANDLED EXCEPTION STATUS CODE in /var/www/html/index.php:12
Stack trace:
#0 {main}
thrown in <b>/var/www/html/index.php</b> on line <b>12</b><br />
Test response from nginx
docker-compose exec nginx curl -i localhost
Response
HTTP/1.1 200 OK
Server: nginx/1.19.6
Date: Mon, 22 Feb 2021 11:47:29 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/8.0.2
<br />
<b>Fatal error</b>: Uncaught Exception: TEST UNHANDLED EXCEPTION STATUS CODE in /var/www/html/index.php:12
Stack trace:
#0 {main}
thrown in <b>/var/www/html/index.php</b> on line <b>12</b><br />
php-fpm response is missing status code 500 but has a body. Nginx treats this as normal response
with status 200.
Test responses with display_errors enabled and header explicitly set to 500
Let's explicitly set response status code with display_errors on.
// set http response code
http_response_code(500);
Test php-fpm response:
SCRIPT_NAME=/var/www/html/index.php SCRIPT_FILENAME=/var/www/html/index.php REQUEST_METHOD=GET cgi-fcgi -bind -connect localhost:9000
Response:
Status: 500 Internal Server Error
X-Powered-By: PHP/8.0.2
Content-type: text/html; charset=UTF-8
<br />
<b>Fatal error</b>: Uncaught Exception: TEST UNHANDLED EXCEPTION STATUS CODE in /var/www/html/index.php:12
Stack trace:
#0 {main}
thrown in <b>/var/www/html/index.php</b> on line <b>12</b><br />
Test Nginx response
docker-compose exec nginx curl -i localhost
Response
HTTP/1.1 500 Internal Server Error
Server: nginx/1.19.6
Date: Mon, 22 Feb 2021 11:52:38 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/8.0.2
<br />
<b>Fatal error</b>: Uncaught Exception: TEST UNHANDLED EXCEPTION STATUS CODE in /var/www/html/index.php:12
Stack trace:
#0 {main}
thrown in <b>/var/www/html/index.php</b> on line <b>12</b><br />
php-fpm response has status code 500 and body. Nginx uses body and status code from php-fpm.
How to fix
A possible fix I can think of is global error handler that catch every unhandled error and
explicitly set proper error status code.

Nginx + Slim route POST data

I have an issue configuring Nginx correctly to work with Slim POST route.
In a post here on StackOverflow it was said that it is not a good practice to rewrite POST request.
Post: nginx rewrite post data
The idea is to have http://someurl.com/scan which is a POST route in index.php and allow only POST requests on the /scan and deny everything else.
index.php:
$app->post('/scan', function(ServerRequestInterface $request, ResponseInterface $response) { ... }
My Nginx conf looks like this:
server {
listen 80;
server_name someurl.com;
index index.php;
root /var/www/maxime/public;
error_log /var/log/nginx/max.err
access_log /var/log/nginx/max.log
location / {
deny all;
}
location = /scan {
limit_except POST {
deny all;
}
rewrite ^/scan$ /index.php last;
}
location ~ index\.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
include fastcgi_params;
fastcgi_pass php_pool;
fastcgi_keep_conn on;
}
}
To me this conf tells me that if a request is done on /scan and it is a POST request then we "redirect" everything to /index.php. Then the index.php location block comes into action which passes everything to the index.php or am I mistaking somewhere?
Currently with this setup I get this error in Nginx:
*100778 readv() failed (104: Connection re
set by peer) while reading upstream client: 123.123.123.123, server: someurl.com,
request: "POST /scan HTTP/1.1", upstream: "fastcgi://127.0.0.1:9001", host: "someurl.com"

nginx+php-fpm issue not able to call other php files in folder

I have a nginx and php-fpm config but when i access it from browser, only index.php is getting executed but rest of the files i am not able to call .
nginx config
{
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
keepalive_timeout 15;
keepalive_requests 2048;
server_tokens off;
upstream php
{
server unix:/tmp/php-cgi.socket;
server serverip:9000;
}
access_log /var/log/nginx/access.log main;
include /etc/nginx/conf.d/*.conf;
}
config in /etc/nginx/conf.d/
server {
root /var/www/Cachet/public/;
location / {
try_files $uri $uri/ /index.php index.php;
}
server_name serverip ; # Or whatever you want to use
listen 80 default;
location ~* \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_keep_conn on;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
These are few lines from error.log and access.log
2015/11/06 12:40:53 [error] 19346#0: *1 FastCGI sent in stderr:
"Unable to open primary script: /var/www/Cachet/public/dashboard.php
(No such file or directory)" while reading response header from
upstream, client: Client IP, server: Server IP, request: "GET
/dashboard.php HTTP/1.1", upstream:
"fastcgi://unix:/var/run/php5-fpm.sock:", host: "Server IP"
2015/11/06 12:41:05 [error] 19346#0: *1 FastCGI sent in stderr:
"Unable to open primary script: /var/www/Cachet/public/autoload.php
(No such file or directory)" while reading response header from
upstream, client: Client IP, server: Server IP, request: "GET
/autoload.php HTTP/1.1", upstream:
"fastcgi://unix:/var/run/php5-fpm.sock:", host: "Server IP"
since there was no response here then with help from my colleague i was able to find two problem here in config file because of which i was not able to call multiple php files in separate folder ..
try_files $uri $uri/ /index.php index.php;
instead it needed
try_files $uri $uri/ /index.php$is_args$args;
Alos since it was not loading the images the line which was missing was
include /etc/nginx/mime.types; in location block of conf.d/default.conf.
Check if your installed PHP version and PHP version inside config.d do not match each other. If that is the case, change PHP version inside conf.d file to your installed PHP version. Reload nginx.
fastcgi_pass unix:/var/run/php5-fpm.sock;

NGINX + HHVM + SOAP Chunked response

Seems that NGINX wont pass the correct HTTP protocol to HHVM backend.
Here the tests I've done:
If the client send a HTTP/1.1 request (non chunked) NGINX pass it to the HHVM backend and the correct response come back to the client
(client) (server backend)
HTTP/1.1(non chunked) -> NGINX -> HHVM
|
v
(HTTP/1.1 200) <- NGINX <- (non chunked response)
If the client send a HTTP/1.1 request (chuncked), the server produce a chunked reponse, but NGINX fire this log message
upstream prematurely closed connection while reading response header from upstream, request: "POST /soap HTTP/1.1", upstream: "fastcgi://unix:/var/run/hhvm/sock:"
and return a 502 (bad gateway) response
(client) (server backend)
HTTP/1.1(chunked) -> NGINX -> HHVM
|
v
(HTTP/1.1 502) <- NGINX <- (chunked response)
Why I got a 502 from NGINX using chunked response?
Is there something wrong with hhvm/fastcgi/hhvm config?
I double checked that backend give a correct 1/1 response :(
I've a scenario with:
NGINX:nginx/1.4.6 (Ubuntu)
HHVM: HipHop VM 3.6.1 (rel)
The NGINX config is
server {
listen 80;
server_name xxxxx;
root /usr/share/nginx/web;
include hhvm.conf;
location / {
# try to serve file directly, fallback to rewrite
try_files $uri #rewriteapp;
}
location #rewriteapp {
# rewrite all to app.php
rewrite ^(.*)$ /app.php/$1 last;
}
location ~ ^/(app|app_dev|app_benchmark|app_logall|config)\.php(/|$) {
fastcgi_keep_conn on;
#fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/run/hhvm/sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_read_timeout 240;
fastcgi_intercept_errors on;
include fastcgi_params;
}
}

$_POST is empty. Nginx OSx

I'm trying to access $_POST and it's empty.
If I try to access it on the root (ie. localhost), it WORKS.
If I try to access in a different folder (ie. localhost/foo), it DOESN'T WORK.
Here is my config file:
server {
listen 80;
root /var/www;
index index.php index.html index.htm;
server_name localhost;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/www;
}
# pass the PHP scripts to FastCGI server listening on the php-fpm socket
location ~ \.php {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Here is a sample RAW request:
POST /dev/post-test HTTP/1.1
Host: localhost
Cache-Control: no-cache
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="action"
store
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="id"
4315251
----WebKitFormBoundaryE19zNvXGzXaLvS5C
What is the problem?
Thanks.
The key on this differnet behavier is the trailing slash in the route.
For nginx the following are two different requests:
http://localhost/foo
http://localhost/foo/
The first one result in an internal 301 redirect (to index.php into that folder), where the $_POST values are lost.
More about this behavior and solution to prevent it, read the answers below:
Nginx causes 301 redirect if there's no trailing slash
I had same issue - empty $_POST variable.
And found that this happens if a request is sent with this header and the destionation is http instead of https:
Upgrade-Insecure-Requests: 1
In my case I was sending requests from my Unity game to it's server. And it turned out Unity adds that header to all requests sent from the game.
So I had to enable SSL for the server domain and switch from http to https. Once I did that - it worked.

Categories