Debugging Empty Response from PHP-FPM to Nginx: FastCGI Configuration and Headers Analysis


12 views

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:

  1. Strace PHP-FPM workers: strace -p $(pgrep -f "php-fpm: pool www")
  2. Compare full environment: php -r 'print_r($_SERVER);' vs PHP-FPM output
  3. 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:

  1. Compare strace outputs between working (cgi-fcgi) and failing (nginx) requests
  2. Check PHP-FPM logs for segmentation faults or timeouts
  3. Test with different PHP handlers (like php-cgi) to isolate the issue
  4. 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 "";
}