Get started Dashboard
Troubleshooting ·

Fix Railway “Application failed to respond” (502)

Explore with AI

Application failed to respond

Railway's edge proxy shows this page with a 502 Bad Gateway when it can't reach your app. Most often the server isn't listening on host 0.0.0.0 and the port in the PORT variable, or the domain's target port points at a different port. Bind to 0.0.0.0:$PORT, make the target port match, and check the deploy logs and Metrics tab for a crash or a CPU ceiling.

On this page

You open your Railway domain and get a page titled “Application failed to respond” where your app should be. The browser’s network tab shows a 502. Sometimes it happens on the very first deploy of a new service. Sometimes a service that worked yesterday starts doing it after a change to the start command, a new framework version, or a spike in traffic.

The page comes from Railway, and your app never saw the request. The fix is to work out why Railway couldn’t hand the request over: wrong host, wrong port, nothing running, or too busy to answer.

What “Application failed to respond” means on Railway

Every public request to a Railway service goes through Railway’s edge proxy first. The proxy works out which service and which internal port the domain points at, and passes the request to your app on that port. According to Railway’s troubleshooting page, this error means the edge proxy can’t communicate with your application, so the request fails with a 502 Bad Gateway status code.

Railway lists three reasons, in this order of frequency:

  1. Your app isn’t listening on the correct host or port. This is the most common one.
  2. The domain’s target port is set to a port your app doesn’t listen on.
  3. Far less often, the app is under heavy load and can’t respond.

There is a fourth case the docs imply: if the process isn’t running at all, because it crashed or hasn’t started yet, there is nothing for the proxy to talk to either.

graph TB
    A[Request hits Railway edge] --> B{Domain has a target port?}
    B -->|yes| C[Proxy dials the target port]
    B -->|no| D[Proxy dials the app port]
    C --> E{App listening on 0.0.0.0 there?}
    D --> E
    E -->|no| F[502 Application failed to respond]
    E -->|yes| G{App answers in time?}
    G -->|no| F
    G -->|yes| H[Your app returns a response]
    style F fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
    style H fill:#d1fae5,stroke:#6ee7b7,color:#065f46

Find the failing requests first

Before changing code, confirm you’re looking at this error and see which requests hit it. Railway records HTTP logs for every request at the edge, and the logs docs list an @responseDetails attribute that is only populated when the application fails to respond.

From the CLI, with the project linked:

railway logs --http --status 502 --lines 50
railway logs --http --status 500..599 --since 1h

In the dashboard’s Log Explorer, the same filter is:

@httpStatus:502

Then read the deploy logs for the active deployment. They capture your app’s stdout and stderr, so they show the line where your server says which address and port it bound to:

railway logs --deployment --lines 100
railway logs --latest

--latest shows the newest deployment even if it failed or is still building. With the HTTP logs and the listen line side by side, match your case to one of the causes below.

Cause 1: the server listens on localhost or a hardcoded port

How to tell it’s yours: the deploy logs show the server started, but on localhost, 127.0.0.1, or a fixed port such as 3000 that doesn’t match PORT. Typical lines:

Server listening on http://localhost:3000
Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Railway injects a PORT environment variable into each deployment, and its docs say your web server should bind to host 0.0.0.0 and listen on that port. The vibe-coded app guide spells out the two traps. AI tools often hardcode the port, as in app.listen(3000). And binding to localhost or 127.0.0.1 makes the app unreachable from outside the container. A loopback address only accepts connections from inside the same container, and the edge proxy is outside it.

You can check what Railway set by printing the variable from inside the service:

railway ssh -- printenv PORT

Fix: read PORT and bind to 0.0.0.0. The code differs by framework. The idea doesn’t.

  1. Node with Express. Pass the host as the second argument to listen:
    const express = require("express");
    const app = express();
    
    const port = process.env.PORT || 3000;
    
    app.listen(port, "0.0.0.0", () => {
      console.log(`Listening on 0.0.0.0:${port}`);
    });
    NestJS takes the same two arguments: await app.listen(port, "0.0.0.0").
  2. Next.js. next start needs the port flag, as Railway’s page shows:
    next start --port ${PORT-3000}
  3. Python with uvicorn. Railway’s page says uvicorn needs extra flags to listen on 0.0.0.0 and PORT, so pass both in the start command:
    uvicorn main:app --host 0.0.0.0 --port $PORT
  4. Python with gunicorn. Railway’s docs say gunicorn listens on 0.0.0.0 and PORT by default, so gunicorn main:app works as is. If you pass your own bind, keep it on all interfaces:
    gunicorn main:app --bind 0.0.0.0:$PORT
  5. Go with net/http. Read the variable, fall back when it’s empty, and give the full address:
    package main
    
    import (
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	mux := http.NewServeMux()
    	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    		w.Write([]byte("ok"))
    	})
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "3000"
    	}
    	log.Printf("listening on 0.0.0.0:%s", port)
    	log.Fatal(http.ListenAndServe("0.0.0.0:"+port, mux))
    }
  6. If the start command lives in the service settings or in railway.json, update it there too. A config file setting overrides the dashboard:
    {
      "$schema": "https://railway.com/railway.schema.json",
      "deploy": {
        "startCommand": "uvicorn main:app --host 0.0.0.0 --port $PORT"
      }
    }
  7. Redeploy and check that the new listen line shows 0.0.0.0 and the value of PORT.

Cause 2: the domain’s target port doesn’t match the app

How to tell it’s yours: the app binds to 0.0.0.0 on some port, the deploy logs prove it, and the domain still returns 502. Open the service, go to Settings, then Networking, and look at the port shown next to the domain.

Railway’s domains docs call these target ports. They map one domain to one internal port your app listens on, which is how a single service can expose several HTTP ports through several domains. When you generate a Railway domain and the app listens on a single port, Railway detects it and sets it as the target port. When you add a custom domain, you pick from a list or type a custom port.

That detection runs once, so the setting can go stale. Railway’s own example: a domain configured for port 3000 while the app was listening on 8080. That happens when you change the port in code or in the start command after the domain was created, or when the app was listening on a temporary port at the moment you generated the domain.

Fix: point the domain at the port the app uses.

  1. Read the real port from the deploy logs, for example Listening on 0.0.0.0:8080.
  2. In Settings > Networking, click the edit icon next to the domain and set that port.
  3. Repeat for every domain on the service. Custom domains and the Railway domain each have their own target port.
  4. If the app deliberately listens on a port other than PORT, also set a PORT service variable to that number. The healthcheck docs say Railway uses PORT for health checks, and leaving it unset with target ports can make the health check return service unavailable:
    railway variable set PORT=8080

The simplest setup is one port everywhere: the app reads PORT, and the target port equals that value.

Cause 3: the app crashed or hasn’t started listening yet

How to tell it’s yours: the deployment shows as Crashed in the service’s deployment list, or the deploy logs end in a stack trace, a missing environment variable, or a database connection error before any listen line. Another sign is 502s that cluster in the first seconds or minutes after each deploy, then stop.

The deployments reference explains why the second pattern happens. Without a healthcheck, Railway marks a deployment Active as soon as the container starts. If your app spends time running migrations or warming a cache before it calls listen, requests routed to it in that window find nothing on the port. With a healthcheck, Railway waits for the endpoint to return a 2xx before it makes the new deployment active.

A crash is easier to spot. A deployment stays Active until it crashes, then becomes Crashed. The restart policy defaults to On Failure with a maximum of 10 restarts. Once those are used up, the service stays down, and every request gets the 502 page. A process that exits with code 0 shows as Completed, which On Failure doesn’t restart.

railway logs --latest --lines 200
railway logs --deployment --filter "@level:error" --lines 50

Fix: make the app start reliably and tell Railway when it’s ready.

  1. Fix the error at the end of the deploy logs. On Railway the usual culprits are variables that only exist in your local .env and connection strings that point at localhost. Railway’s guide says databases run as separate services, so reference their connection URL as a variable.
  2. Add a health endpoint that returns 200 once the app is ready, and set it as the healthcheck path:
    {
      "$schema": "https://railway.com/railway.schema.json",
      "deploy": {
        "healthcheckPath": "/health",
        "healthcheckTimeout": 300
      }
    }
    The default timeout is 300 seconds. If the app doesn’t return a 2xx within it, Railway marks the deploy as failed and keeps the previous one serving traffic.
  3. If your app restricts hosts, allow healthcheck.railway.app. Railway sends health checks from that hostname, and rejecting it shows up as failed with service unavailable or failed with status 400.
  4. Move slow one-off work such as migrations into a pre-deploy command, so the container that serves traffic starts listening straight away:
    {
      "$schema": "https://railway.com/railway.schema.json",
      "deploy": {
        "preDeployCommand": ["npm run db:migrate"],
        "startCommand": "node server.js"
      }
    }
  5. For a service that must always be up, change the restart policy to Always in the service settings. It isn’t available on the Free plan or during a trial.

One related case: if Serverless is on, Railway puts the service to sleep after at least 5 minutes with no outbound traffic. The Serverless docs say the first request to a slept service may return a 502 Bad Gateway while it wakes. If that matches your pattern, turn Serverless off for that service.

Cause 4: the app is too busy to answer

How to tell it’s yours: host, port and target port are all correct, most requests succeed, and the 502s come in bursts during traffic peaks. Slow responses show up in the HTTP logs before the failures do:

railway logs --http --filter "@responseTime:>=1000" --lines 50
railway metrics --cpu --memory --since 6h
railway metrics --http --since 6h

railway metrics --http reports request totals, status-code buckets, error rate and latency percentiles. Railway’s troubleshooting page gives one concrete marker for Node.js: vCPU usage peaking at around 1 vCPU in the Metrics tab is a good sign of heavy load, because Node runs your JavaScript on a single thread.

Keep in mind how replicas show up. The scaling docs say metrics from all replicas are summed, so two replicas each using 100 MB appear as 200 MB.

Fix: spread the load.

  1. Add replicas. In the service settings, raise the replica count in the Scale section. Railway distributes public traffic randomly across the replicas in a region, and each replica gets the full resources of your plan. In config:
    {
      "$schema": "https://railway.com/railway.schema.json",
      "deploy": {
        "multiRegionConfig": {
          "us-west2": { "numReplicas": 2 }
        }
      }
    }
  2. Use the cores you already have. A single Node process runs your JavaScript on one thread, so run several processes or workers per replica if your server supports it.
  3. Find the hot path. Filter the HTTP logs by @path and @responseTime to see which routes are slow, and fix the query or call behind them.
  4. Remember that Railway doesn’t support sticky sessions. Any session state has to live in a shared store before you scale out.

Confirming the 502 is gone

After the redeploy, check three things:

  1. The deploy logs show your server’s listen line with 0.0.0.0 and the port Railway gave it:
    Listening on 0.0.0.0:8080
  2. The public domain answers with a 200:
    curl -sS -o /dev/null -w "%{http_code}\n" https://your-app.up.railway.app/
    Expected output:
    200
  3. The HTTP logs have no new 502s:
    railway logs --http --status 502 --since 15m

Stopping “Application failed to respond” from coming back

graph TB
    A[Deploy starts] --> B[App reads PORT]
    B --> C[App binds 0.0.0.0 on PORT]
    C --> D{Healthcheck returns 2xx?}
    D -->|no| E[Deploy fails, old one serves]
    D -->|yes| F[New deploy goes Active]
    F --> G[Webhook alerts on Crashed]
    style E fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
    style F fill:#d1fae5,stroke:#6ee7b7,color:#065f46

  • Never hardcode the port or the host. Read PORT in code or in the start command, and keep the domain’s target port equal to it.
  • Keep a healthcheck path on every web service. A deploy that never listens then fails before it takes traffic.
  • Alert on crashes. Railway’s alerting guide shows how a project webhook can post Failed and Crashed deployment events to Slack or Discord with no middleware. Monitors on CPU and RAM thresholds need the Pro plan.
  • Watch for 502s from outside. Railway only calls the healthcheck while a deploy goes live, never after. An uptime check on the public domain, or a saved @httpStatus:502 filter in the Log Explorer, catches a service that falls over later.
  • Check replicas before launches. If Metrics shows CPU near its ceiling at normal traffic, add replicas before the next spike.

Catching Railway 502s with Polylane

Polylane connects to Railway with OAuth or a workspace token and syncs your projects, services and deployments. It watches deploys, reads logs and checks service metrics for issues, so a crashed deployment or a run of 502s after a deploy shows up with the log lines that explain it.

Running on Railway? See how Polylane monitors Railway in production.

Common questions.

What port does Railway expect my app to listen on?

Railway injects a PORT environment variable into every deployment, and your server should listen on that value on host 0.0.0.0. Read it at startup with process.env.PORT in Node, $PORT in a uvicorn start command, or os.Getenv("PORT") in Go. Don't hardcode 3000 or 8080 unless the domain's target port is set to the same number.

Why does Railway say “Application failed to respond” when my app works locally?

Locally your browser and server share one machine, so a server bound to localhost or 127.0.0.1 answers. On Railway the edge proxy connects from outside the container, so it needs a listener on 0.0.0.0. Railway's docs say binding to localhost makes the app unreachable from outside the container.

Where do I change the target port of a Railway domain?

Open the service, go to Settings, then Networking, and click the edit icon next to the domain. Railway sets the target port automatically when it detects a single listening port, and you can change it at any time. It must equal the port the deploy logs show your server listening on.

How do I find which requests got the 502?

Run railway logs --http --status 502 from the CLI, or filter the HTTP logs in the dashboard with @httpStatus:502. The @responseDetails attribute is only populated when the application fails to respond, so it marks these requests. Add @path or @host to narrow it to one route or domain.

Can high CPU cause “Application failed to respond”?

Yes, though Railway's docs call it far less common than a host or port problem. For a Node.js app, the docs say vCPU usage peaking around 1 vCPU in the Metrics tab is a good sign of heavy load, because Node runs JavaScript on one thread. Add replicas in the service settings to spread requests across more instances.

Does a healthcheck stop this error after a deploy?

It stops the window where traffic reaches a deploy that isn't listening yet. With a healthcheck path set, Railway only marks a deployment Active after the endpoint returns a 2xx, and the default timeout is 300 seconds. Railway doesn't call the healthcheck again once the deploy is live, so it won't catch a crash later.

Why do I get a 502 on the first request after my service was idle?

If Serverless is enabled, Railway puts the service to sleep after at least 5 minutes with no outbound traffic. Railway's docs say the first request to a slept service may return a 502 Bad Gateway while it wakes up. Turn Serverless off for services that must answer the first request.

Sources

  1. Application Failed to Respond (Railway Docs)
  2. Working with Domains: target ports (Railway Docs)
  3. Healthchecks (Railway Docs)
  4. Deploy a Vibe-Coded App on Railway (Railway Docs)
  5. Logs (Railway Docs)
  6. railway logs (Railway CLI Docs)
  7. railway metrics (Railway CLI Docs)
  8. Scaling (Railway Docs)
  9. Deployments reference (Railway Docs)
  10. Restart Policy (Railway Docs)
  11. Serverless (Railway Docs)
  12. railway ssh (Railway CLI Docs)
  13. Config as code reference (Railway Docs)
  14. Set Up Alerts for Crashes, Restarts, and Failed Deploys (Railway Docs)
  15. Polylane documentation
  16. Polylane full content

About the author

Boris Tane

Founder of Polylane

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

Related

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free