How to Fix Nginx 502 Bad Gateway

Fix Nginx 502 Bad Gateway errors by tracing upstream listeners, PHP-FPM sockets, SELinux, timeouts, CDN layers, and reload checks.

PublishedAuthorJoshua JamesRead time12 minGuide typeNginx

An Nginx 502 Bad Gateway error means Nginx accepted the client request but could not get a valid response from the upstream service. The upstream might be a reverse-proxied application, a PHP-FPM pool, a container, a private backend server, or another proxy layer in front of your origin.

The fastest fix is not to increase every timeout and hope for the best. Start by proving which layer failed: Nginx itself, the upstream listener, the PHP-FPM socket, file or socket permissions, SELinux, a container port mapping, or a CDN/proxy sitting in front of the server.

The checks stay Linux-focused but distro-neutral. Where Nginx package layouts differ, branch between Debian/Ubuntu-style sites-available and sites-enabled paths, Fedora/RHEL-style conf.d paths, and custom or source-built layouts.

Understand Nginx 502 Bad Gateway Causes

A 502 error is an upstream response problem. Nginx is usually running, but the service behind it is unavailable, misconfigured, blocked, or returning a response Nginx cannot use.

SymptomLikely CauseFirst Check
connect() failed (111: Connection refused)The backend service is stopped, listening on a different port, or bound to a different address.Test the backend directly with curl and inspect listeners with ss.
connect() to unix:... failed (2: No such file or directory)Nginx points to a PHP-FPM socket that does not exist, often after a PHP version change.Find the active PHP-FPM pool listen value.
connect() to unix:... failed (13: Permission denied)The Nginx worker user cannot access the FastCGI socket, or SELinux is blocking access.Check socket ownership, worker user, and SELinux status.
upstream prematurely closed connectionThe upstream application crashed, restarted, exceeded a worker limit, or closed the response early.Check the application logs and service state.
upstream sent invalid headerThe upstream sent malformed HTTP headers or a non-HTTP response to an HTTP proxy location.Test the upstream directly and confirm the correct protocol is used.
502 only through Cloudflare, load balancer, or another proxyThe edge proxy cannot reach your origin, or the origin responds differently through the proxied path.Test the origin directly with the same host header.

These Nginx directives are the ones most often involved in 502 diagnosis. Do not change all of them at once; use the logs to identify the failed layer first.

DirectiveContextUse in 502 Diagnosis
proxy_passlocation, if in location, limit_exceptSends HTTP requests to a proxied upstream. Wrong address, port, protocol, or upstream name commonly causes 502 errors.
fastcgi_passlocation, if in locationSends FastCGI requests to PHP-FPM or another FastCGI service. Wrong socket paths and permission issues are common 502 causes.
proxy_connect_timeouthttp, server, locationControls how long Nginx waits to establish a connection to a proxied HTTP backend.
proxy_read_timeouthttp, server, locationControls the timeout between successive reads from the proxied upstream response.
fastcgi_read_timeouthttp, server, locationControls the timeout between successive reads from a FastCGI response, such as PHP-FPM.
error_logmain, http, server, locationShows the exact upstream failure message that separates wrong ports, dead sockets, permission failures, and invalid responses.

Work from the error log outward. A 502 page tells you the class of failure, but the Nginx error log usually tells you the exact failed socket, address, upstream, or response stage.

Diagnose an Nginx 502 Bad Gateway Error

First confirm that Nginx is the server returning the 502 response. Use the curl command from any machine that can reach the site:

curl -I http://example.com

A typical Nginx 502 response looks like this:

HTTP/1.1 502 Bad Gateway
Server: nginx

If a CDN, hosting panel, proxy manager, or load balancer returns the response instead, keep that extra layer in scope. You still need to inspect the origin Nginx logs, but the visible 502 might be generated before the request reaches your Nginx server.

Check the Nginx Service State

On systemd-based Linux servers, confirm that Nginx is running before inspecting upstream failures:

systemctl is-active nginx

A running service prints:

active

If the service is not active, fix the Nginx service or configuration syntax first:

sudo nginx -t
sudo systemctl restart nginx

