RAILWAY SECURITY CHECKLIST

Railway makes wiring multi-service apps trivial — Postgres + Redis + worker + web in one project, talking to each other via shared variables. Two patterns produce most Railway security failures: shared environment variables leak across services that shouldn’t share them, and inter-service traffic uses public URLs because “private networking” wasn’t configured. The checklist below is what we look for first when we audit a Railway-deployed app.

Treat Critical as launch-blocking. High is week-one. Medium is the cleanup once you have users.

How to use this checklist

Walk it once in the Railway dashboard for your production project, ticking items as you go. Audit each environment (production, staging, dev) separately. After the whole list passes, run a black-box scan against the production URL.

Critical (fix before launch)

1. Restrict environment variables by service

Why it matters. Railway “shared variables” are exposed to every service in the project. Convenient for DATABASE_URL, dangerous for service-specific secrets. A worker that gets exploited can read every other service’s env — including third-party API keys and webhook signing secrets it had no business knowing.

How to check. Project → Variables → Shared. List every shared var. For each, ask “does every service in this project actually need this?” If no, demote.

How to fix. Move service-specific vars to the service-level scope. Keep shared vars to genuinely shared values (the database URL, the project’s domain).

2. Configure private networking for inter-service traffic

Why it matters. By default, services in a Railway project talk to each other via public URLs. That means service-to-service traffic traverses the internet, with all the latency, billing, and exposure that implies. A leaked internal API endpoint becomes a public attack surface.

How to check. Audit how each service references other services. Internal service URLs should use the *.railway.internal form, not *.up.railway.app.

How to fix. Enable private networking on each service. Update inter-service references to use <service-name>.railway.internal:<port>.

3. Enable IP allowlisting on databases

Why it matters. Railway-managed Postgres and Redis instances are publicly reachable by default unless you configure access controls. The connection strings are bearer tokens — leaked, they’re immediately exploitable.

How to check. For each database service, audit network access. Confirm only your app services (or specific IPs) can connect.

How to fix. Use Railway’s TCP proxy with private networking, or restrict the public endpoint to specific IP ranges.

4. Audit deploy tokens for least-privilege scope

Why it matters. Railway deploy tokens grant API access to the project. CI tokens often get scoped broadly (“project admin”) because that’s what the deploy command needs — but that token can also delete services, change env vars, and roll back deployments.

How to check. Project → Tokens. Audit each token’s scope and where it’s used.

How to fix. Use environment-scoped tokens for CI. Rotate broad-scope tokens; replace with narrower ones.

5. Rotate database passwords if seeded via Railway templates

Why it matters. Railway templates (Discord bot, Mastodon, etc.) seed databases with template-defined credentials. If you deployed from a popular template and didn’t rotate, the credentials may be predictable from the template definition.

How to check. Compare your database credentials against the template’s defaults. Rotate if they match.

How to fix. Generate a fresh strong password; update the database; update services that connect to it.

6. Confirm no application code uses superuser-equivalent credentials

Why it matters. Railway-managed Postgres ships with a postgres superuser. App services should connect via a least-privilege role, not as superuser.

How to check. Decode each service’s DATABASE_URL; confirm the role is application-scoped.

How to fix. Create per-service roles with minimum privileges; switch services; rotate the superuser password.

High (fix in the first week)

7. Configure custom domains with HSTS

Railway provides *.up.railway.app by default. For production, use a custom domain with HSTS so browsers refuse to downgrade to HTTP.

8. Set up health checks that don’t expose internal data

Health-check endpoints are unauthenticated by definition. Ensure they return only “I’m alive” — not version strings, not feature flags, not config dumps.

9. Pin language/runtime versions

In railway.json or your service config, pin Node/Python/Ruby version explicitly. Auto-upgrades through major versions can change crypto and HTTP defaults.

10. Restrict deploy hook scope

Railway deploy hooks can trigger deploys from external systems. Audit which hooks exist; rotate after team changes.

11. Verify domain DNS is locked

Custom domains use DNS records you manage. Audit who can change them at the registrar level. Domain hijacking is real.

12. Set up build-time secret scanning

Add gitleaks or trufflehog to your build command. Fail the build if a secret is detected in committed code.

Medium (fix when you can)

13. Configure log retention and PII redaction

Railway captures stdout/stderr from each service. Confirm logs don’t contain PII; configure your app to redact emails, tokens, and similar before logging.

14. Set resource limits per service

Memory and CPU limits per service prevent a runaway (or attacked) service from consuming the whole project’s resources.

15. Document service-to-service auth

How do services authenticate to each other? With shared secrets via env? With mTLS? Document it; review during onboarding.

16. Audit installed templates and plugins

Each template is a one-time copy but lessons learned stick around. Audit periodically.

17. Restrict who can change environment

Production environment changes should require approval. Audit who has write access to the production env.

18. Set up alerting on deploy frequency anomalies

A sudden spike in deploys signals a compromise of a deploy token or a developer account.

After every config change

  • Re-test inter-service traffic — should still use private network.
  • Re-verify env vars haven’t drifted across scopes.
  • Re-test database access from outside the project (should fail).

Common attack patterns we see in Railway apps

The shared-variable leak. Stripe webhook secret added as a shared var “for convenience”. Worker service has a SQL injection; attacker reads every shared var via printenv, including the Stripe secret.

The public inter-service URL. Web service calls worker via worker.up.railway.app/internal-api/process. URL is not authenticated because “it’s internal”. Attacker discovers the URL via DNS enumeration and triggers the internal API at will.

The template-default credentials. Deployed from a Mastodon template six months ago without rotating the database password. Same template; same default; somebody published a list.

The superuser app. App connects to Postgres as postgres (the superuser). SQL injection becomes RCE on the database.

How to Secure Railway

Step-by-step guide for hardening a Railway project — variable scoping, private networking, role design, and the database hardening above in long form.

Is Railway Safe?

In-depth analysis of Railway’s defaults — what’s locked down, what isn’t, and what we find when we audit a typical Railway-deployed app.

Automate Your Checklist

A checklist tells you what to look for. A scanner tells you what’s actually broken in the deployed app right now. VibeEval drives a real browser through your Railway deployment, attempts the missing-auth and credential-leak attacks above, and reports what got through.

SCAN YOUR DEPLOYMENT

14-day trial. No card. Results in under 60 seconds.

START FREE SCAN