Web Servers Intermediate

Fixing Nginx 502 Bad Gateway with PHP-FPM Unix Socket & TCP Pools (Ubuntu, Debian, Alpine, WSL2)

Comprehensive troubleshooting guide for connect() to unix:/var/run/php/php-fpm.sock failed (111: Connection refused). Learn root causes, system traces, and verified resolutions.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Comprehensive troubleshooting guide for connect() to unix:/var/run/php/php-fpm.sock failed (111: Connection refused). Learn root causes, system traces, and verified resolutions.

A "502 Bad Gateway" error indicates that Nginx, acting as a reverse proxy, failed to receive a valid response from an upstream server. In the context of a PHP application served by Nginx on Alpine Linux, this almost universally means Nginx was unable to communicate correctly with the PHP-FPM (FastCGI Process Manager) service. When configured to use a Unix domain socket, this communication breakdown is frequently due to misconfigurations related to the socket path, file permissions, or the PHP-FPM service itself. This guide will walk you through diagnosing and resolving these common issues.

NGINX TO FASTCGI / PHP-FPM PROXY ARCHITECTURE
<!-- Client Box -->
<rect x="35" y="90" width="130" height="110" rx="6" fill="#151926" stroke="#2d3748" stroke-width="1.5"/>
<text x="100" y="135" fill="#f3f4f6" font-size="14" font-weight="700" text-anchor="middle">HTTP Client</text>
<text x="100" y="160" fill="#06b6d4" font-size="11" font-family="'JetBrains Mono', monospace" text-anchor="middle">GET /index.php</text>

<!-- Arrow 1 -->
<path d="M 165 145 L 225 145" stroke="#06b6d4" stroke-width="2"/>

<!-- Nginx Web Server -->
<rect x="230" y="70" width="180" height="155" rx="6" fill="#151926" stroke="#06b6d4" stroke-width="1.5"/>
<text x="320" y="100" fill="#06b6d4" font-size="14" font-weight="700" text-anchor="middle">Nginx Web Server</text>
<text x="320" y="122" fill="#9ca3af" font-size="11" text-anchor="middle">fastcgi_pass directive</text>
<rect x="245" y="140" width="150" height="65" rx="4" fill="#0b0d14" stroke="#2d3748" stroke-width="1"/>
<text x="320" y="162" fill="#f3f4f6" font-size="11" font-weight="600" text-anchor="middle">Proxy Gateway</text>
<text x="320" y="180" fill="#f87171" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="middle">502 Bad Gateway</text>
<text x="320" y="195" fill="#9ca3af" font-size="9" text-anchor="middle">(if socket is closed)</text>

<!-- Arrow 2 Socket -->
<path d="M 410 145 L 485 145" stroke="#f59e0b" stroke-width="2" stroke-dasharray="4,4"/>
<text x="448" y="135" fill="#f59e0b" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="middle">.sock / :9000</text>

<!-- PHP-FPM / Upstream Daemon -->
<rect x="490" y="70" width="265" height="155" rx="6" fill="#151926" stroke="#10b981" stroke-width="1.5"/>
<text x="622" y="100" fill="#10b981" font-size="14" font-weight="700" text-anchor="middle">PHP-FPM / Application Pool</text>
<text x="622" y="122" fill="#9ca3af" font-size="11" text-anchor="middle">Unix Domain Socket / TCP Pool</text>
<rect x="505" y="140" width="235" height="65" rx="4" fill="#0b0d14" stroke="#10b981" stroke-width="1"/>
<text x="622" y="162" fill="#34d399" font-size="11" font-family="'JetBrains Mono', monospace" text-anchor="middle">/run/php/php8.x-fpm.sock</text>
<text x="622" y="180" fill="#9ca3af" font-size="10" text-anchor="middle">Owner: www-data:www-data (0660)</text>
<text x="622" y="195" fill="#a7f3d0" font-size="9" text-anchor="middle">pm.max_children / Worker Execution</text>

Figure 2: FastCGI socket communication and upstream pool permission requirements.

Symptom & Error Signature

When encountering this issue, users will typically see a generic "502 Bad Gateway" page in their web browser. On the server side, the critical information is found in the Nginx error logs.

Browser Output:

502 Bad Gateway
nginx/1.24.0

