nginx 502 Bad Gateway: Upstream Connection Refused
When nginx returns 502 and the error log shows connect() failed (111: Connection refused), the upstream service is not listening. This guide shows how to confirm the refused upstream, locate the active vhost, and choose between restarting the service or fixing the proxy_pass port.
Rao Aadil, India 9 min read
What the 502 and 'connection refused' actually tell you
An nginx 502 bad gateway upstream connection refused error in the error log almost always means nginx could not connect to the backend service. A 502 Bad Gateway says nginx, acting as a proxy or gateway, received an invalid response from the upstream server. That is not a 504 Gateway Timeout; a 504 means nginx waited too long for a response. Here the log line shows connect() failed (111: Connection refused) while connecting to upstream. More specific: nginx tried to open a TCP connection to the upstream address, but nothing accepted it.
On Linux and most Unix-like systems, errno 111 is ECONNREFUSED. The operating system sends a reset or refusal when no process is listening on the target IP and port. A firewall can cause the same if it sends an RST. In the vast majority of on-server debugging, though, it just means the backend service is not running or is listening on a different port.
The app "works on the developer machine" is a common confusion. That does not mean the port is open on the server where nginx runs. proxy_pass might point to 127.0.0.1:4000, but the application could be running on the developer's workstation, not on the nginx host. Or the service could be bound to a different interface or port. The error log gives the exact address nginx tried. That is where the debug starts.
“nginx is running, the site is down, and the error page tells you nothing about which of the two sides is at fault.”
It reads the error log and the vhost, then checks what is actually listening on the upstream port. Usually the app moved and the proxy did not, and it can tell you which one changed.
It reads the real state of the server before it says anything, shows you the exact command, and waits for your approval. Windows and Linux, over the SSH access you already have.
Read the nginx error log to get the upstream address
Start with the nginx error log. The line contains the upstream URL, the server name, and the client request. A typical line looks like this:
[error] 1234#1234: *56 connect() failed (111: Connection refused) while connecting to upstream, client: 192.0.2.10, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:4000/", host: "example.com"
On Linux, the default error log is usually /var/log/nginx/error.log. Tail the last lines and grep for the refusal pattern:
sudo tail -n 50 /var/log/nginx/error.log
sudo grep 'connect() failed' /var/log/nginx/error.log
If you are following the log live:
sudo tail -f /var/log/nginx/error.log
On Windows, the log location depends on where you extracted nginx. A common path is C:\nginx\logs\error.log. Use PowerShell to view the tail:
Get-Content 'C:\nginx\logs\error.log' -Tail 50 -Wait
To search for the refusal pattern with PowerShell:
Select-String -Path 'C:\nginx\logs\error.log' -Pattern 'connect\(\) failed'
With cmd, you can use:
findstr "connect() failed" C:\nginx\logs\error.log
If nginx is installed under C:\Program Files\nginx, adjust the path. The line's upstream: "http://127.0.0.1:4000/" tells you exactly which IP and port nginx attempted to connect to. Keep that value handy: 127.0.0.1:4000.
Locate which config file is actually serving the domain
Find which server block is handling the request before changing anything. A domain can be configured in multiple places: sites-available, sites-enabled, or conf.d. If no server_name matches, nginx falls back to the default server. That often shows a "Welcome to nginx" page instead of your app.
On Linux, search for the domain in the active configuration directories:
sudo grep -r "server_name example.com" /etc/nginx/sites-enabled /etc/nginx/conf.d /etc/nginx/sites-available
The file that is actually included and loaded will appear under sites-enabled or conf.d. To see the effective configuration that nginx is using, run:
sudo nginx -T
This prints the merged config with all includes resolved. Grep that output for server_name or proxy_pass.
On Windows, use PowerShell to search the configuration files. Assuming nginx is installed at C:\nginx and your server blocks are in C:\nginx\conf\conf.d:
Select-String -Path 'C:\nginx\conf\conf.d\*.conf' -Pattern 'server_name\s+example.com'
If you use sites-enabled on Windows, include that path too. With cmd, findstr /S "server_name example.com" C:\nginx\conf\*.conf searches recursively. The nginx.conf file includes these directories; check the include directives to see where active vhosts live.
If the domain does not appear in any active file, the default server is likely serving the request. That default server might have a proxy_pass to a different port, or none at all. If the default server tries to proxy to a dead backend, you get the 502. If you manage many vhosts, an nginx and IIS management tool that identifies the active config file and surfaces the exact error log line for the request will save time here.
Check whether anything is listening on the upstream port
Take the upstream address from the error log, for example 127.0.0.1:4000, and check if any process is listening on that port.
On Linux, use ss or netstat:
ss -ltnp | grep ':4000'
or if ss is not available:
sudo netstat -ltnp | grep ':4000'
You should see a line like LISTEN 0 128 127.0.0.1:4000 0.0.0.0:* users:(("node",pid=1234,fd=18)). The process name and PID tell you what is listening. If nothing is returned, nothing is listening on that address and port.
On Windows, use netstat and findstr:
netstat -ano | findstr :4000
If a process is listening, the output shows the local address, state LISTENING, and the PID. To find the process name:
tasklist /FI "PID eq 1234"
Replace 1234 with the actual PID. Using PowerShell, you can combine both steps:
Get-NetTCPConnection -LocalPort 4000 | Select-Object LocalAddress,LocalPort,State,OwningProcess
Get-Process -Id (Get-NetTCPConnection -LocalPort 4000).OwningProcess
You want a match between the listener and the upstream address in proxy_pass. If the error log says upstream: "http://127.0.0.1:4000/", but the listener is on 0.0.0.0:3000 or on a different IP like 10.0.0.5:4000, nginx will get connection refused. Verify the port number exactly. proxy_pass http://127.0.0.1:4000; with no trailing slash is usually fine, but a missing or different port often causes this.
Fix the cause: restart the missing service or correct the proxy_pass port
No listener on the upstream port means the backend service is stopped or crashed. Start it and check its status.
On Linux with systemd, start the service:
sudo systemctl start your-app
sudo systemctl status your-app
Use journalctl -u your-app -f to see its logs for startup errors.
On Windows, start a Windows service with PowerShell:
Start-Service -Name "YourService"
Or use the Services GUI (services.msc) to start it manually. If the upstream is an IIS application pool, start it with:
Start-WebAppPool -Name "DefaultAppPool"
For Tomcat running as a service, use:
Start-Service -Name "Tomcat9"
After starting the backend, re-run the port listening check to confirm it is now listening on the expected address and port.
If the service is running but the port is wrong, edit the nginx configuration. On Linux, the file is usually in /etc/nginx/sites-available/example.com (or whatever file you found earlier). Change the proxy_pass line to the correct port:
location / {
proxy_pass http://127.0.0.1:NEW_PORT;
}
On Windows, edit the corresponding file, often C:\nginx\conf\conf.d\example.com.conf, and update the same line.
After any config change, always test before reloading. On both Linux and Windows, nginx uses the same executable:
sudo nginx -t
or on Windows from the nginx directory:
C:\nginx\nginx.exe -t
If the test passes, reload nginx without dropping connections. On Linux:
sudo systemctl reload nginx
On Windows:
C:\nginx\nginx.exe -s reload
Check the error log again; if the 502 disappeared, the upstream is now reachable.
When the upstream is IIS or Tomcat on Windows
When nginx on Windows proxies to IIS or Tomcat, a refused connection often means the IIS site binding or Tomcat connector does not match the port in proxy_pass.
For IIS, check the site bindings with PowerShell:
Import-Module WebAdministration
Get-IISSite | Select-Object Name, State, Bindings
Look for a binding like http *:4000:. If the site is stopped, start it with Start-IISSite -Name "YourSite". Also check the application pool state:
Get-WebAppPoolState -Name "DefaultAppPool"
If the state is Stopped or Stopping, start it:
Start-WebAppPool -Name "DefaultAppPool"
A crashed app pool can also produce connection refused if the worker process is not running.
For Tomcat, verify the connector port in server.xml, usually at C:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\server.xml. Look for <Connector port="8080" protocol="HTTP/1.1" ... />. If nginx is proxying to port 4000 but Tomcat listens on 8080, change the proxy_pass to http://127.0.0.1:8080 or change the Tomcat connector port. Then restart the Tomcat service:
Restart-Service -Name "Tomcat9"
Cross-reference the upstream address from the nginx error log. The port there must match the IIS binding or Tomcat connector port.
Prevent 502s from misconfigured proxies
Stop connection refused 502s by catching the port mismatch before it goes live. Add nginx -t to your CI pipeline or pre-deploy checks. The test command is the same on Linux and Windows; running it during deployment prevents a broken config from being reloaded.
If your environment provides root-owned wrapper scripts for nginx reloads, use them instead of raw shell commands. That avoids accidental mistakes like reloading with an untested config. The wrapper should run nginx -t first and only reload if it passes.
Monitor the nginx error log for connect() failed (111: Connection refused) while connecting to upstream lines. Alert on this pattern so you know the moment a backend port changes. On Linux you can write a simple log watcher; on Windows, use the Windows Event Log if your nginx is configured to log there, or monitor the log file with a scheduled task.
Document the upstream port mapping in your repository. A simple UPSTREAM_PORTS.md that lists each domain and the backend IP:port prevents the next engineer from guessing and creating a mismatch.