Railway Deployment Failed? How to Fix Build, Deploy, and Health Check Errors
Troubleshoot Railway deployment failures — Nixpacks build detection, health check timeouts, PORT binding, environment variables not loading, and the difference between build failed and deploy failed.
Build failed vs Deploy failed vs Health check failed
Step-by-step fixes
1. Nixpacks build detection failing
Nixpacks scans your repo root for well-known files to detect the runtime. If it cannot identify the stack it exits with Error: no build plan found. Common requirements:
Node.js — needs a start script
{
"scripts": {
"start": "node server.js"
}
} A package.json without a start script causes Nixpacks to skip Node detection. Railway does not run npm run dev — it runs npm start.
Python — needs a dependency file and a Procfile
# requirements.txt must be in the repo root (or Pipfile / pyproject.toml)
fastapi==0.111.0
uvicorn[standard]==0.30.0
# Procfile (also in repo root)
web: uvicorn main:app --host 0.0.0.0 --port $PORT 2. PORT variable — must use process.env.PORT
Railway injects PORT at runtime. Hardcoding any port causes the health check to probe the wrong port and fail.
Node.js (Express)
// Wrong — hardcoded port will fail health check
app.listen(3000);
// Correct — read PORT from environment
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
}); Python (FastAPI / uvicorn)
import os
# Procfile passes $PORT to uvicorn — no change needed in app code
# web: uvicorn main:app --host 0.0.0.0 --port $PORT
# If running via Python directly:
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("PORT", 8000))
uvicorn.run(app, host="0.0.0.0", port=port) 3. Health check failed — add a /health endpoint
When a health check path is configured, Railway sends GET /health and expects HTTP 200 within the timeout window (default 60 s). Configure this in your service: Settings → Deploy → Health Check Path.
Node.js (Express)
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
}); Python (FastAPI)
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"} - Slow startup: if your app takes more than 60 s to start (loading a large ML model, running migrations), increase the health check timeout in Settings → Deploy → Health Check Timeout.
- No path set: if you have not configured a health check path, Railway probes
/. If your root route returns anything other than 2xx, the deployment fails.
4. Environment variables not found
Railway never reads .env files from your repository. All variables must be set in the Railway dashboard or via the CLI:
# CLI (install with: npm i -g @railway/cli)
railway login
railway link # link to your project
railway variables set DATABASE_URL=postgres://...
railway variables set SECRET_KEY=my-secret
# Dashboard path:
# Project → your service → Variables tab → + New Variable - Auto-redeploy: Railway automatically triggers a redeploy when you add or change a variable in the dashboard — no manual redeploy needed.
- Railway-injected vars: Railway automatically provides
PORT,RAILWAY_PUBLIC_DOMAIN, andRAILWAY_PRIVATE_DOMAIN— do not set these manually. - Shared variables: use Railway’s Shared Variables (project-level) to share secrets like
DATABASE_URLacross multiple services in the same project.
5. Private networking vs public domain
Railway gives each service two domains — a public one and a private internal one. Using the wrong one is a common source of connection errors between services in the same project:
# Public domain (accessible from the internet)
RAILWAY_PUBLIC_DOMAIN=my-app.up.railway.app
# Private domain (only works between services in the same project)
RAILWAY_PRIVATE_DOMAIN=my-app.railway.internal
# For service-to-service calls inside Railway, use the private domain:
# http://my-api.railway.internal:3000/endpoint
# This avoids egress billing and is faster than the public URL. - Internal port: services on the private network are reached on the port your app listens on (i.e.
process.env.PORT), not port 80 or 443. - No public domain yet: if
RAILWAY_PUBLIC_DOMAINis not set, go to your service → Settings → Networking → Generate Domain.
Get alerts when Railway has an outage
Free email alerts. Star Railway on Prismix — no credit card needed.
FAQ
What is the difference between Railway build failed and deploy failed?
Build failed means Nixpacks could not compile or install your app — check for a missing start script or dependency file. Deploy failed means the container crashed at startup — check your PORT binding and runtime environment variables.
Why does Railway say “Health check failed”?
Railway probes your Health Check Path and expects HTTP 200. Your app must listen on process.env.PORT and respond before the timeout. Add a GET /health route returning 200 and set it as the health check path in Settings → Deploy.
How do I fix Railway environment variables not found?
Do not use .env files — Railway ignores them. Add variables in the Railway dashboard under your service → Variables tab, or with railway variables set KEY=value via the CLI. Railway automatically redeploys after a variable change.
What PORT should my app listen on for Railway?
Always use process.env.PORT (Node.js) or os.environ["PORT"] (Python). Railway injects this variable — never hardcode a port number in a Railway service.