Nginx Error Log (/var/log/nginx/error.log or Docker container logs): You'll typically find one of these entries, providing a clue to the root cause:

  • Socket not found/PHP-FPM not running:
    2023/10/26 10:30:05 [crit] 1#1: *1 connect() to unix:/var/run/php/php7.4-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 192.168.1.100, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.4-fpm.sock:", host: "example.com"
    
  • Permission denied:
    2023/10/26 10:30:05 [crit] 1#1: *1 connect() to unix:/var/run/php/php7.4-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.1.100, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.4-fpm.sock:", host: "example.com"
    
  • PHP-FPM process prematurely closed connection (often due to crashes or timeouts):
    2023/10/26 10:30:05 [error] 1#1: *1 upstream prematurely closed connection while reading response header from upstream, client: 192.168.1.100, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.4-fpm.sock:", host: "example.com"
    

Root Cause Analysis

The "502 Bad Gateway" error with Nginx and PHP-FPM via a Unix socket on Alpine Linux usually boils down to one or more of these underlying issues:

  1. PHP-FPM Service Not Running: The most straightforward cause. If PHP-FPM isn't active, the Unix socket won't exist, and Nginx has nowhere to connect.
  2. Incorrect Unix Socket Path: A mismatch between the socket path Nginx is configured to use (fastcgi_pass) and the path PHP-FPM is configured to listen on (listen).
  3. Socket Permissions: Nginx runs under a specific user (e.g., nginx). This user must have read and write permissions to the PHP-FPM Unix socket file, and execute permissions on all parent directories leading to the socket. Alpine Linux often uses the nginx user by default for Nginx, and www-data or a similar user for PHP-FPM.
  4. PHP-FPM Pool Configuration Issues: The listen.owner, listen.group, and listen.mode directives in the PHP-FPM pool configuration file (www.conf or similar) are crucial for setting correct socket permissions.
  5. Resource Exhaustion or PHP Errors: PHP-FPM processes might be crashing due to memory limits, fatal PHP errors, or script timeouts (request_terminate_timeout), causing PHP-FPM to close the connection before Nginx receives a full response. This often manifests as "upstream prematurely closed connection".
  6. Nginx User Mismatch: The user Nginx is running as does not belong to the group that owns the PHP-FPM socket, or vice-versa, preventing proper access.
  7. Docker-specific Issues: In a Dockerized environment, bind mounts for the socket might be misconfigured, or processes inside the container might not be starting correctly.

Step-by-Step Resolution

Follow these steps to diagnose and resolve the 502 Bad Gateway error. Remember to check Nginx and PHP-FPM logs after each change.

1. Verify PHP-FPM Service Status

First, ensure PHP-FPM is actually running and listening for connections. Alpine Linux often runs in containers or uses OpenRC as an init system.

  • For Docker Containers:

    docker ps | grep php-fpm
    docker logs <php-fpm-container-id-or-name>
    

    If php-fpm is not listed in docker ps, or logs show startup failures, restart the container:

    docker restart <php-fpm-container-id-or-name>
    
  • For Alpine with OpenRC (bare metal or VM):

    rc-service php-fpm status
    rc-service php-fpm start
    
  • General process check (works everywhere):

    ps aux | grep php-fpm
    

    You should see several php-fpm: pool www processes running. If not, PHP-FPM isn't active.

    If PHP-FPM isn't running, check its startup configuration and logs (e.g., /var/log/php-fpm.log or container logs) for specific errors preventing it from starting.

2. Confirm Socket Path Consistency

A common mistake is having Nginx configured to use one socket path while PHP-FPM is configured to listen on another.

  • Check Nginx configuration: Locate your Nginx server block configuration for the site (e.g., /etc/nginx/conf.d/default.conf or /etc/nginx/http.d/default.conf). Find the fastcgi_pass directive:
    # /etc/nginx/conf.d/default.conf
    server {
        # ...
        location ~ .php$ {
            fastcgi_pass   unix:/var/run/php/php7.4-fpm.sock; # <-- Note this path
            fastcgi_index  index.php;
            include        fastcgi.conf;
        }
        # ...
    }
    
  • Check PHP-FPM pool configuration: Locate your PHP-FPM pool configuration file (e.g., /etc/php8/php-fpm.d/www.conf on Alpine, replace php8 with your PHP version). Find the listen directive:
    ; /etc/php8/php-fpm.d/www.conf
    [www]
    listen = /var/run/php/php7.4-fpm.sock ; <-- This path must match Nginx
    

    Ensure both paths are identical, including the exact PHP version in the socket name if specified.

