Fix 502 Bad Gateway Error in Nginx

You deploy a change, restart a server, or open your website and get a 502 Bad Gateway error. Nginx is answering the request, but it cannot get a valid response from the service behind it. That service, called the upstream, might be PHP-FPM, a Node.js application, or another web server.
The fix depends on what went wrong between Nginx and the upstream. A stopped service, an incorrect socket path, and an oversized response header can all produce the same error page. This guide explains how to use the error log to find the cause and apply the right fix.
You need access to the server and a user with sudo privileges. The examples use Ubuntu and Debian configuration paths and were tested with Nginx 1.28.3 and PHP-FPM 8.5 on Ubuntu 26.04. On other installations, site configurations are often under /etc/nginx/conf.d/ instead of /etc/nginx/sites-available/.
If you are visiting someone else’s website, try reloading the page or returning later. The checks below are for the person managing the server.
Quick Reference
For a printable quick reference, see the Nginx cheatsheet .
Start with sudo tail -n 50 /var/log/nginx/error.log, then match the message to the appropriate check:
| Error log message | What to check |
|---|---|
Connection refused | Upstream service status and the address and port in proxy_pass |
No such file or directory on a Unix socket | Whether PHP-FPM is running and fastcgi_pass points to its socket |
Permission denied on a Unix socket | Nginx worker user, socket permissions, and parent directory permissions |
Permission denied on a TCP address | SELinux on Fedora and RHEL, the httpd_can_network_connect boolean |
upstream prematurely closed connection | Application logs, crashes, and worker termination |
Connection reset by peer | PHP-FPM or application log for a worker killed during the request |
upstream sent too big header | Response header size and proxy_buffer_size or fastcgi_buffer_size |
SSL_do_handshake() failed | Upstream protocol, TLS configuration, and server name |
upstream timed out | Connection or response timeout, usually a 504 rather than a 502 |
Check the Nginx Error Log
Start with the error log. It names the upstream, the socket or port, and the reason the request failed:
sudo tail -n 50 /var/log/nginx/error.logFor example, these excerpts identify three different causes of a 502:
connect() failed (111: Connection refused) while connecting to upstream
upstream prematurely closed connection while reading response header from upstream
upstream sent too big header while reading response header from upstreamThe first message means the connection was refused. The service may be stopped, or Nginx may be using the wrong address or port. The second means the upstream closed the connection before Nginx received a complete response header. Check the application logs to find out why it closed. The third means the response header exceeded the buffer Nginx uses to read it.
Match the log entry to the time and URL of the failed request. To watch new entries while you reproduce the error, run:
sudo tail -f /var/log/nginx/error.logPress Ctrl+C to stop watching. If the request does not produce an entry, check the error_log directive for your site. Another proxy or the application itself may also be returning the 502. The Nginx log files
guide explains log locations and levels.
Check the Upstream Address and Service
When the log shows Connection refused, compare the upstream address in the log with your configuration. If you are unsure which file contains it, print the active configuration and its included files:
sudo nginx -TFind the proxy_pass directive in the location handling the failed request. For example, this directive sends requests to port 3000 on the same host:
proxy_pass http://127.0.0.1:3000;Check whether anything is listening on that port:
sudo ss -ltnp 'sport = :3000'If the output contains only the table headings, no TCP service is listening on port 3000 in that network namespace. If a service is listed, compare its listening address with proxy_pass. A service listening only on 127.0.0.1 cannot accept connections sent to the server’s private network address.
For an HTTP upstream, try requesting it directly from the Nginx host:
curl -sS -o /dev/null -D - http://127.0.0.1:3000/Replace the address, port, and path with those used by your application. If it routes requests by hostname, also pass the expected host, for example -H 'Host: example.com'. A successful direct request shows that the upstream can answer, but you still need to check whether Nginx uses the same address, protocol, and request headers. PHP-FPM speaks FastCGI, so this HTTP test does not apply to its socket or port.
If the application is managed by systemd, check its status and recent logs. Replace myapp with the actual unit name:
sudo systemctl status myapp
sudo journalctl -u myapp -n 50 --no-pagerIf the service is stopped, start it. If it has failed, check the log and correct the cause before restarting it:
sudo systemctl restart myappConfirm that the service stays up and the site loads again. If it keeps exiting, look for the underlying application error, such as a missing environment variable, an unreachable database, or a process being killed for using too much memory.
If you corrected proxy_pass, test the configuration before reloading Nginx:
sudo nginx -t && sudo systemctl reload nginxThe && operator runs the reload only if the configuration test succeeds.
Fix PHP-FPM Socket Problems
On PHP sites, Nginx usually connects to a PHP-FPM pool through a Unix socket. PHP-FPM can also listen on a TCP port, so check the fastcgi_pass directive before assuming a socket is in use.
Check the Service and Socket Path
A socket that does not exist produces a log entry containing No such file or directory. This often happens when PHP-FPM is stopped or a distribution upgrade changes the installed PHP version.
List the installed PHP-FPM service units and the sockets that exist:
systemctl list-unit-files 'php*-fpm.service'
ls -l /run/php/These examples use PHP 8.5. Replace the version in service names, socket paths, and configuration files with the version used by your site. Check its status:
sudo systemctl status php8.5-fpm
sudo journalctl -u php8.5-fpm -n 50 --no-pagerIf the service has failed, correct the error shown in its log, then restart it:
sudo systemctl restart php8.5-fpmIf PHP-FPM is running, compare the socket in fastcgi_pass with the pool’s listen setting. For example, Nginx cannot use /run/php/php8.3-fpm.sock when the pool listens on /run/php/php8.5-fpm.sock.
On Ubuntu and Debian, an existing PHP location can use the packaged snippet with the correct socket:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}Keep your site’s existing routing rules and change the socket in the PHP location that already handles requests. The snippets/fastcgi-php.conf file is supplied by Ubuntu and Debian packages; other installations may use a different FastCGI configuration.
After correcting the socket path, test and reload Nginx:
sudo nginx -t && sudo systemctl reload nginxOur guide on installing PHP on Ubuntu covers the PHP-FPM setup in more detail.
Fix Socket Permissions
If the socket exists but the log reports Permission denied, check the Nginx worker user and the permissions along the socket path:
sudo nginx -T 2>&1 | grep -E '^user[[:space:]]'
namei -l /run/php/php8.5-fpm.sockOn a standard Ubuntu or Debian installation, Nginx workers run as www-data. That user needs permission to traverse each parent directory and write to the socket. In the PHP-FPM pool configuration, these settings allow www-data to connect:
listen.owner = www-data
listen.group = www-data
listen.mode = 0660Use the account and pool configured for your site. If listen.acl_users or listen.acl_groups is set, PHP-FPM ignores listen.owner and listen.group; check the PHP-FPM configuration reference
before changing those settings.
Test the PHP-FPM configuration before restarting the service:
sudo php-fpm8.5 -t && sudo systemctl restart php8.5-fpmPHP-FPM recreates the socket when it starts. Changing its permissions with chmod alone does not make a lasting fix. Do not make the socket world-writable. If Unix permissions look correct, check for SELinux or AppArmor denials rather than disabling the security policy.
Raise the Buffer Size for Large Response Headers
The upstream sent too big header message means the upstream response header did not fit into the buffer Nginx reserves for it. Large Set-Cookie headers or a long Location header can cause this.
The proxy_buffer_size directive
controls the initial response buffer. Its default is one memory page, usually 4 KB or 8 KB. For an HTTP upstream, add the following settings to the existing location that contains proxy_pass:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_buffer_size 16k;
proxy_buffers 4 16k;
}For PHP-FPM, use the corresponding fastcgi_buffer_size and fastcgi_buffers directives
in the existing PHP location:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
}These examples allow a response header of about 16 KB. Increase the initial buffer only as far as your application needs. The other buffers hold response data; adding more of them does not allow a larger header. Larger buffers can also increase memory use across concurrent requests. If the application is sending unexpectedly large cookies, fix that behavior as well.
Test the configuration and reload Nginx, then repeat the failing request:
sudo nginx -t && sudo systemctl reload nginxRaise proxy_buffers whenever you raise proxy_buffer_size. With the default 8 4k buffers, a 16k proxy_buffer_size makes nginx -t fail with "proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer. You can also get this error from an explicit proxy_busy_buffers_size inherited from another block. The FastCGI directives follow the same rule with fastcgi_buffers and fastcgi_busy_buffers_size.
These are upstream response headers. If the page instead says 400 Request Header Or Cookie Too Large, follow the request header and cookie fix
.
Troubleshooting
502 errors appear only under load
Match the Nginx errors to application and system logs at the same time. A worker crash, an out-of-memory kill, or an exhausted connection queue can cause intermittent failures. On PHP sites, server reached pm.max_children means the pool has reached its worker limit. Check memory use and slow requests before increasing that limit. A saturated pool that leaves requests waiting can also produce a 504.
The upstream uses the wrong protocol
Check the scheme in proxy_pass. Using https:// for a service that only speaks HTTP causes a TLS handshake failure. For an HTTPS upstream that requires a server name, configure proxy_ssl_server_name on; and, if necessary, proxy_ssl_name for the name it expects. A PHP-FPM endpoint needs fastcgi_pass, not proxy_pass.
Permission denied when connecting to a TCP upstream
On Fedora, RHEL, and derivatives, SELinux can block Nginx from connecting to the upstream port. The error log then shows a message such as connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream. When you run sudo ausearch -m avc -ts recent, the output should contain a name_connect denial for nginx. To allow Nginx to make outbound network connections, run sudo setsebool -P httpd_can_network_connect 1. The -P option keeps the setting after you reboot the server.
502 after recreating Docker containers
Run connection checks from the Nginx container. In a normal separate container network namespace, 127.0.0.1 points to the Nginx container itself. Containers on the same user-defined network can reach each other by name; use the backend’s container port rather than assuming its published host port applies. If Nginx resolved a hostname when it loaded its configuration, it may still have an old container IP. Reload Nginx after the backend is ready, or configure DNS re-resolution with an upstream resolve parameter, a shared memory zone, and a resolver. This dynamic upstream setup is available in open-source Nginx 1.27.3 and later; see the upstream module documentation
.
The error page shows Cloudflare branding
Cloudflare can pass through an origin 502 or generate an error itself. Its 502/504 troubleshooting guide
explains that a Cloudflare-branded page normally means the origin returned the error. Check the origin logs for the failed request. When testing the origin directly, preserve the requested hostname and HTTPS server name, for example with curl’s --resolve option. Requesting a bare IP can reach a different virtual host. Keep your normal origin access restrictions and certificate checks in place.
Increasing timeouts did not fix the 502
A timeout change does not correct a refused connection, missing socket, invalid response, or oversized header. A 504 means Nginx timed out waiting for the upstream, either while connecting or while exchanging data. If the error log says upstream timed out, investigate that delay. The HTTP status codes cheatsheet
compares the common server errors.
Restarting Nginx did not help
Check the upstream and the matching error log entry before restarting more services. Nginx only needs a reload when you change its configuration. The Nginx restart guide
explains how to test and apply those changes.
Conclusion
After applying a fix, repeat the request that failed and check for new error log entries. For more about upstream addresses, protocols, and forwarded headers, see our guide on using Nginx as a reverse proxy .
Tags
Linuxize Weekly Newsletter
A quick weekly roundup of new tutorials, news, and tips.
About the authors

Dejan Panovski
Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.
View author page