DEV Community

jason rauch
jason rauch

Posted on

# 5 Railway.io Config Mistakes That Silently Break Deployments (And How to Fix Them)

If you've used Railway.io for more than a week, you've probably experienced the special frustration of a deployment that looks like it worked — green checkmark, no errors — but your app is completely unreachable. No traffic. No response. Just silence.

Most of the time it comes down to a config mistake that Railway doesn't loudly flag. Here are the five that get developers most often, with exact fixes for each.


1. Hardcoding the PORT

This is the #1 Railway gotcha and it catches almost everyone at least once.

Railway injects a $PORT environment variable dynamically at runtime. Your app must read and listen on that port. If you hardcode port 3000 or 8080, your service starts fine but never receives traffic — Railway is sending requests to a port your app isn't listening on.

Broken:

{
  "variables": {
    "PORT": "3000"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setting PORT as a static variable doesn't help — Railway's injected value overrides it anyway.

Fixed — in your app code:

const port = process.env.PORT || 3000;
app.listen(port);
Enter fullscreen mode Exit fullscreen mode

Fixed — in a Dockerfile:

# Don't do this:
EXPOSE 3000

# Do this — let Railway set the port at runtime:
CMD ["node", "server.js"]
# And in server.js: app.listen(process.env.PORT)
Enter fullscreen mode Exit fullscreen mode

This applies to every language and framework. Python with Flask, Go with net/http, Ruby with Puma — they all need to bind to process.env.PORT (or the equivalent env var read in your language).


2. Using an Invalid Builder Value

Railway supports four builders: nixpacks, dockerfile, heroku, and railpack. That's it. If you write anything else — "node", "auto", "docker" — Railway either ignores it or fails silently.

Broken:

{
  "build": {
    "builder": "node"
  }
}
Enter fullscreen mode Exit fullscreen mode

Fixed:

{
  "build": {
    "builder": "nixpacks"
  }
}
Enter fullscreen mode Exit fullscreen mode

If you're using Railway's newer Metal infrastructure, you're likely moving to Railpack (the successor to Nixpacks). In that case, use a railpack.json file with the correct providers array instead:

{
  "$schema": "https://schema.railpack.com",
  "providers": ["node"],
  "deploy": {
    "startCommand": "node server.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

3. No Restart Policy — Crashed Services Stay Down

By default, if your service crashes Railway won't automatically restart it. You need to explicitly set restartPolicyType.

Broken (service stays dead after a crash):

{
  "deploy": {
    "startCommand": "node server.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

Fixed:

{
  "deploy": {
    "startCommand": "node server.js",
    "restartPolicyType": "ON_FAILURE"
  }
}
Enter fullscreen mode Exit fullscreen mode

Valid values are ON_FAILURE, ALWAYS, and NEVER. For most production services you want ON_FAILURE. Using ALWAYS means Railway will restart even intentional shutdowns — usually not what you want.


4. Missing or Broken healthcheckPath

If you define a healthcheckPath, it must start with a /. If it doesn't, Railway either rejects the config or the health check never passes — causing your service to restart in a loop.

Broken:

{
  "deploy": {
    "healthcheckPath": "health"
  }
}
Enter fullscreen mode Exit fullscreen mode

Fixed:

{
  "deploy": {
    "healthcheckPath": "/health"
  }
}
Enter fullscreen mode Exit fullscreen mode

Also make sure that route actually exists in your app and returns a 200 status. A common mistake is defining /health in the config but forgetting to add the route handler in the code.

If you're not ready to implement a health endpoint, remove the healthcheckPath field entirely rather than leaving it broken — Railway will fall back to basic process monitoring.


5. npm install Instead of npm ci in Build Steps

This one is subtle but causes non-deterministic builds — meaning your app works locally and in one deployment but breaks in the next.

npm install can silently upgrade packages within the ranges defined in package.json. npm ci installs exactly what's in your package-lock.json — no surprises.

Broken (nixpacks.toml):

[phases.install]
cmds = ["npm install"]
Enter fullscreen mode Exit fullscreen mode

Fixed:

[phases.install]
cmds = ["npm ci"]
Enter fullscreen mode Exit fullscreen mode

Broken (railpack.json):

{
  "steps": {
    "install": {
      "cmds": ["npm install"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Fixed:

{
  "steps": {
    "install": {
      "cmds": ["npm ci"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Same rule applies to Dockerfiles — RUN npm ci instead of RUN npm install.


Catching These Automatically

These five mistakes are easy to make and annoying to debug because Railway doesn't always give you a clear error message. I got tired of finding them manually so I built Railway DevTools — paste your config and Claude AI audits it instantly, flags issues by severity, and shows you the exact fix.

It supports railway.json, railway.toml, Dockerfile, nixpacks.toml, railpack.json, and Procfile. Free to try with no sign-up.


Summary

Mistake Symptom Fix
Hardcoded PORT App unreachable Read process.env.PORT
Invalid builder Build fails silently Use nixpacks, dockerfile, or heroku
No restart policy Crashed service stays down Set restartPolicyType: ON_FAILURE
Bad healthcheckPath Restart loop Start path with /
npm install vs npm ci Non-deterministic builds Use npm ci

If you've run into other Railway gotchas that aren't on this list, drop them in the comments — I'll add them to the article and potentially to the validator too.

Top comments (0)