3. Inspect Socket Permissions

This is a frequent culprit for (13: Permission denied) errors. Nginx needs appropriate permissions to access the socket file.

  • Check the socket file and its parent directory:

    ls -l /var/run/php/php7.4-fpm.sock
    ls -ld /var/run/php/
    

    Expected output might look like:

    srw-rw---- 1 www-data www-data 0 Oct 26 10:45 /var/run/php/php7.4-fpm.sock
    drwxr-xr-x 2 root     root     60 Oct 26 10:45 /var/run/php/
    

    Here, the socket is owned by www-data:www-data. Nginx, typically running as the nginx user, would need to be part of the www-data group or have appropriate listen.mode settings.

  • Check Nginx user: Inspect your main Nginx configuration file (/etc/nginx/nginx.conf) for the user directive:

    # /etc/nginx/nginx.conf
    user nginx; # <-- Nginx runs as this user
    worker_processes auto;
    # ...
    
  • Adjust PHP-FPM listen directives: In your PHP-FPM pool configuration (/etc/php8/php-fpm.d/www.conf), modify the listen.owner, listen.group, and listen.mode directives to grant Nginx access.

    Option A: Add Nginx user to PHP-FPM group (Recommended): Ensure the nginx user is a member of the group that owns the PHP-FPM socket (e.g., www-data).

    # On Alpine with OpenRC
    adduser nginx www-data
    # Or in Dockerfile:
    # RUN addgroup -S www-data && adduser -S nginx www-data
    

    Then, configure PHP-FPM to create the socket with the group ownership and mode:

    ; /etc/php8/php-fpm.d/www.conf
    listen = /var/run/php/php7.4-fpm.sock
    listen.owner = www-data
    listen.group = www-data
    listen.mode = 0660 ; Allows owner and group to read/write
    

    Option B: Grant broader permissions (less secure, but sometimes necessary in constrained environments):

    ; /etc/php8/php-fpm.d/www.conf
    listen = /var/run/php/php7.4-fpm.sock
    ; listen.owner = www-data  ; Optional, if not needed to be explicitly www-data
    ; listen.group = www-data  ; Optional
    listen.mode = 0666 ; Allows anyone to read/write (use with caution!)
    

    After changing PHP-FPM configuration, you must restart PHP-FPM for changes to take effect. If you're modifying users/groups, a full restart of both Nginx and PHP-FPM is recommended.

4. Review Nginx & PHP-FPM Configuration Files

Incorrect or insufficient settings can lead to premature connection closure.

  • Nginx Configuration (/etc/nginx/nginx.conf or site-specific conf): Ensure fastcgi_buffer_size, fastcgi_buffers, and fastcgi_read_timeout are adequate, especially for larger PHP scripts or slower operations.

    # In your Nginx http or server block
    http {
        # ...
        fastcgi_buffers 16 16k;       # Increase buffer size
        fastcgi_buffer_size 32k;
        fastcgi_connect_timeout 5s;   # How long Nginx waits to connect to FPM
        fastcgi_send_timeout 120s;    # How long Nginx waits for FPM to send data
        fastcgi_read_timeout 120s;    # How long Nginx waits for FPM to send a response
        # ...
    }
    
  • PHP-FPM Pool Configuration (/etc/php8/php-fpm.d/www.conf): Review resource limits. "Upstream prematurely closed connection" can be a symptom of PHP-FPM processes dying.

    ; /etc/php8/php-fpm.d/www.conf
    request_terminate_timeout = 60s ; How long a single PHP script can run
    ; If set to 0, it means 'Off'. Set to a reasonable value.
    catch_workers_output = yes ; Important for debugging, sends PHP errors to PHP-FPM logs
    
    ; Process Manager settings (adjust based on server resources)
    pm = dynamic              ; or ondemand, static
    pm.max_children = 50      ; Max number of FPM child processes
    pm.start_servers = 5
    pm.min_spare_servers = 5
    pm.max_spare_servers = 35
    

    Also, check php.ini (/etc/php8/php.ini) for memory_limit and max_execution_time. If scripts exceed these, PHP processes can crash.