A successful syntax test prints these lines:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Read the Nginx Error Log

On systemd-based servers, use the journal first. Then read the configured Nginx error log file with the tail command if the journal does not show enough detail:

sudo journalctl -u nginx --since "15 minutes ago" --no-pager
sudo tail -n 50 /var/log/nginx/error.log

If your Nginx build or virtual host writes errors to a different file, filter the active error_log directive with the grep command:

sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*error_log[[:space:]]+'

Relevant output often shows the log destination:

error_log /var/log/nginx/error.log;

Common 502 log patterns include:

connect() failed (111: Connection refused) while connecting to upstream
no live upstreams while connecting to upstream
upstream prematurely closed connection while reading response header from upstream
upstream sent invalid header while reading response header from upstream
connect() to unix:/run/php/php8.4-fpm.sock failed (2: No such file or directory) while connecting to upstream
connect() to unix:/run/php/php8.4-fpm.sock failed (13: Permission denied) while connecting to upstream

Use the exact message to choose the fix below. A refused TCP connection points to a stopped service or wrong port. A missing Unix socket points to PHP-FPM or socket path drift. A permission message points to user, group, file mode, or SELinux rather than a timeout.

Find the Active Nginx Configuration File

Before editing, identify how your Nginx package loads site configuration. Do not assume every system uses Debian-style sites-available and sites-enabled.

sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*include[[:space:]]+/etc/nginx/(sites-enabled|conf\.d)/'

Relevant output usually contains one of these include patterns:

