Fix HTTP 502/504 Bad Gateway Errors on Linux Web Servers
502 and 504 errors mean your reverse proxy cannot reach a healthy backend. This guide walks through real Linux checks—Nginx/Apache upstreams, PHP-FPM sockets, timeouts, and resource limits—so you can restore service or know when to book Fixwebnode server support.
If your site suddenly shows HTTP 502 Bad Gateway or 504 Gateway Timeout, visitors cannot complete checkouts, forms, or logins—and the reverse proxy is telling you the upstream application failed or never answered.
This practical runbook is for homeowners running a small VPS and small businesses on Linux (Ubuntu/Debian or RHEL-family) with Nginx or Apache in front of PHP-FPM, Node, or another app process. You will diagnose the most common root causes with copy-paste commands, apply safe fixes, and verify recovery. When the stack is too tangled or production risk is high, Fixwebnode server support can take over the same class of work without turning a brief outage into a multi-day rebuild.
Why 502 and 504 matter on Linux web servers
A 502 usually means the proxy reached something broken (crashed worker, closed socket, refused connection, or bad response). A 504 means the proxy waited for the upstream and gave up. Both are gateway problems—not “the browser is wrong.” On Linux they almost always trace to process health, socket/port paths, timeouts, or exhausted CPU/RAM/file descriptors.
Fixwebnode focuses on direct specialist server support for these outages across the regions listed on our service areas page, including teams working with Australian-hosted Linux estates. The steps below stay on diagnosis and repair so you can act immediately.
Common issues that trigger 502/504
These problems look similar in the browser but differ in logs and process state. Treat each as a separate root cause.
- Upstream process down or socket missing — Nginx/Apache proxies to PHP-FPM or an app on a Unix socket or localhost port that is not listening. Symptom: intermittent or total 502; error log lines like connect() failed (111: Connection refused) or No such file or directory for a
.sockpath. - Proxy read/send timeouts too aggressive for slow backends — Heavy pages, cold caches, or locked database queries exceed
proxy_read_timeout/fastcgi_read_timeout. Symptom: consistent 504 under load or on specific slow routes; access log shows long request times then 504. - Worker or memory exhaustion — PHP-FPM
pm.max_children, Apache MPM limits, or the OOM killer stops workers. Symptom: 502 spikes during traffic peaks;dmesgshows out-of-memory; pool status shows busy workers at the ceiling. - Wrong upstream host, port, or SELinux/AppArmor denial — Config points at an old container IP, wrong port after a redeploy, or policy blocks the proxy from connecting. Symptom: 502 after a deploy or reboot while the app “looks fine” when tested alone.
Before you change anything: capture evidence
Work from the proxy error log and confirm what is listening. On most Ubuntu/Debian Nginx stacks:
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/apache2/error.log 2>/dev/null
sudo ss -lntp | egrep ':(80|443|8080|3000|9000)\b'
sudo systemctl status nginx apache2 php*-fpm --no-pager
Note the exact upstream path (for example unix:/run/php/php8.2-fpm.sock or 127.0.0.1:3000). You will match that path in the fixes below. Prefer a maintenance window for restarts on busy shops.
Issue 1 — Upstream process down or socket path mismatch
The reverse proxy is healthy, but PHP-FPM or the app is stopped, crashed, or listening on a different socket than the vhost expects. This is the classic sudden 502 after a package upgrade that changed PHP minor versions.
Step 1 — Confirm the configured upstream
sudo grep -R "fastcgi_pass\|proxy_pass\|unix:" /etc/nginx/sites-enabled/ /etc/nginx/conf.d/ 2>/dev/null
sudo grep -R "ProxyPass\|SetHandler.*proxy:fcgi" /etc/apache2/sites-enabled/ 2>/dev/null
Record the socket or host:port string exactly.
Step 2 — See whether that endpoint exists and is owned correctly
ls -l /run/php/ /var/run/php/ 2>/dev/null
sudo ss -lntp | grep -E 'php-fpm|node|gunicorn|uwsgi'
ps aux | grep -E 'php-fpm|node|gunicorn' | grep -v grep
If the socket file is missing or the port is closed, the proxy cannot forward traffic.
Step 3 — Start or restart the backend cleanly
sudo systemctl restart php8.2-fpm
# adjust version: php8.1-fpm, php8.3-fpm, etc.
sudo systemctl restart nginx
sudo systemctl is-active php8.2-fpm nginx
For a Node app under systemd:
sudo systemctl restart your-app.service
sudo journalctl -u your-app.service -n 80 --no-pager
Step 4 — Align socket names after PHP upgrades
If Nginx still points at php8.1-fpm.sock but only php8.3-fpm.sock exists, either update the vhost fastcgi_pass or enable the older pool. Then test config and reload:
sudo nginx -t && sudo systemctl reload nginx
curl -sI http://127.0.0.1/ | head -n 5
When to call Fixwebnode: multiple PHP versions, custom pool directories, or containers where the socket lives on another network namespace. Mis-editing pools can take every site on the box offline.
Issue 2 — Gateway timeouts from slow upstreams
The backend eventually works, but the proxy abandons the request. You see 504 on reports, exports, or first-hit pages while short static requests still return 200.
Step 1 — Prove duration in access logs
sudo tail -n 50 /var/log/nginx/access.log
# If using a custom log format with request_time, filter slow lines:
sudo awk '$NF+0 > 5 {print}' /var/log/nginx/access.log | tail
Step 2 — Raise proxy/FastCGI timeouts deliberately (not infinitely)
Inside the affected Nginx location (example values—tune to your SLA):
proxy_connect_timeout 10s;
proxy_send_timeout 90s;
proxy_read_timeout 90s;
fastcgi_connect_timeout 10s;
fastcgi_send_timeout 90s;
fastcgi_read_timeout 90s;
Validate and reload:
sudo nginx -t && sudo systemctl reload nginx
Apache equivalent ideas: ProxyTimeout 90 and PHP-FPM request_terminate_timeout must stay consistent so PHP does not kill work the proxy still expects.
Step 3 — Fix the real slowness, not only the symptom
sudo tail -n 100 /var/log/mysql/error.log 2>/dev/null
sudo tail -n 100 /var/log/php8.2-fpm.log 2>/dev/null
# Example: show MySQL processlist if mysql client is available
mysql -e "SHOW FULL PROCESSLIST\G" 2>/dev/null | head -n 80
Look for long queries, missing indexes, external API calls without timeouts, or disk saturation:
iostat -xz 1 5 2>/dev/null || cat /proc/loadavg
df -h
free -h
Step 4 — Verify the slow route
curl -o /dev/null -s -w "HTTP %{http_code} time %{time_total}\n" https://your-domain.example/slow-path
You want HTTP 200 with time under your new timeout. If time stays near the ceiling, keep investigating the app/database rather than pushing timeouts to several minutes in production.
When to call Fixwebnode: 504s tied to database locks, shared hosting limits you cannot raise, or application code paths you do not maintain. Timeout inflation alone can mask data-layer failures.
Issue 3 — Resource exhaustion and worker ceilings
Under concurrent checkouts or cron storms, every PHP-FPM child is busy, new connections queue, and the proxy returns 502/504. Memory pressure may also invoke the OOM killer.
Step 1 — Check OOM and pool saturation
sudo dmesg -T | egrep -i 'oom|killed process' | tail -n 20
sudo systemctl status php8.2-fpm --no-pager
ps -o pid,user,%mem,%cpu,cmd -C php-fpm8.2 --sort=-%mem | head
If your pool uses a status page, enable it temporarily only on localhost and inspect active processes; otherwise count FPM workers vs pm.max_children in the pool file:
sudo grep -E '^pm\.|max_children|start_servers' /etc/php/*/fpm/pool.d/*.conf
Step 2 — Size workers from RAM, not guesswork
Rough guide: average PHP worker RSS × pm.max_children must leave headroom for MySQL/Nginx and the OS. Example inspection:
free -h
ps -C php-fpm8.2 -o rss= | awk '{s+=$1} END {print "approx_MB", s/1024}'
Then edit the pool (path varies), for example lower or raise pm.max_children, set pm = dynamic, and define sane pm.start_servers / pm.max_spare_servers. Restart FPM:
sudo systemctl restart php8.2-fpm
sudo systemctl status php8.2-fpm --no-pager
Step 3 — Watch file descriptors and backlog
ulimit -n
sudo sysctl net.core.somaxconn
sudo ss -s
Persistent SYN-RECV piles or FPM max_children warnings in logs mean you still need capacity or traffic shaping (rate limits, queue, CDN) rather than blind restarts.
Step 4 — Confirm recovery under light load
for i in 1 2 3 4 5; do curl -s -o /dev/null -w "%{http_code}\n" https://your-domain.example/; done
All lines should be 200/301/302—not 502/504. Re-check error logs for fresh upstream failures.
When to call Fixwebnode: recurring OOM on a host that also runs databases, or multi-site FPM pools fighting for the same RAM. Capacity planning mistakes take sites down again at the next traffic spike.
Issue 4 — Wrong upstream target, firewall, or mandatory access control
After a redeploy, Docker publish change, or hardening pass, the proxy still points at an old port or is denied by firewall/SELinux.
Step 1 — Test the upstream independently of the public vhost
curl -sI http://127.0.0.1:3000/ | head -n 10
curl --unix-socket /run/php/php8.2-fpm.sock http://localhost/ | head
If local curl fails, fix the app first. If local curl works but public HTTPS returns 502, the proxy config or policy layer is wrong.
Step 2 — Correct proxy_pass / port and reload
sudo nginx -t && sudo systemctl reload nginx
# or
sudo apache2ctl configtest && sudo systemctl reload apache2
Step 3 — Firewall and local policy checks
sudo iptables -L -n | head -n 40
sudo nft list ruleset 2>/dev/null | head -n 40
sudo ufw status verbose 2>/dev/null
# RHEL-family SELinux denials:
sudo ausearch -m avc -ts recent 2>/dev/null | tail
sudo getenforce 2>/dev/null
Open only required ports; do not disable SELinux permanently as a “fix.” If denials reference httpd/nginx connecting to a port, use the proper boolean or fcontext rather than setenforce 0 on a production host.
Step 4 — DNS and IPv6 surprises
If proxy_pass uses a hostname, ensure it resolves to the intended address:
getent ahosts backend.internal
ping -c 1 backend.internal
Prefer 127.0.0.1 or a Unix socket for same-host backends to avoid flaky resolver paths.
When to call Fixwebnode: mixed Docker/host networking, corporate firewalls, or SELinux policies you did not author. Guessing at booleans can open unintended access.
Related Linux maintenance that prevents repeat outages
Broken package sources can block PHP-FPM or Nginx security updates and leave you stuck on a bad worker build. If apt update fails because sources.list is corrupted, use specialist help such as Fix Corrupted Linux APT Sources.list - Alabama Expert Support rather than pasting random mirrors. For broader hardening—TLS, SSH, reverse proxies, and full-stack Linux admin—see Secure Linux Admin & Full-Stack Infrastructure Australia.
When DIY is enough vs when to book Fixwebnode
DIY is reasonable when you have SSH, a recent backup or snapshot, a single clear error-log signature (missing socket, obvious timeout, simple port mismatch), and you can reload services off-peak. Follow the numbered steps, keep a console session open, and revert config with version control or a copied file if nginx -t fails.
Book Fixwebnode when any of these apply: repeated 502/504 after “successful” restarts; multi-site servers where one pool change can cascade; database-backed timeouts you cannot explain; container overlays you do not control; or a live revenue site where extended debugging is riskier than a guided recovery. Direct specialist server support is the framing here—not a marketplace of bids. Start from the server support step-by-step landing page and outline your distro, proxy (Nginx/Apache), backend (PHP-FPM version or app unit), and the exact log lines.
Geography is straightforward: check all service areas for coverage notes if your VPS or office uplink sits in a listed metro or region, including Australian-hosted infrastructure common to many small business stacks.
Closing: restore the gateway, then harden the path
HTTP 502/504 on Linux almost always reduce to a proxy that cannot obtain a timely, valid response from PHP-FPM, Node, or another upstream—because the process is down, the socket/port is wrong, timeouts are tighter than the work, workers are exhausted, or policy blocks the hop. Use the commands above to prove which case you have, apply the matching fix, and verify with curl and error logs before you walk away.
If you want a specialist to finish diagnosis, stabilize production, and leave you with a cleaner proxy/backend layout, open a conversation through Fixwebnode server support and share your symptoms and log excerpts. Getting the gateway healthy again is the priority; preventing the next silent socket mismatch is the lasting win.