When working with Nginx and PHP-FPM configurations, one particularly puzzling issue occurs when PHP-FPM returns technically valid responses (HTTP 200) but with empty content bodies. The response headers (including X-Powered-By) appear correct, yet the actual content is missing.
Using tcpdump
to analyze the raw FastCGI protocol communication reveals identical headers between successful (command line) and failing (Nginx) requests:
# Successful CLI request SCRIPT_FILENAME=/var/www/.status REQUEST_METHOD=GET SCRIPT_NAME=/.status # Nginx request (via fastcgi_params) SCRIPT_FILENAME=/var/www/.status REQUEST_METHOD=GET SCRIPT_NAME=/.status
While the core FCGI headers match, subtle environment variable differences exist:
- Shell-inherited variables (USER, HOME, PATH) present in CLI calls
- Nginx-specific variables (REMOTE_ADDR, SERVER_PROTOCOL)
- Potential PHP configuration differences from CLI vs FPM contexts
Add these critical parameters to your Nginx location block:
location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param HTTP_PROXY ""; # Critical for some FCGI implementations fastcgi_buffer_size 128k; # Prevents potential buffer issues fastcgi_intercept_errors on; # Helps diagnose silent failures }
Modify your php-fpm pool configuration (/etc/php-fpm.d/www.conf):
[www] ; Ensure these values are properly set ping.path = /.status ping.response = pong ; Add these troubleshooting parameters catch_workers_output = yes php_admin_value[error_log] = /var/log/php-fpm/www-error.log php_admin_flag[log_errors] = on
When standard fixes fail, try these deeper diagnostic methods:
- Strace PHP-FPM workers:
strace -p $(pgrep -f "php-fpm: pool www")
- Compare full environment:
php -r 'print_r($_SERVER);'
vs PHP-FPM output - Test with minimal FCGI client:
cgi-fcgi -bind -connect 127.0.0.1:9000 /index.php
Real-world cases we've encountered:
- Missing
SCRIPT_FILENAME
when using aliases - Chroot environments with incorrect path resolutions
- SELinux/AppArmor blocking FCGI communications
- PHP opcache returning empty responses after crashes
When working with Nginx and PHP-FPM through FastCGI, encountering empty responses despite proper headers can be frustrating. Let's dissect this common issue where:
- Direct cgi-fcgi requests work perfectly
- Nginx receives valid headers (200 OK, X-Powered-By) but empty body
- TCP dump shows identical FCGI headers between working and broken requests
First, verify your current setup with these checks:
# Check PHP-FPM status directly
SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000
# Sample working nginx location block
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
One frequent cause is FastCGI buffering behavior. Try these adjustments in your nginx config:
fastcgi_buffering off;
fastcgi_request_buffering off;
fastcgi_intercept_errors on;
fastcgi_param HTTP_PROXY "";
File access issues often manifest as empty responses:
# Check PHP-FPM user permissions
ps aux | grep php-fpm
ls -la /var/www/your_project
# Important nginx directives for path resolution
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
Create a diagnostic endpoint to inspect raw FastCGI responses:
location /fcgi-debug {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root/debug.php;
# Output raw response without processing
fastcgi_param HTTP_ACCEPT_ENCODING "";
fastcgi_keep_conn on;
}
With debug.php containing:
<?php
header('Content-Type: text/plain');
print_r([
'SERVER' => $_SERVER,
'ENV' => $_ENV,
'FCGI_VARS' => array_filter($_SERVER, fn($k) => str_starts_with($k, 'FCGI_'), ARRAY_FILTER_USE_KEY)
]);
When standard fixes fail, consider these deeper investigations:
- Compare
strace
outputs between working (cgi-fcgi) and failing (nginx) requests - Check PHP-FPM logs for segmentation faults or timeouts
- Test with different PHP handlers (like
php-cgi
) to isolate the issue - Inspect PHP-FPM process memory limits and opcache settings
Here's a verified configuration that solves most empty response issues:
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 64k;
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
fastcgi_param HTTP_PROXY "";
}