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.
| Symptom | Likely Cause | First 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 connection | The upstream application crashed, restarted, exceeded a worker limit, or closed the response early. | Check the application logs and service state. |
upstream sent invalid header | The 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 proxy | The 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.
| Directive | Context | Use in 502 Diagnosis |
|---|---|---|
proxy_pass | location, if in location, limit_except | Sends HTTP requests to a proxied upstream. Wrong address, port, protocol, or upstream name commonly causes 502 errors. |
fastcgi_pass | location, if in location | Sends FastCGI requests to PHP-FPM or another FastCGI service. Wrong socket paths and permission issues are common 502 causes. |
proxy_connect_timeout | http, server, location | Controls how long Nginx waits to establish a connection to a proxied HTTP backend. |
proxy_read_timeout | http, server, location | Controls the timeout between successive reads from the proxied upstream response. |
fastcgi_read_timeout | http, server, location | Controls the timeout between successive reads from a FastCGI response, such as PHP-FPM. |
error_log | main, http, server, location | Shows 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 Layout | Common Systems | Edit Path | Enable Step |
|---|---|---|---|
sites-available plus sites-enabled | Debian and Ubuntu packages | /etc/nginx/sites-available/example.com | Create or update the symlink in /etc/nginx/sites-enabled/. |
conf.d | Fedora, RHEL, Rocky Linux, nginx.org packages | /etc/nginx/conf.d/example.com.conf | No symlink step when conf.d/*.conf is already included. |
| Custom or source-built layout | Source builds, panels, containers, custom prefixes | Use 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-namewhen they should always run. - After PHP upgrades, re-check the PHP-FPM socket path and update
fastcgi_passif 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
upstreamblocks 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.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><a href="https://example.com">link</a><blockquote>quote</blockquote>