MSSQL to Railway Postgres
This is an operator’s playbook for an MSSQL to Railway Postgres migration. It covers the Railway-specific connection details — the public TCP proxy, self-signed TLS, and private networking — plus the SQL Server type caveats you need to move Microsoft SQL Server into a Railway PostgreSQL service.
If you searched for how to migrate SQL Server to Railway Postgres or move MSSQL to Railway, the short version is: use Railway’s public proxy URL (DATABASE_PUBLIC_URL) with sslmode=require from an external host, run the migration inside Railway to avoid egress charges when you can, and let pgferry’s sys.* catalog introspection handle the SQL Server types generic tools botch.
What this guide is for
Section titled “What this guide is for”Use this guide when you have a Microsoft SQL Server database (on-prem, Azure SQL, AWS RDS for SQL Server, etc.) and want it on a Railway-hosted PostgreSQL service. It assumes you have added a Postgres database to a Railway project. For source-side behavior that is not Railway-specific, read the generic MSSQL to PostgreSQL guide alongside this page.
Why use pgferry instead of generic pgloader advice
Section titled “Why use pgferry instead of generic pgloader advice”For SQL Server, the generic advice is especially weak: pgloader’s MSSQL support is thin and largely unmaintained, and most tutorials assume MySQL. pgferry is built for this pair:
- It introspects SQL Server through
sys.*catalog views and applies SQL Server-specific conversions (UUID byte reordering,datetime2/timescale clamping,money→numeric). - It streams with chunked, parallel
COPYand resumes from a checkpoint — important over Railway’s TCP proxy. pgferry planreports computed columns, skipped non-B-tree/filtered indexes, temporal tables, andNEXT VALUE FORdefaults before PostgreSQL is touched. See how to read plan output.- It creates objects as the connecting role, sidestepping ownership/
SET ROLEerrors thatpg_dump-style restores hit.
Destination prerequisites
Section titled “Destination prerequisites”- A Railway project with a Postgres service. Default database is
railway, default user ispostgres. - The connection variables from the service’s Variables tab. Railway’s Postgres image runs as a single-tenant container, so the
postgresrole has broad privileges —CREATE SCHEMA,CREATE EXTENSION(for extensions whose binaries ship in the image), etc. - For pgvector or other non-default extensions, deploy the matching Railway template/image variant — the base image only carries the standard contrib set.
Recommended pgferry config
Section titled “Recommended pgferry config”schema = "app"on_schema_exists = "error"unlogged_tables = falseresume = truevalidation = "row_count"chunk_size = 100000source_snapshot_mode = "single_tx"
[source]type = "mssql"source_schema = "dbo"# dsn supplied via PGFERRY_SOURCE_DSN
[target]# dsn supplied via PGFERRY_TARGET_DSN
[type_mapping]datetime_as_timestamptz = falsemoney_as_numeric = trueresume = true requires unlogged_tables = false. On MSSQL, source_snapshot_mode = "single_tx" uses SNAPSHOT isolation — see the MSSQL guide for the ALLOW_SNAPSHOT_ISOLATION details (and the Azure SQL to Neon guide if your source is Azure SQL).
Railway DSN, TLS, proxy, and networking notes
Section titled “Railway DSN, TLS, proxy, and networking notes”Railway exposes two connection URLs on the Postgres service:
| Variable | Host shape | Use from |
|---|---|---|
DATABASE_URL (private) | postgres.railway.internal:5432 | Another service inside the same Railway project |
DATABASE_PUBLIC_URL (public) | <name>.proxy.rlwy.net:<random-port> | An external migration host |
- From your laptop or any external host, use
DATABASE_PUBLIC_URL. The.railway.internalhost is not routable outside Railway. The public proxy port is randomly assigned — never hardcode5432for the external endpoint; read it from the variable. - TLS: Railway’s Postgres image uses a self-signed certificate. Use
?sslmode=require(encrypts without certificate-chain verification).sslmode=verify-fullfails because Railway does not publish a CA for the auto-generated cert. - Egress: traffic through the public proxy leaves Railway’s network and counts toward egress/network billing. A multi-GB migration over the proxy can add up.
- Private networking: if you run pgferry as a service inside the same Railway project, use
DATABASE_URL(the internal host) — no egress charges, lower latency. The private network is IPv6-based; thepgxdriver pgferry uses handles this as long as the host resolves.
Example external (public proxy) target DSN, with an MSSQL source DSN:
export PGFERRY_TARGET_DSN='postgres://postgres:<password>@<name>.proxy.rlwy.net:<proxy-port>/railway?sslmode=require'export PGFERRY_SOURCE_DSN='sqlserver://user:pass@mssql-host:1433?database=source_db'Inside Railway (same project, no egress):
export PGFERRY_TARGET_DSN='postgres://postgres:<password>@postgres.railway.internal:5432/railway?sslmode=require'Capacity gotcha
Section titled “Capacity gotcha”The Railway Postgres service is backed by a volume with a plan-dependent size cap, and WAL plus indexes built during the load consume extra space transiently. Pre-size or upgrade the volume before a large import so you do not run out mid-load.
Source-specific caveats (SQL Server)
Section titled “Source-specific caveats (SQL Server)”These come from the MSSQL side (full detail in the MSSQL guide):
- Choose the right
source_schemainstead of relying ondboblindly. - Decide
datetime_as_timestamptz; keepmoney_as_numeric = trueunless you want text preservation. datetime2/timefractional precision is clamped to PostgreSQL’s max scale of 6 (SQL Server allows 7).uniqueidentifiervalues are byte-reordered into standard UUID order.- Computed columns are materialized as values and reported for manual recreation.
- Non-B-tree indexes (columnstore, hash, XML, spatial) and filtered indexes are skipped with warnings.
NEXT VALUE FORdefaults, system-versioned temporal tables, andsql_variantcolumns produce semantic warnings — handle via hooks or manual DDL.
Step-by-step MSSQL to Railway Postgres migration flow
Section titled “Step-by-step MSSQL to Railway Postgres migration flow”- Add Postgres to your Railway project and copy
DATABASE_PUBLIC_URL(or use the internal URL if running inside Railway). - Confirm the volume is large enough for the dataset plus index/WAL overhead.
- Generate a config with
pgferry wizardor start from the snippet above; exportPGFERRY_SOURCE_DSNandPGFERRY_TARGET_DSN. - Run
pgferry plan migration.tomland resolve every warning (computed columns, skipped indexes, sequence defaults, temporal tables). - Run
pgferry migrate migration.toml; rerun on interruption (resume = true). - Recreate views, routines, triggers, and
NEXT VALUE FORdefaults via hooks.
Validation and cutover checklist
Section titled “Validation and cutover checklist”pgferry validate migration.tomlre-runs validation without redoing DDL orCOPY.- Confirm required extensions exist (and that you used the right image variant for pgvector etc.).
- Verify computed columns and sequence-backed columns on the target.
- Verify the volume has headroom after the load.
- Walk the cutover checklist and first production migration checklist.
Common failures for this provider pair
Section titled “Common failures for this provider pair”| Symptom | Cause | Fix |
|---|---|---|
could not translate host name "postgres.railway.internal" | Used the private host from outside Railway | Use DATABASE_PUBLIC_URL |
| TLS / certificate verification error | verify-full against a self-signed cert | Use sslmode=require |
Connection refused on :5432 externally | Hardcoded port instead of proxy port | Use the random proxy port from DATABASE_PUBLIC_URL |
single_tx fails on a read-only login | Snapshot isolation not enabled on the source | Enable ALLOW_SNAPSHOT_ISOLATION (see MSSQL guide) |
No space left on device mid-load | Volume too small for data + indexes | Pre-size/upgrade the volume |
See common failures and recovery.
Related
Section titled “Related”- MSSQL to PostgreSQL — generic SQL Server source guide
- Configuration reference
- Type mapping
- MSSQL minimal-safe example
- Cutover checklist · First production migration checklist
- Other destinations: MSSQL to Render Postgres · MSSQL to Supabase · MSSQL to Neon · MSSQL to PlanetScale Postgres · Azure SQL to Neon