How to back up Railway Postgres to Amazon S3
RESTORE-TESTED · ENCRYPTED · IN A BUCKET YOU OWN
Railway keeps your database running, but its snapshots live in the same account and region as the database itself. An off-site copy in your own Amazon S3 bucket protects you from the failures the platform can't: a bad migration, a locked account, or a compliance question you need a real answer to. Here's how to set it up in a few minutes — and, crucially, how to know the backup actually restores.
Step 1 — Get your Railway connection string
Railway project → your Postgres service → Variables tab → DATABASE_URL (or the public proxy URL under Connect).
Use the public connection URL (the TCP proxy host Railway exposes), not the internal .railway.internal hostname — that one only resolves inside Railway's network and we can't reach it from outside.
A read-only role is all you need. The exact CREATE ROLE SQL is on the security page.
Step 2 — Create a Amazon S3 bucket and access keys
- In the S3 console, create a bucket (block all public access — backups should never be public).
- Create an IAM user with programmatic access and the S3 policy above.
- Copy the access key ID and secret access key.
Create an IAM user with programmatic access and a policy granting s3:PutObject, s3:GetObject, s3:DeleteObject, and s3:ListBucket on the bucket. Use that user's access key ID and secret.
Endpoint: (leave blank — AWS default) Region: your bucket's region, e.g. us-east-1 Bucket: your-backup-bucket
Step 3 — Connect it to OffsiteDB
Paste the Railway connection string, add Amazon S3 as your destination with the bucket and keys from Step 2, and choose a schedule (hourly to daily). OffsiteDB tests the connection, then runs pg_dump, gzips and seals the artifact with AES-256-GCM, and ships it to your bucket. Railway runs vanilla Postgres, so a read-only role on your schemas captures everything.
Step 4 — Know it restores (the part everyone skips)
Every snapshot is restore-drilled: OffsiteDB restores it into a throwaway Postgres cluster and counts the rows before marking it sealed. When you need it back, every artifact is a standard custom-format dump:
gunzip -c railway-db_2026-06-09.dump.gz \ | pg_restore -d "$NEW_DATABASE_URL" --clean --if-exists
You also get a monthly Restore Drill Report with tested restore times — the document you forward when someone asks “are your backups tested?”