include /etc/nginx/sites-enabled/*;
include /etc/nginx/conf.d/*.conf;
Nginx LayoutCommon SystemsEdit PathEnable Step
sites-available plus sites-enabledDebian and Ubuntu packages/etc/nginx/sites-available/example.comCreate or update the symlink in /etc/nginx/sites-enabled/.
conf.dFedora, RHEL, Rocky Linux, nginx.org packages/etc/nginx/conf.d/example.com.confNo symlink step when conf.d/*.conf is already included.
Custom or source-built layoutSource builds, panels, containers, custom prefixesUse the file included from the active nginx.conf.Follow the include layout you confirmed with nginx -T.

Back up the file you are about to change. For a Debian or Ubuntu site file, use:

sudo cp /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.com.backup

For a conf.d layout, use:

sudo cp /etc/nginx/conf.d/example.com.conf /etc/nginx/conf.d/example.com.conf.backup

Do not edit production proxy settings without a rollback copy. A broken upstream path, broad location, or invalid PHP socket can take the whole virtual host offline.

Fix Nginx 502 Bad Gateway for Reverse Proxy Backends

For a reverse proxy, Nginx needs to reach the backend address defined by proxy_pass. Test the backend from the Nginx server itself, not from your laptop, because firewall rules and bind addresses can differ.

curl -I http://127.0.0.1:3000

A healthy HTTP backend returns an HTTP status line instead of a connection error:

HTTP/1.1 200 OK

If the direct backend test fails, fix the application before changing Nginx. Check whether the expected port is listening:

sudo ss -ltnp | grep ':3000'

Look for a LISTEN entry on the same address and port used in proxy_pass. If the application listens on 127.0.0.1:3000, Nginx on the same host can reach it with http://127.0.0.1:3000. If the application listens only inside a container, Nginx on the host needs a published host port or must run in the same container network.

Correct the Nginx proxy_pass Target

Use a concrete loopback target or a defined upstream block. Avoid undefined upstream names in copy-ready configuration because Nginx cannot resolve them reliably during syntax checks or reloads.

upstream app_backend {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 15s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Replace 127.0.0.1:3000 with the backend address that passed the direct curl test. If the backend uses HTTPS, set proxy_pass https://... and check upstream TLS separately; do not proxy HTTPS traffic as plain HTTP.

After editing the site file, test and reload Nginx:

sudo nginx -t && sudo systemctl reload nginx

For full proxy setup beyond a 502 repair, create a reverse proxy in Nginx with headers, load balancing, TLS, and WebSocket handling in one workflow.

Restart a Failed Backend Service

If the backend is a systemd service, check its state and logs. Replace app.service with the real unit name for your application:

systemctl is-active app.service
sudo journalctl -u app.service --since "15 minutes ago" --no-pager

If the service should be running but is inactive, restart it and enable it for boot:

sudo systemctl enable --now app.service

Run the direct backend curl test again before reloading Nginx. If the backend still fails directly, Nginx is not the main problem.

Allow Nginx to Reach Network Backends on SELinux Systems

On Fedora, RHEL, Rocky Linux, AlmaLinux, CentOS Stream, and other SELinux-enabled systems, Nginx can be blocked from connecting to a network backend even when the port is correct. Check SELinux mode first:

getenforce

If the system is enforcing and the error log points to a network upstream connection failure, check the relevant boolean:

getsebool httpd_can_network_connect

Enable it only when Nginx must proxy to a TCP backend:

sudo setsebool -P httpd_can_network_connect on

This is not required for every 502 error. Use it when SELinux is enforcing and Nginx must connect to a network service such as 127.0.0.1:3000, 127.0.0.1:8000, or a private upstream host.

Avoid disabling SELinux as a 502 shortcut. If the boolean does not match the log message, inspect audit logs and fix the specific policy, socket label, or service permission instead.

Fix Nginx 502 Bad Gateway for PHP-FPM

PHP sites commonly return 502 errors when Nginx points to the wrong PHP-FPM socket, PHP-FPM is stopped, or the Nginx worker user cannot access the socket. This often happens after a PHP upgrade because the socket name can include the PHP version.

Find the Active PHP-FPM Service and Socket

List PHP-FPM service units first:

systemctl list-units --type=service --all 'php*-fpm.service' 'php-fpm.service' --no-pager

Then find the pool listen setting. This search covers the common Debian/Ubuntu and Fedora/RHEL pool paths:

grep -R "^[[:space:]]*listen[[:space:]]*=" /etc/php/*/fpm/pool.d/*.conf /etc/php-fpm.d/*.conf 2>/dev/null

Relevant output might show one of these socket paths:

/etc/php/8.4/fpm/pool.d/www.conf:listen = /run/php/php8.4-fpm.sock
/etc/php-fpm.d/www.conf:listen = /run/php-fpm/www.sock

Your Nginx fastcgi_pass value must match the active PHP-FPM listen value. If PHP-FPM listens on TCP, use the matching address and port, such as 127.0.0.1:9000.

Correct PHP-FPM fastcgi_pass on Debian or Ubuntu

Debian and Ubuntu PHP-FPM packages usually place sockets under /run/php/. Match the socket to the installed PHP branch:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}

Replace php8.4-fpm.sock with the socket shown by your PHP-FPM pool. If the site uses PHP 8.3, for example, the socket might be /run/php/php8.3-fpm.sock.

Correct PHP-FPM fastcgi_pass on Fedora, RHEL, or Rocky Linux

Fedora, RHEL, Rocky Linux, AlmaLinux, and CentOS Stream systems often use a PHP-FPM socket under /run/php-fpm/. A typical Nginx location looks like this:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php-fpm/www.sock;
}

If your pool listens on TCP instead of a Unix socket, use the TCP target from the pool config:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
}

For Ubuntu-specific PHP-FPM tuning and service management, use the configure PHP-FPM on Ubuntu guide. For Fedora-specific Nginx and PHP-FPM layout, use the configure Nginx for PHP-FPM on Fedora guide.

Fix PHP-FPM Socket Permission Errors

If the log shows Permission denied for a PHP-FPM socket, compare the Nginx worker user with the socket owner and group:

ps -o user,group,comm -C nginx
ls -l /run/php/ /run/php-fpm/ 2>/dev/null

The Nginx worker user must be allowed to connect to the socket. Debian and Ubuntu commonly use www-data. Fedora and RHEL-family systems often use nginx for Nginx packages, while PHP-FPM pool defaults can vary by package source.

Adjust the PHP-FPM pool rather than weakening the socket mode globally. In the pool file, align these values with the Nginx worker user and group for your package layout:

listen.owner = www-data
listen.group = www-data
listen.mode = 0660

On Fedora or RHEL-family systems using an nginx worker user, the pool might instead use:

listen.owner = nginx
listen.group = nginx
listen.mode = 0660

Restart PHP-FPM after changing the pool, then test and reload Nginx. Use the PHP-FPM unit name found earlier:

sudo systemctl restart php8.4-fpm
sudo nginx -t && sudo systemctl reload nginx

On systems with an unversioned PHP-FPM unit, use:

sudo systemctl restart php-fpm
sudo nginx -t && sudo systemctl reload nginx

Fix Nginx 502 Bad Gateway from Slow or Invalid Upstream Responses

Timeouts are worth changing only after the backend is reachable and the logs show a slow response path. If the backend is stopped or the socket path is wrong, timeout changes only hide the real problem.

Tune Nginx Proxy Timeouts for Slow Applications

For a slow reverse-proxied application, set timeouts inside the affected location rather than globally. This keeps one slow route from changing behavior for every site on the server.

location / {
    proxy_pass http://app_backend;
    proxy_http_version 1.1;

    proxy_connect_timeout 15s;
    proxy_send_timeout 60s;
    proxy_read_timeout 120s;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

proxy_connect_timeout covers connection establishment. proxy_read_timeout covers the gap between reads from the upstream response, not the total request duration. If the application does no work for longer than that timeout before sending headers or data, Nginx can close the upstream connection.

Tune Nginx FastCGI Timeouts for Slow PHP Requests

For PHP-FPM, use the matching FastCGI directives in the PHP location:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;

    fastcgi_connect_timeout 15s;
    fastcgi_send_timeout 60s;
    fastcgi_read_timeout 120s;
}

Long-running PHP requests can also fail because of PHP-FPM settings, application timeouts, database locks, or exhausted workers. Check the PHP-FPM and application logs before assuming Nginx needs longer limits.

Fix Oversized Upstream Headers

If the error log says upstream sent too big header while reading response header from upstream, the upstream response headers are larger than the configured buffers. This is common with large cookies, very large redirect headers, or applications that attach excessive metadata.

For HTTP proxy backends, increase the proxy header buffer only for the affected location:

location / {
    proxy_pass http://app_backend;

    proxy_buffer_size 16k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;
}

For PHP-FPM backends, use FastCGI buffers instead:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;

    fastcgi_buffer_size 16k;
    fastcgi_buffers 4 32k;
}

Buffer increases should be targeted. If one application sends very large cookies or response headers, fixing the application is often better than raising global buffers for every site.

Fix Nginx 502 Bad Gateway Behind a CDN or Extra Proxy

When Cloudflare, another CDN, a load balancer, Nginx Proxy Manager, a hosting panel, or a second reverse proxy sits in front of your origin, identify which layer returns the 502. The browser page alone is not enough.

Test the origin directly from a trusted machine. Replace 203.0.113.10 with the real origin IP address for your server:

curl -I --resolve example.com:443:203.0.113.10 https://example.com

If the direct origin test works but the proxied request still returns 502, inspect the CDN or load-balancer configuration. Check origin port rules, TLS mode, origin certificates, WAF rules, proxy timeouts, and whether the edge proxy is sending traffic to the correct backend address.

If the direct origin test also returns 502, continue debugging the origin Nginx configuration, backend service, PHP-FPM pool, and local logs. Fixing the CDN will not repair an origin upstream that is already failing locally.

Apply and Verify Nginx 502 Bad Gateway Fixes

After changing an Nginx site file, run the syntax test before reloading. This prevents a typo from replacing one 502 error with a failed Nginx reload.

sudo nginx -t

A successful syntax test prints:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

On systemd-based Linux servers, reload Nginx only after the test passes:

sudo systemctl reload nginx

Then check the site again:

curl -I http://example.com

A fixed HTTP site should return a normal status line instead of 502 Bad Gateway:

HTTP/1.1 200 OK
Server: nginx

For HTTPS sites, test the HTTPS endpoint as well:

curl -I https://example.com

Relevant output should show a successful HTTPS response:

HTTP/2 200
server: nginx

If the response still shows 502, re-open the Nginx error log immediately after the failed request. The newest log message is usually more useful than the original one because it reflects the current configuration.

Prevent Repeat Nginx 502 Bad Gateway Errors

Once the site is working again, add a few checks that make repeat 502 errors easier to diagnose next time.

  • On systemd systems, keep backend services enabled at boot with systemctl enable --now service-name when they should always run.
  • After PHP upgrades, re-check the PHP-FPM socket path and update fastcgi_pass if the socket name changed.
  • Use one place to own redirects, headers, and proxying for a hostname. Avoid splitting the same behavior between Nginx, a CDN, a proxy manager, and an application framework unless each layer has a clear role.
  • Keep Nginx upstream targets syntax-testable. Use loopback IP addresses, private IP addresses, or named upstream blocks instead of undefined hostnames.
  • Monitor application logs as well as Nginx logs. A 502 is often the first visible sign of an application crash, worker exhaustion, database failure, or deployment problem.

For high-traffic sites, consider a separate health-checking or monitoring layer. NGINX Open Source can use passive upstream failure handling, but active HTTP health checks require NGINX Plus or an external monitoring system.

Troubleshoot Common Nginx 502 Bad Gateway Log Messages

connect() failed (111: Connection refused)

This means Nginx reached the target address but nothing accepted the connection. The backend service is usually stopped, listening on another port, or bound to a different interface.

curl -I http://127.0.0.1:3000
sudo ss -ltnp | grep ':3000'

Fix the backend service or update proxy_pass to match the address that is actually listening.

no live upstreams while connecting to upstream

This usually appears when every server in an upstream group is considered unavailable, or the upstream group has no usable targets. Check the upstream block and test each backend directly:

curl -I http://192.0.2.10:3000
curl -I http://192.0.2.11:3000

Replace the documentation addresses with your real private backend addresses. If every direct test fails, repair the application nodes or network path before reloading Nginx.

connect() to unix Socket Failed With No Such File

This points to a missing Unix socket. For PHP-FPM, the active PHP branch may have changed, or PHP-FPM might not be running.

systemctl list-units --type=service --all 'php*-fpm.service' 'php-fpm.service' --no-pager
grep -R "^[[:space:]]*listen[[:space:]]*=" /etc/php/*/fpm/pool.d/*.conf /etc/php-fpm.d/*.conf 2>/dev/null

Update fastcgi_pass to the socket that exists, restart PHP-FPM, then test and reload Nginx.

connect() to unix Socket Failed With Permission Denied

This means the socket exists but Nginx cannot access it. Check the socket mode, owner, group, and the Nginx worker user. On SELinux systems, also check audit logs if normal Unix permissions look correct.

ps -o user,group,comm -C nginx
ls -l /run/php/ /run/php-fpm/ 2>/dev/null
sudo journalctl -k --since "15 minutes ago" --no-pager | grep -i denied

Fix the PHP-FPM pool ownership or SELinux policy for the actual socket instead of setting broad world-writable permissions.

upstream prematurely closed connection

The upstream accepted the connection but closed it before Nginx received a usable response header. This is usually an application crash, worker timeout, memory pressure, deployment restart, or unhandled exception.

sudo journalctl -u app.service --since "15 minutes ago" --no-pager

Fix the application error first. Raising Nginx timeouts will not help when the upstream process crashes or closes the response early.

upstream sent invalid header

Nginx expected an HTTP or FastCGI response but received malformed headers, a non-HTTP protocol, or output that started before valid headers. This often means the wrong protocol is configured, such as sending HTTP proxy traffic to a FastCGI socket or sending FastCGI traffic to an HTTP server.

curl -v http://127.0.0.1:3000/ 2>&1 | head -20

If the backend is an HTTP service, use proxy_pass. If it is PHP-FPM or another FastCGI service, use fastcgi_pass with the correct FastCGI parameters.

Conclusion

Fixing an Nginx 502 Bad Gateway error comes down to proving the failed handoff: Nginx service state, error-log message, backend listener, PHP-FPM socket, permissions, SELinux, timeout, header size, or upstream proxy layer. After each repair, run sudo nginx -t, reload only after it passes, and confirm the final response with curl -I.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: