[FIXED] Healthcheck "service unavailable" on all new deploys, app returns 200
azkron
PROOP

17 days ago

All new deployments fail healthcheck with "service unavailable" even though the application responds HTTP 200 to /health internally.

Symptoms:

  • App starts, listens on 0.0.0.0:3000, responds 200 to /health (visible in deploy logs)

  • Railway's healthcheck prober simultaneously reports "service unavailable" for every attempt

  • Retries exhaust the full window and deploy fails

Deploy logs (truncated):

Starting Healthcheck
Path: /health
Retry window: 1m40s

Attempt #1 failed with service unavailable. Continuing to retry for 1m29s
Attempt #2 failed with service unavailable. Continuing to retry for 1m27s
timestamp=... level=INFO message="Listening on http://0.0.0.0:3000"
timestamp=... message="Sent HTTP response" http.status=200 http.method=GET http.url=/health
timestamp=... message="Sent HTTP response" http.status=200 http.method=GET http.url=/health
Attempt #3 failed with service unavailable. Continuing to retry for 1m25s
Attempt #4 failed with service unavailable. Continuing to retry for 1m21s
...
Deploy failed

What I've tried (all produce the same result):

  • railway up (CLI upload)

  • railway redeploy (build from GitHub repo)

  • Removing Docker HEALTHCHECK instruction from Dockerfile

  • Removing PORT env var (let Railway inject it)

  • Setting PORT=3000 explicitly

  • Increasing healthcheckTimeout to 300s in railway.toml

  • Disabling Cloudflare proxy (DNS only)

  • Removing the auto-generated Railway domain

  • Deleting the service and creating a brand new one

What works:

  • Redeploying a previously successful deployment from the dashboard (old cached image)

Setup:

  • Node.js 24.13.1-alpine, Effect Platform, Prisma ORM

  • PORT=3000, app listens on 0.0.0.0:3000

  • Custom domain with target port 3000

  • railway.toml: healthcheckPath = "/health", healthcheckTimeout = 300

This looks identical to the issue in this thread which was caused by conflicting firewall rules and required a platform-side fix.

Solved

2 Replies

azkron
PROOP

17 days ago

Found the issue, my CORS middleware was crashing on requests with no Origin header. When you use a specific allowlist (not *), the origin matcher function receives undefined for same-origin or server-to-server requests and throws. Adding a null check before matching fixed it.


17 days ago

Glad you found it. CORS origin matchers receiving undefined for requests without an Origin header (like our healthcheck prober) is a subtle one. Thanks for sharing the fix.


Status changed to Awaiting User Response Railway 17 days ago


Status changed to Solved brody 17 days ago


Loading...