MySQL to Neon
This is an operator’s playbook for a MySQL to Neon migration. It covers the Neon-specific endpoint, scale-to-zero, and TLS details you need to move MySQL into Neon’s serverless PostgreSQL — the parts generic import tutorials leave out.
If you searched for how to migrate MySQL to Neon or move a MySQL database to Neon Postgres, the short version is: point pgferry at the unpooled (direct) Neon endpoint, disable scale-to-zero for the load, and let the MySQL type mapping handle enums, sets, and unsigned integers that pgloader mishandles.
What this guide is for
Section titled “What this guide is for”Use this guide when you have a live MySQL database (self-hosted, RDS, Aurora MySQL, Cloud SQL, PlanetScale MySQL, etc.) and want it on Neon Postgres. It assumes you have a Neon project and branch. For source-side behavior that is not Neon-specific, read the generic MySQL to PostgreSQL guide alongside this page.
Why use pgferry instead of generic pgloader advice
Section titled “Why use pgferry instead of generic pgloader advice”The usual “mysql to neon” advice is pgloader, which struggles on real migrations:
pgloaderhas no resume. Over a Neon connection that auto-suspends or hiccups, an interrupted load restarts from zero.pgferrycheckpoints and resumes.- MySQL enums, sets, unsigned integers,
tinyint(1), and zero dates are explicit, documented knobs inpgferry;pgloaderguesses and frequently pickstext. pgferrystreams with chunked, parallelCOPYand runs aplanpreflight that surfaces skipped indexes, generated columns, and required extensions before PostgreSQL is touched.pgferrycreates objects as the connecting role, avoiding the ownership/SET ROLEerrors thatpg_dump/pg_restorehit against Neon’s non-superuser role.
Destination prerequisites
Section titled “Destination prerequisites”- A Neon project and branch. Note the default database (
neondb) and the owner role (neondb_ownerby default), or create your own. - The connection string from the Neon console (Connect button), which gives you both pooled and direct host forms.
- Neon’s owner role is a member of
neon_superuser— not a true superuser, but it can create databases, schemas, tables, indexes, FKs, sequences, and allow-listed extensions. That is everything pgferry needs.
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 = "mysql"# dsn supplied via PGFERRY_SOURCE_DSN
[target]# dsn supplied via PGFERRY_TARGET_DSN
[type_mapping]tinyint1_as_boolean = falsejson_as_jsonb = trueenum_mode = "check"set_mode = "text"sanitize_json_null_bytes = trueresume = true requires unlogged_tables = false (see the configuration reference). Use source_snapshot_mode = "single_tx" for one consistent read view on a live MySQL source.
Neon DSN, TLS, pooling, and firewall notes
Section titled “Neon DSN, TLS, pooling, and firewall notes”Neon endpoints differ only by a -pooler suffix on the endpoint ID:
| Endpoint | Host shape | Use for |
|---|---|---|
| Direct (unpooled) | ep-<id>.<region>.aws.neon.tech | Migrations, DDL, bulk load |
| Pooled | ep-<id>-pooler.<region>.aws.neon.tech | App runtime / many short connections |
- Use the direct (unpooled) endpoint for the migration. The pooled endpoint is PgBouncer in transaction mode and breaks session-scoped DDL, temp tables, and the session features pgferry relies on. Neon’s own import guidance says to avoid the pooled string for bulk load. The direct endpoint’s connection count scales with compute size, which is far more than pgferry needs.
- TLS is mandatory. Neon rejects non-TLS connections. Use
?sslmode=requireat minimum;sslmode=verify-fullis better and works against the system trust store (Neon uses a public CA). Neon’s console strings also addchannel_binding=require, which the Go/pgxdriver pgferry uses supports. - IP Allow (allowlisting) is a paid-plan feature and defaults to open. If you have it enabled, add your migration host’s egress IP/CIDR before starting.
Example direct-endpoint target DSN:
export PGFERRY_TARGET_DSN='postgresql://neondb_owner:<password>@ep-<id>.<region>.aws.neon.tech/neondb?sslmode=require'export PGFERRY_SOURCE_DSN='user:pass@tcp(mysql-host:3306)/source_db'Scale-to-zero — the Neon-specific gotcha
Section titled “Scale-to-zero — the Neon-specific gotcha”Neon computes auto-suspend after a period of inactivity (5 minutes by default; fixed on the Free plan). An active query keeps the compute busy, but to remove any risk of a suspend during quiet gaps in a long load, disable scale-to-zero (or raise the timeout) for the migration window, then re-enable it. Set this per branch in Branches → compute → Edit. For large datasets, also bump the compute size — more RAM means more max_connections and more local disk for index builds.
Neon’s idle_in_transaction_session_timeout defaults to 5 minutes; keep transactions moving so an idle gap does not terminate one mid-migration.
Source-specific caveats (MySQL)
Section titled “Source-specific caveats (MySQL)”Decide these on the MySQL side; they are not Neon-specific:
enum_mode/set_mode— howENUMandSETland in PostgreSQL.tinyint1_as_boolean— only iftinyint(1)truly means boolean.widen_unsigned_integers/add_unsigned_checks— preserve unsigned ranges.zero_date_mode—0000-00-00→NULLor error.- Generated columns copy as values, not expressions.
FULLTEXT, prefix, and expression indexes are reported and skipped.- For
_cicollations,ci_as_citext = trueneeds thecitextextension — Neon supports it viaCREATE EXTENSION(or letpgferrysurface it inplan).
See the MySQL guide and type mapping for the complete picture.
Step-by-step MySQL to Neon migration flow
Section titled “Step-by-step MySQL to Neon migration flow”- Create the Neon project/branch and copy the direct connection string (no
-pooler). - Disable scale-to-zero for the target branch and, for large data, raise the compute size.
- Generate a config with
pgferry wizardor start from the snippet above. - Export
PGFERRY_SOURCE_DSNandPGFERRY_TARGET_DSN. - Run
pgferry plan migration.tomland resolve every warning. - Run
pgferry migrate migration.toml. Rerun on interruption —resume = truecontinues from the checkpoint. - Recreate views, routines, and triggers 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 (
CREATE EXTENSIONfor anythingplanflagged). - Re-enable scale-to-zero and restore the compute size if you changed it.
- Spot-check enum/set and
tinyint(1)columns. - 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 |
|---|---|---|
| Session/DDL errors, temp-table failures | Connected via the -pooler endpoint | Use the direct (unpooled) endpoint |
| Connection refused / SSL required | Missing TLS | Append ?sslmode=require |
| Compute suspended mid-load | Scale-to-zero fired during a quiet gap | Disable scale-to-zero for the migration window |
extension "..." is not available | Extension not in Neon’s supported set | Map the type differently or recreate manually |
| Slow index builds / OOM | Compute too small | Raise the compute size for the load |
See common failures and recovery for the general catalog.
Related
Section titled “Related”- MySQL to PostgreSQL — generic source guide
- Configuration reference
- Type mapping
- MySQL minimal-safe example
- Cutover checklist · First production migration checklist
- Other destinations: MySQL to Supabase · MySQL to Railway Postgres · MySQL to Render Postgres · MySQL to PlanetScale Postgres · MSSQL to Neon