How to Run a Production Postgres and Worker on Railway Safely
Running Postgres on Railway in production needs four things the one-click template does not give you: a private network connection, an external backup schedule, a separate worker service, and a restart policy that does not thrash. Each takes a few minutes to configure and none of them is on by default.
Railway is genuinely fine for a database up to a few hundred gigabytes, but the defaults are built for demos. Treat the template as a starting point.
Does Railway’s Postgres Survive a Restart?
Yes, with a caveat. Railway stores the Postgres volume persistently, so data survives a service restart or redeploy. The volume belongs to the service — deleting the Postgres service deletes the data, and there is no undo window.
What does not exist out of the box is point-in-time recovery or an automated off-site backup. On the Pro plan you can enable scheduled volume backups. On Hobby, you write your own dump.
How Do I Connect Services Without Exposing the Database?
The one-click template exposes Postgres publicly by default because it generates a TCP proxy for external tools. If only your own services need access, delete that proxy.
Steps:
- Open the Postgres service → Settings → Networking.
- Remove the TCP Proxy entry.
- Set your app’s
DATABASE_URLto the internal hostname, which is the internal hostpostgres.railway.internal. - Confirm with
psql "$DATABASE_URL" -c 'select 1'from a Railway shell.
Internal traffic between services in the same project does not leave Railway’s network and is not billed as egress. Leaving the TCP proxy on means an internet-reachable database on a long random port — not fatal, but unnecessary.
How Do I Schedule Backups on the Hobby Plan?
Pro has backups; Hobby does not. The workaround is a cron service in the same project.
pg_dump "$DATABASE_URL" | gzip | aws s3 cp - s3://your-bucket/db/$(date +%F).sql.gz
Run it as a service with cron schedule 0 3 * * * and restartPolicyType: NEVER. Set a bucket lifecycle rule to expire after 30 days. Test the restore once — an untested backup is a file, not a backup.
How Do I Add a Background Worker?
Railway treats a worker as just another service from the same repo with a different start command. There is no separate worker tier.
| Setting | Web service | Worker service |
|---|---|---|
| Start command | npm start |
npm run worker |
| Public domain | Yes | No |
| Health check | /health returns 200 |
None — use a heartbeat row |
| Restart policy | On failure | On failure, with backoff |
Share the same DATABASE_URL and REDIS_URL by adding the worker to the project; variables set on a shared group are inherited. Without a shared group you will paste connection strings twice and drift.
Why Does My Service Keep Restarting?
Crash loops on Railway usually trace to a memory limit. Hobby containers get a soft ceiling based on plan credits; when a Node process crosses roughly 512 MB of resident memory it gets OOM-killed, restarts, and repeats.
Add a health check path under Settings → Deploy → Healthcheck Path. Railway waits for a 200 before routing traffic, so a slow boot does not serve errors for 40 seconds. Also pin your Node version — an unpinned engines field can pull a major you never tested.
When Should You Not Use Railway for the Database?
If you need multi-region replication, connection pooling at the thousands-of-connections scale, or a compliance audit trail, use a dedicated provider. Railway’s managed Postgres is a convenience layer, not a DBA replacement. Teams that pick it for a regulated workload end up migrating within a quarter.
Quick Summary
- Railway Postgres persists across restarts but has no automated backup on the Hobby plan.
- Delete the TCP proxy and use
PROJECT.railway.internalhostnames for private traffic. - Schedule
pg_dumpto object storage with a NEVER-restart cron service. - Workers are the same repo with a different start command and no public domain.
- OOM kills at roughly 512 MB are the most common crash-loop cause.
