Fix: 502 Bad Gateway from Nginx
Nginx is running but could not get a valid response from your application. Read the error log, check that the app is listening where proxy_pass points, then work through sockets, SELinux, Docker and timeouts.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
502 Bad Gateway
nginx/1.24.0
A 502 means Nginx itself is fine (it sent you the page) but the upstream it proxies to did not give a usable answer: the connection was refused, timed out, was reset, or the reply was not valid HTTP. The cause is almost never in Nginx; it is in whatever proxy_pass points at, or in the path between them. The error log says which.
Step 1: Read the error log
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo tail -n 30 /var/log/nginx/error.log
connect() failed (111: Connection refused) while connecting to upstream: nothing is listening on that address and port. Go to step 2.connect() to unix:/run/app.sock failed (13: Permission denied)or(2: No such file or directory): Unix socket problem. Step 3.connect() to 127.0.0.1:3000 failed (13: Permission denied): on RHEL, Fedora, Rocky or Alma this is SELinux. Step 4.upstream timed out (110: Connection timed out): the app accepted the connection but did not answer in time. Step 5. (A pure timeout often shows as 504 instead.)upstream prematurely closed connectionorrecv() failed (104: Connection reset by peer): the app crashed or killed the connection while handling the request. Look at the application’s own log.upstream sent too big header: the app’s response headers (usually a huge cookie or session) exceed Nginx’s buffer. Step 6.no live upstreams: every server in anupstream { }block is marked failed. Fix the servers, then it recovers on its own.
Step 2: Is the app actually listening?
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo ss -ltnp | grep 3000 # anything on the port?
curl -I http://127.0.0.1:3000/ # ask the app directly, from the server
systemctl status myapp # if it runs as a service
journalctl -u myapp -n 50 --no-pager
grep -R proxy_pass /etc/nginx/sites-enabled/ /etc/nginx/conf.d/
Compare the port in proxy_pass with what ss shows. Common mismatches: the app moved to another port after a config change, it listens only on IPv6 ([::1]:3000) while Nginx connects to 127.0.0.1, or it binds to localhost which resolved differently. Writing proxy_pass http://127.0.0.1:3000; with the IP, and making the app bind to the same, removes the ambiguity. If the app is simply down, start it and fix the reason it stopped, then make sure its service has Restart=on-failure.
Step 3: Unix sockets
Gunicorn, uWSGI, PHP-FPM and Puma are often reached through a socket file. Nginx (running as www-data or nginx) must be able to reach and write to it:
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
ls -la /run/app.sock
sudo -u www-data test -w /run/app.sock && echo writable || echo not writable
Fix by having the app create the socket with a group Nginx belongs to and mode 660 (Gunicorn: --umask 007 and run under a group shared with Nginx; PHP-FPM: listen.owner, listen.group and listen.mode in the pool config). Also check that every parent folder is traversable; a socket under /home/ana/ fails if that folder is 700. For PHP sites, a 502 after an upgrade usually means Nginx points at php8.1-fpm.sock while the installed version is php8.3-fpm.sock.
Step 4: SELinux on RHEL-family systems
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
getenforce
sudo ausearch -m avc -ts recent | grep nginx
sudo setsebool -P httpd_can_network_connect 1
By default SELinux forbids the web server from opening outgoing network connections, which is exactly what proxying is. The boolean above allows it. Careful: -P makes the change permanent and applies to every web server process on the host; sudo setsebool -P httpd_can_network_connect 0 restores the default. For sockets in unusual paths, restorecon -Rv /path or a proper semanage fcontext rule is the fix rather than disabling SELinux.
Step 5: Timeouts and slow starts
Nginx waits 60 seconds for the upstream to send a response (proxy_read_timeout) and 60 seconds to connect (proxy_connect_timeout). Reports and uploads that legitimately take longer need more, inside the location:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
A 502 that appears only for a few seconds after each deploy is the app still starting while Nginx already forwards traffic. Make the deploy wait for the app’s health endpoint before switching, or run two instances in an upstream block so one is always up.
Step 6: Headers too large
location / {
proxy_pass http://127.0.0.1:3000;
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
}
Raising the buffers hides the symptom. Better to find out why the app sends 10 KB of headers; a session cookie that stores the whole user object is the usual answer.
Step 7: Docker
- If Nginx runs on the host and the app in a container, the container must publish the port (
-p 127.0.0.1:3000:3000orports:in Compose) and the process inside must listen on0.0.0.0, not127.0.0.1, or it is unreachable from outside the container. - If both run in Compose,
proxy_passshould use the service name (http://app:3000) and the container’s internal port, and both services must share a network. Container names are resolved when Nginx starts; if the app container is recreated with a new IP, Nginx may keep the old one until it is reloaded. Settingresolver 127.0.0.11;and putting the upstream in a variable makes Nginx re-resolve. docker compose logs appshows whether the app crashed on start.
Apply and verify
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
sudo nginx -t
sudo systemctl reload nginx
curl -I https://app.example.com/
sudo tail -f /var/log/nginx/error.log
Careful: reload replaces the running configuration; keep a copy of the file you changed so you can put it back and reload again if a new problem appears. If the 502 is intermittent, correlate timestamps in the Nginx error log with the application log: memory limits (OOM kills), worker restarts and database timeouts all show up there first.
Official documentation
- ngx_http_proxy_module (timeouts, buffers, proxy_pass)
- Nginx debugging log
- Gunicorn deployment guide (socket and Nginx example)
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0279 What is a reverse proxy? A reverse proxy is a server that sits in front of your apps, receives all requests and forwards them to the right… Beginner · Explains an idea · Any system Article Web servers & reverse proxies 15 Oct 2026
- 0408 What a reverse proxy is and when you need one A reverse proxy is a server that sits in front of your application, accepts requests from the internet and forwards them to… Beginner · Explains an idea · Any system Article Web servers & reverse proxies 15 Oct 2026
- 0409 Nginx as a reverse proxy with HTTPS Put Nginx in front of an app on localhost: a server block that proxies to the port, forwards the client headers, supports… Intermediate · Step by step · Linux Article Deploying sites · Web servers & reverse proxies 15 Oct 2026
- 0410 Caddy: HTTPS reverse proxy in five lines Caddy gets and renews TLS certificates on its own. Install it, write a Caddyfile with your domain and the port your app… Beginner · Step by step · Linux Article Deploying sites · Web servers & reverse proxies 15 Oct 2026
- 0411 CORS explained, and how to fix the browser error The browser blocks a request from one origin to another unless the server allows it with Access-Control headers. Fix it in the… Intermediate · Step by step · Any system, Browser Article Making requests · Web servers & reverse proxies 15 Oct 2026
- 0412 Deploy a static site: from folder to a URL Take a folder of HTML, CSS and JavaScript and publish it at your own domain with HTTPS on a small Linux server… Beginner · Guided project · Linux, macOS Article Deploying sites · Web servers & reverse proxies 15 Oct 2026
Repos in the wild
- Caddy: the web server with automatic HTTPS caddyserver/caddy Caddy is an open-source web server and reverse proxy written in Go that obtains and renews TLS certificates on its own, with a configuration file short enough to memorize and HTTP/1, 2 and 3 support out of the box. GitHub
- Traefik: a reverse proxy that configures itself from Docker and Kubernetes traefik/traefik Traefik is an open-source edge router that discovers services from Docker labels, Kubernetes resources and other providers and routes traffic to them, with automatic Let's Encrypt certificates and a dashboard. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +6,001 chars