{"id":539,"date":"2026-09-07T10:47:02","date_gmt":"2026-09-07T10:47:02","guid":{"rendered":"https:\/\/www.vps.tc\/blog\/?p=539"},"modified":"2026-09-07T09:21:25","modified_gmt":"2026-09-07T09:21:25","slug":"how-to-fix-502-bad-gateway-error","status":"publish","type":"post","link":"https:\/\/www.vps.tc\/blog\/en\/how-to-fix-502-bad-gateway-error\/","title":{"rendered":"How to Fix a 502 Bad Gateway Error"},"content":{"rendered":"<div class=\"aiw-toc\" style=\"border:1px solid #dbe3ea;border-radius:8px;padding:16px 20px;margin:0 0 28px\"><strong>Table of Contents<\/strong><\/p>\n<ol style=\"margin:10px 0 0;padding-left:22px\">\n<li><a href=\"#what-does-a-502-bad-gateway-error-mean\">What does a 502 Bad Gateway error mean?<\/a><\/li>\n<li><a href=\"#what-usually-causes-a-502\">What usually causes a 502?<\/a><\/li>\n<li><a href=\"#how-i-troubleshoot-a-502-bad-gateway-error\">How I troubleshoot a 502 Bad Gateway error<\/a><\/li>\n<li><a href=\"#reading-nginx-apache-and-application-logs\">Reading Nginx, Apache, and application logs<\/a><\/li>\n<li><a href=\"#502-500-504-and-503-are-not-interchangeable\">502, 500, 504, and 503 are not interchangeable<\/a><\/li>\n<li><a href=\"#fixing-a-502-behind-docker-and-a-reverse-proxy\">Fixing a 502 behind Docker and a reverse proxy<\/a><\/li>\n<li><a href=\"#why-a-cdn-can-show-a-502\">Why a CDN can show a 502<\/a><\/li>\n<li><a href=\"#if-you-are-a-visitor-not-the-administrator\">If you are a visitor, not the administrator<\/a><\/li>\n<li><a href=\"#preventing-repeat-502-errors\">Preventing repeat 502 errors<\/a><\/li>\n<li><a href=\"#frequently-asked-questions\">Frequently asked questions<\/a><\/li>\n<li><a href=\"#sources\">Sources<\/a><\/li>\n<\/ol>\n<\/div>\n<h2 id=\"what-does-a-502-bad-gateway-error-mean\">What does a 502 Bad Gateway error mean?<\/h2>\n<p>A 502 Bad Gateway error means that a web server or reverse proxy could not obtain a usable HTTP response from an upstream server. Your request reached the proxy, but Nginx, Apache, a CDN, PHP-FPM, Node.js, or another backend could not connect, respond correctly, or finish the exchange.<\/p>\n<p>The 502 page in your browser does not usually mean your computer is broken. The request may have reached the public Nginx server successfully while the upstream service was stopped, unreachable, closing connections, or returning an invalid response.<\/p>\n<p>HTTP status codes are defined in RFC 9110. A 502 tells us that a gateway or proxy received an invalid response from the upstream. MDN describes it as a server-side response produced while passing a request between the client and the target application.<\/p>\n<h2 id=\"what-usually-causes-a-502\">What usually causes a 502?<\/h2>\n<p>Several separate processes may sit between a reverse proxy and an application. The visitor connects to Nginx, Nginx forwards the request to PHP-FPM or a Node.js application, and that application may call a database or another service. One broken link in that chain can appear as a 502 at the edge.<\/p>\n<ul>\n<li><strong>The upstream service is stopped:<\/strong> PHP-FPM, Gunicorn, uWSGI, Node.js, or a Docker container may have exited.<\/li>\n<li><strong>The socket or port is wrong:<\/strong> Nginx may be looking at <code>127.0.0.1:3000<\/code> while the application listens somewhere else.<\/li>\n<li><strong>A Unix socket is inaccessible:<\/strong> Its owner, group, or permissions may not allow the Nginx user to connect.<\/li>\n<li><strong>The upstream takes too long:<\/strong> The application may be running a slow query, stuck, or unable to finish within the configured interval.<\/li>\n<li><strong>The application returns invalid HTTP:<\/strong> Response headers may be malformed, or the application may close the connection before replying.<\/li>\n<li><strong>Resources are exhausted:<\/strong> RAM pressure, the OOM killer, CPU contention, a full filesystem, or a file descriptor limit can affect the process.<\/li>\n<li><strong>Proxy layers disagree:<\/strong> CDN, load balancer, Nginx, and application timeout or connection settings may not fit together.<\/li>\n<\/ul>\n<p>Restarting Nginx immediately is not a useful diagnostic method. The page may start working after the restart, but the cause is still waiting underneath. I am not fond of pressing the terminal&#8217;s restart button before the logs get a chance to speak.<\/p>\n<h2 id=\"how-i-troubleshoot-a-502-bad-gateway-error\">How I troubleshoot a 502 Bad Gateway error<\/h2>\n<p>First, I identify which layer generated the error. If the domain runs through a CDN, its error page may not match the origin server&#8217;s response. I test the local origin first, then test the request through the external proxy.<\/p>\n<h3>1. Check the service state<\/h3>\n<p>Start with the web server and backend service. Service names vary by distribution, and the PHP version in the command must match the version installed on Debian or Ubuntu.<\/p>\n<pre><code>sudo systemctl status nginx --no-pager\nsudo systemctl status php8.2-fpm --no-pager\nsudo ss -ltnp | grep -E ':(80|443|3000|8000)'<\/code><\/pre>\n<p>Do not look only for the word <em>active<\/em> in <code>systemctl status<\/code>. The start time, main process PID, and latest log lines matter too. The <code>ss<\/code> output shows whether the application is actually listening on the port you expect.<\/p>\n<p>If a service is stopped, inspect its logs before starting it in a controlled way:<\/p>\n<pre><code>sudo journalctl -u php8.2-fpm --since '30 minutes ago' --no-pager\nsudo systemctl restart php8.2-fpm\nsudo systemctl status php8.2-fpm --no-pager<\/code><\/pre>\n<p>That <code>restart<\/code> is not a diagnosis. Even if the error disappears, look through the earlier logs for memory pressure, configuration errors, or an application crash.<\/p>\n<h3>2. Read the Nginx or Apache error logs<\/h3>\n<p>The common Nginx error log is <code>\/var\/log\/nginx\/error.log<\/code>, although a virtual host may define another path. On many Debian Apache installations, the equivalent is <code>\/var\/log\/apache2\/error.log<\/code>.<\/p>\n<pre><code>sudo tail -n 80 \/var\/log\/nginx\/error.log\nsudo journalctl -u nginx --since '15 minutes ago' --no-pager\nsudo nginx -t<\/code><\/pre>\n<p><code>connect() failed<\/code> means Nginx could not connect to the upstream. <code>upstream timed out<\/code> means the connection or response took too long. <code>permission denied<\/code> usually points toward Unix socket or filesystem permissions.<\/p>\n<p><code>nginx -t<\/code> checks configuration syntax. A successful result does not prove that the upstream application is healthy; it only says Nginx can read a syntactically valid configuration.<\/p>\n<h3>3. Connect to the upstream without the proxy<\/h3>\n<p>If the application listens on a TCP port, send a request directly:<\/p>\n<pre><code>curl -i --max-time 10 http:\/\/127.0.0.1:3000\/health\ncurl -i --max-time 10 http:\/\/127.0.0.1:8000\/<\/code><\/pre>\n<p>For a Unix socket, verify the path used in the Nginx configuration. On many Debian PHP-FPM installations, the path looks like <code>\/run\/php\/php8.2-fpm.sock<\/code>, depending on the PHP version.<\/p>\n<pre><code>grep -R -E 'fastcgi_pass|proxy_pass' \/etc\/nginx\/sites-enabled\/ \/etc\/nginx\/conf.d\/ 2&gt;\/dev\/null\nsudo ls -l \/run\/php\/<\/code><\/pre>\n<p>If the direct port request returns the expected status but the domain returns 502, investigate proxy configuration, socket permissions, TLS, and timeouts. If the direct request fails too, leave Nginx alone for the moment and inspect the application service.<\/p>\n<h3>4. Compare socket permissions and users<\/h3>\n<p>PHP-FPM usually creates the FastCGI socket, while Nginx workers may run as another user such as <code>www-data<\/code>. If the socket&#8217;s group and group permissions do not match the Nginx process, the proxy cannot connect.<\/p>\n<pre><code>ps -o user,group,cmd -C nginx\nsudo stat \/run\/php\/php8.2-fpm.sock\nsudo grep -E '^(listen|listen.owner|listen.group|listen.mode)' \/etc\/php\/8.2\/fpm\/pool.d\/www.conf<\/code><\/pre>\n<p>Do not make <code>chmod 777<\/code> your first response. It does not fix the access model; it simply grants far more permission than necessary. The PHP-FPM pool&#8217;s <code>listen.group<\/code> should align with the Nginx user&#8217;s group. After changing it, validate the configuration and reload the service in a controlled way.<\/p>\n<h3>5. Inspect timeouts and resource use<\/h3>\n<p>If a query takes 60 seconds, do not randomly increase the proxy timeout before finding out why it is slow. A full disk, swap activity, or memory pressure can also cause a 502 indirectly.<\/p>\n<pre><code>free -h\ndf -h\nsudo dmesg -T | grep -iE 'oom|killed process|out of memory'\nhtop<\/code><\/pre>\n<p><code>df -h<\/code> shows total filesystem use. I prefer <code>ncdu<\/code> when I need to find the directory consuming it.<\/p>\n<p>In one deployment-related 502 incident, the application was not the problem at all: I had applied an incorrect Nginx configuration to a server I thought was staging. That was the day my red production prompt stopped being negotiable. Now it is <code>nginx -t<\/code> first, reload second.<\/p>\n<p>If the kernel log contains an OOM event, Linux may have killed the application process. Increasing an Nginx timeout will not solve that. Review PHP-FPM worker counts, application memory use, swap, and the VPS&#8217;s available resources together.<\/p>\n<h2 id=\"reading-nginx-apache-and-application-logs\">Reading Nginx, Apache, and application logs<\/h2>\n<p>Each layer answers a different question: did Nginx receive the request, did it connect upstream, did the application process it, and did the database reply? Use the same time window for every log and place the entries side by side.<\/p>\n<table>\n<thead>\n<tr>\n<th>Log or check<\/th>\n<th>Question<\/th>\n<th>Common clue<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Nginx error.log<\/td>\n<td>Did the proxy connect upstream?<\/td>\n<td><code>connect() failed<\/code>, <code>upstream timed out<\/code><\/td>\n<\/tr>\n<tr>\n<td>PHP-FPM journal<\/td>\n<td>Is the worker running and accepting requests?<\/td>\n<td>Crashes, max children, pool, or socket errors<\/td>\n<\/tr>\n<tr>\n<td>Application log<\/td>\n<td>Did the request fail inside the application?<\/td>\n<td>Exceptions, refused connections, memory errors<\/td>\n<\/tr>\n<tr>\n<td>Kernel journal<\/td>\n<td>Did the system kill the process?<\/td>\n<td>OOM, I\/O errors, filesystem warnings<\/td>\n<\/tr>\n<tr>\n<td>CDN or load balancer log<\/td>\n<td>Could the edge reach the origin?<\/td>\n<td>Origin timeout, TLS handshake error<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Do not interpret one log line in isolation. An application-generated 500 and an Nginx-generated 502 point to different layers. The application may have returned HTTP 500; Nginx returns 502 when it cannot connect to the application at all or receives an unusable response.<\/p>\n<h2 id=\"502-500-504-and-503-are-not-interchangeable\">502, 500, 504, and 503 are not interchangeable<\/h2>\n<p>These codes look similar, but the place to investigate changes. Keeping the distinction clear helps you avoid restarting the wrong service.<\/p>\n<table>\n<thead>\n<tr>\n<th>Status code<\/th>\n<th>Meaning<\/th>\n<th>First check<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>500 Internal Server Error<\/td>\n<td>The application or server encountered an unexpected error while processing the request.<\/td>\n<td>Application logs and framework errors<\/td>\n<\/tr>\n<tr>\n<td>502 Bad Gateway<\/td>\n<td>The proxy did not receive a valid response from the upstream.<\/td>\n<td>Upstream port, socket, and proxy error log<\/td>\n<\/tr>\n<tr>\n<td>503 Service Unavailable<\/td>\n<td>The service is temporarily unavailable or not ready to accept the request.<\/td>\n<td>Service state, maintenance mode, and resources<\/td>\n<\/tr>\n<tr>\n<td>504 Gateway Timeout<\/td>\n<td>The proxy did not receive an upstream response in time.<\/td>\n<td>Slow queries, application stalls, and timeouts<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In real systems these statuses overlap a little. A PHP-FPM pool that reaches its resource limit may produce 502, while a long-running backend operation may produce 504. Do not increase <code>proxy_connect_timeout<\/code>, <code>proxy_read_timeout<\/code>, or FastCGI timeouts until you know what each one is waiting for; otherwise the same problem merely appears later.<\/p>\n<h2 id=\"fixing-a-502-behind-docker-and-a-reverse-proxy\">Fixing a 502 behind Docker and a reverse proxy<\/h2>\n<p>On Docker hosts, one mistake I see often is confusing the container port with the host port. When Nginx shares a Docker network with the application, it normally needs the service name and the port inside the container, not the port published on the host.<\/p>\n<pre><code>docker compose ps\ndocker compose logs --tail=100 app\ndocker inspect app --format '{{json .NetworkSettings.Networks}}'<\/code><\/pre>\n<p>If the application listens on port <code>3000<\/code> inside its container, Nginx may need <code>proxy_pass http:\/\/app:3000;<\/code>. If the application listens only on <code>127.0.0.1<\/code> inside the container, it may reject connections from the Docker network, so check the bind address too.<\/p>\n<p>For network and service checks in a Docker setup, see <strong><a href=\"https:\/\/www.vps.tc\/blog\/en\/docker-installation-on-vps-first-container\/\">How to Install Docker on a VPS and Run Your First Container<\/a><\/strong>. A container being listed as running does not prove that its application is healthy; check its health status, application log, and a <code>curl<\/code> request from the same network.<\/p>\n<pre><code>docker compose exec nginx getent hosts app\ndocker compose exec nginx curl -i --max-time 5 http:\/\/app:3000\/health<\/code><\/pre>\n<p>The first command tests service-name resolution. The second tests whether the Nginx container can reach the application. If DNS works but the connection fails, the port or bind address may be wrong. The image must contain <code>curl<\/code>; if it does not, use a temporary diagnostic container on the same network.<\/p>\n<h2 id=\"why-a-cdn-can-show-a-502\">Why a CDN can show a 502<\/h2>\n<p>A CDN adds another layer between the browser and the origin. The browser connects to the CDN, and the CDN connects to your server. If the origin is down, restricted to certain IP addresses, using incompatible TLS settings, or blocking the CDN&#8217;s addresses with a firewall, the CDN may display its own 502 page.<\/p>\n<p>I use this check order:<\/p>\n<ol>\n<li>Request the origin locally using its IP address and the appropriate <code>Host<\/code> header.<\/li>\n<li>Check the CDN&#8217;s origin health check and the exact error time.<\/li>\n<li>Look in the web server access log for the CDN request.<\/li>\n<li>Confirm that the origin firewall allows the CDN&#8217;s published IP ranges.<\/li>\n<li>Check that the origin certificate matches the TLS mode selected by the CDN.<\/li>\n<\/ol>\n<p>For background on the extra layer between a client and an origin, see <strong><a href=\"https:\/\/www.vps.tc\/blog\/en\/what-is-a-proxy-server-and-how-does-it-work\/\">What Is a Proxy Server and How Does It Work?<\/a><\/strong> Your browser can resolve the domain successfully while the CDN still fails to connect to the origin.<\/p>\n<h2 id=\"if-you-are-a-visitor-not-the-administrator\">If you are a visitor, not the administrator<\/h2>\n<p>If you see a 502 on a website, wait a few minutes and try again. Testing from another network can help, but constantly refreshing only adds work to an already unhealthy server. If other sites open normally and only one site fails, the problem is probably on that site&#8217;s side.<\/p>\n<p>If you own the site, include these details in a support request:<\/p>\n<ul>\n<li>The full URL and the time when the error appeared<\/li>\n<li>Whether it affects every page or only one endpoint<\/li>\n<li>The browser, mobile application, or API client involved<\/li>\n<li>Any request ID, CDN Ray ID, or timestamp from the server log<\/li>\n<li>Recent deployment, DNS, SSL, firewall, or PHP version changes<\/li>\n<\/ul>\n<p>Clearing local DNS or browser cache cannot revive a stopped upstream. Those steps make sense when you suspect an old proxy response or a local resolution problem.<\/p>\n<h2 id=\"preventing-repeat-502-errors\">Preventing repeat 502 errors<\/h2>\n<p>I want a signal before the error reaches visitors. In my basic monitoring setup, the HTTP request, application service, resource usage, and logs are observed separately.<\/p>\n<ul>\n<li><strong>Health endpoint:<\/strong> Test more than an open port; confirm that the application can answer with its basic dependencies.<\/li>\n<li><strong>Service monitoring:<\/strong> Monitor PHP-FPM, Node.js, Docker, and Nginx separately.<\/li>\n<li><strong>Log rotation:<\/strong> Alert on filesystem usage before <code>\/var<\/code> fills up.<\/li>\n<li><strong>Resource tracking:<\/strong> Watch RAM, swap, CPU, disk I\/O, and filesystem usage together.<\/li>\n<li><strong>Timeout design:<\/strong> Set CDN, load balancer, Nginx, and application timeouts in a deliberate order.<\/li>\n<li><strong>Deployment control:<\/strong> Use health checks and a rollback plan instead of sending every release straight to production.<\/li>\n<li><strong>Configuration tests:<\/strong> Run <code>nginx -t<\/code> for Nginx and configuration validation for Docker Compose.<\/li>\n<\/ul>\n<p>An uptime graph alone is not enough. PHP-FPM can run out of workers while Nginx remains healthy, leaving general monitoring green while visitors receive 502 responses. Synthetic HTTP checks catch that difference.<\/p>\n<p>I used to check only the homepage after a deployment. A lightweight health endpoint returned 200 while a real database-backed page was timing out upstream. Now I monitor both a simple health check and a low-cost synthetic request that touches the database.<\/p>\n<h2 id=\"frequently-asked-questions\">Frequently asked questions<\/h2>\n<h3>Who usually causes a 502 Bad Gateway error?<\/h3>\n<p>It usually comes from communication between the reverse proxy and the backend application. A crashed application, incorrect port, socket permissions, exhausted resources, or a failed CDN-to-origin connection can all produce the same status code.<\/p>\n<h3>Does the hosting company have to fix a 502 error?<\/h3>\n<p>On shared hosting, you may not have access to PHP-FPM or the web server, so the hosting provider should inspect the service and server logs. On your own VPS, you are responsible for checking Nginx, the application service, firewall rules, and resource usage.<\/p>\n<h3>Should you restart Nginx to fix a 502 error?<\/h3>\n<p>A controlled reload or restart may be needed after a configuration change, but inspect the error log and upstream state first. Restarting without reading the logs can hide the real cause while providing only a temporary improvement.<\/p>\n<h3>What is the main difference between 502 and 504?<\/h3>\n<p>A 502 means the proxy received an invalid or unusable upstream response. A 504 means the proxy did not receive a response in time. Both require checking the application, network path, and proxy logs, but slow queries and timeout settings deserve particular attention with 504.<\/p>\n<p>When I see a 502, I trace the path instead of restarting the first service I notice: client, proxy, socket or port, application, and finally its dependencies. The broken link tells me where to start.<\/p>\n<h2 id=\"sources\">Sources<\/h2>\n<ul class=\"aiw-sources\">\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc9110#name-502-bad-gateway\" target=\"_blank\" rel=\"noopener\">RFC 9110 &#8211; 502 Bad Gateway<\/a> \u2014 rfc-editor.org<\/li>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Reference\/Status\/502\" target=\"_blank\" rel=\"noopener\">MDN &#8211; 502 Bad Gateway<\/a> \u2014 developer.mozilla.org<\/li>\n<li><a href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_proxy_module.html\" target=\"_blank\" rel=\"noopener\">NGINX &#8211; HTTP Proxy Module<\/a> \u2014 nginx.org<\/li>\n<li><a href=\"https:\/\/docs.docker.com\/compose\/how-tos\/networking\/\" target=\"_blank\" rel=\"noopener\">Docker Docs &#8211; Networking in Compose<\/a> \u2014 docs.docker.com<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>A 502 Bad Gateway error usually means a reverse proxy cannot get a usable response from its upstream. Trace the request from Nginx to the application, inspect logs, verify ports and sockets, and check resources before restarting anything.<\/p>\n","protected":false},"author":2,"featured_media":537,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9],"tags":[],"class_list":["post-539","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to"],"lang":"en","translations":{"en":539,"tr":538},"pll_sync_post":[],"_links":{"self":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts\/539","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/comments?post=539"}],"version-history":[{"count":1,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts\/539\/revisions"}],"predecessor-version":[{"id":541,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts\/539\/revisions\/541"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/media\/537"}],"wp:attachment":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/media?parent=539"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/categories?post=539"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/tags?post=539"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}