5. Examine PHP-FPM Logs for Errors

PHP-FPM logs are invaluable for understanding why processes might be failing.

tail -f /var/log/php-fpm.log
# Or for Docker:
docker logs -f <php-fpm-container-id-or-name>

Look for FATAL, ERROR, or WARNING messages, especially those related to memory_limit, max_execution_time, or uncaught exceptions in your PHP applications.

If catch_workers_output = yes is set in www.conf, PHP stderr (including errors) will be redirected to the PHP-FPM logs, which is very helpful for debugging.

6. Restart Services

After making any configuration changes, you must restart Nginx and PHP-FPM for the changes to take effect.

  • Nginx:
    nginx -t # Test Nginx configuration syntax
    nginx -s reload # Reload Nginx configuration (without dropping connections)
    # If a full restart is needed:
    # rc-service nginx restart # On Alpine with OpenRC
    # docker restart <nginx-container-id> # In Docker
    
  • PHP-FPM:
    # On Alpine with OpenRC
    rc-service php-fpm restart
    # In Docker (if PHP-FPM is PID 1 or managed by supervisord)
    # docker restart <php-fpm-container-id-or-name>
    # or for supervisord inside container:
    # docker exec <php-fpm-container-id-or-name> supervisorctl restart php-fpm
    

    Always test Nginx configuration with nginx -t before reloading or restarting to avoid syntax errors that could take your web server offline.

7. Docker-Specific Considerations

If you're running Nginx and PHP-FPM in separate Docker containers, ensure the Unix socket is properly shared.

  • Shared Volume for Socket: The most robust way to share a Unix socket between containers is using a shared volume.
    # docker-compose.yml example
    version: '3.8'
    services:
      nginx:
        image: nginx:alpine
        volumes:
          - ./nginx/conf.d:/etc/nginx/conf.d:ro
          - php-fpm-sock:/var/run/php # Mount the shared volume
        depends_on:
          - php-fpm
        # ... other nginx settings
    
      php-fpm:
        image: php:8.2-fpm-alpine # or similar
        volumes:
          - ./app:/var/www/html:rw
          - php-fpm-sock:/var/run/php # Mount the shared volume
        # ... other php-fpm settings
    
    volumes:
      php-fpm-sock: # Define the named volume
    
    Ensure that the listen directive in PHP-FPM's www.conf and fastcgi_pass in Nginx's config point to the same path within the respective containers that the shared volume is mounted to (e.g., /var/run/php/php7.4-fpm.sock).

By systematically working through these steps, you should be able to identify and resolve the root cause of your Nginx 502 Bad Gateway error on Alpine Linux.

Multi-Platform Step-by-Step Implementation Matrix

The core underlying mechanism is identical across UNIX distributions, but configuration paths, daemon names, and kernel socket limits differ by platform. Follow the exact instructions for your target operating system below.

🐧 Debian 12 (Bookworm) & Ubuntu 24.04 / 22.04 LTS

On Debian-based systems, systemd service units and standard apt package paths apply:

# Verify active listening sockets and associated process IDs
sudo ss -tulpn | grep -E ":(80|443|3306|5432|6379)"

# Inspect live systemd daemon status
sudo systemctl status --no-pager nginx mysql postgresql redis

🔴 Rocky Linux 9, AlmaLinux & CentOS Stream

On RHEL-derived distributions, remember to check SELinux boolean contexts and firewalld rules:

# Check if SELinux is enforcing and denying network binds
sudo getenforce
sudo setsebool -P httpd_can_network_connect 1

# Open firewall ports
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload

🍎 macOS Local Environment (Homebrew)

On macOS, Homebrew services run in user space or launchd:

# Identify processes occupying ports on macOS
sudo lsof -iTCP -sTCP:LISTEN -n -P

# Restart Homebrew services
brew services restart nginx

🪟 Windows WSL2 (Ubuntu / Debian)

On WSL2, port forwarding bridges the Windows host with the WSL virtual adapter. Always ensure host port sharing is not blocked by Windows Fast Startup or native IIS/Hyper-V services:

# Check ports on Windows Host (PowerShell as Admin)
Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -in 80,443,3306,5432 }
👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.