Migrate from MySQL to PostgreSQL
Updated
MySQL support for the Management store is deprecated and will be removed in an upcoming release. This guide moves an existing MySQL deployment to PostgreSQL with the netbird-mysql-migrate tool.
The tool copies the following into PostgreSQL:
- the Management store, from MySQL
- the activity events (
events.db) and embedded IdP (idp.db) stores, from their SQLite files
The tool only reads your MySQL database and doesn't change it.
Before You Begin
- Upgrade the management server to
v0.81.0or later and start it once on MySQL, so the database schema is current. That's thenetbird-serverservice, ormanagementon the older multi-container setup, not the NetBird client. Use the tool from the same release. - Prepare an empty PostgreSQL database. To run one in Docker, see Set Up PostgreSQL. The examples in this guide connect with
sslmode=require; useverify-fullto also verify the server certificate. Usesslmode=disableonly when PostgreSQL runs on the same host or Docker network as the management server. - Run the tool on a Linux machine that can reach both MySQL and PostgreSQL. The tool is Linux-only, built for
amd64,arm64, andarm.
Step 1: Get the Migration Tool
VERSION=0.81.0
curl -L -o netbird-mysql-migrate.tar.gz \
https://github.com/netbirdio/netbird/releases/download/v${VERSION}/netbird-mysql-migrate_${VERSION}_linux_amd64.tar.gz
tar xzf netbird-mysql-migrate.tar.gz
chmod +x netbird-mysql-migrate
On arm64 or arm, replace linux_amd64 with linux_arm64 or linux_arm.
Step 2: Stop the Management Server and Back Up
Stop the management server and copy its data directory, which holds events.db and idp.db:
docker compose stop netbird-server
mkdir backup
docker compose cp -a netbird-server:/var/lib/netbird/. backup/
chmod 700 backup
On the older multi-container setup, use the management service instead of netbird-server.
Back up MySQL as well:
mysqldump --no-tablespaces -h <MYSQL_HOST> -u <MYSQL_USER> -p <MYSQL_DB> > backup/netbird-mysql.sql
The backup holds password hashes from idp.db and a full database dump, so chmod 700 keeps it readable only by you. --no-tablespaces avoids a PROCESS privilege error when you dump as the NetBird database user.
Step 3: Run the Migration
./netbird-mysql-migrate \
--mysql-dsn '<MYSQL_USER>:<MYSQL_PASSWORD>@tcp(<MYSQL_HOST>:3306)/<MYSQL_DB>' \
--postgres-dsn 'host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require' \
--events-db backup/events.db \
--auth-db backup/idp.db
When it finishes, you see:
management: 1520 rows from 39 tables
activity: 8230 rows from 2 tables
auth: 41 rows from 12 tables
Done. Point the store, activity and auth store settings at Postgres before starting the server.
- All stores go into the database given by
--postgres-dsn. To keep the activity or auth store in a separate database, add--events-postgres-dsnor--auth-postgres-dsn. - A missing
events.dboridp.dbis skipped. For example, deployments on an external IdP have noidp.db. - MySQL timestamps are read as UTC. If your management server runs in another time zone, for example with
TZset on the container, add--mysql-timezone, such as--mysql-timezone Europe/Berlin. - The tool stops if the PostgreSQL database already holds NetBird data.
Step 4: Point the Management Server at PostgreSQL
Change only the store settings. Keep the encryption key (server.store.encryptionKey in config.yaml, DataStoreEncryptionKey in management.json) as it is: the tool copies encrypted values as they are, so the management server needs the same key to read them.
Combined setup (config.yaml)
Set all three stores to PostgreSQL in config.yaml, using the DSNs you migrated into:
server:
store:
engine: "postgres"
dsn: "host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require"
activityStore:
engine: "postgres"
dsn: "host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require"
authStore:
engine: "postgres"
dsn: "host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require"
Remove any NETBIRD_STORE_ENGINE_MYSQL_DSN or NB_STORE_ENGINE_MYSQL_DSN variable from docker-compose.yml.
Older multi-container setup (management.json)
In management.json, set the store engine and, if the file has an EmbeddedIdP section, its storage:
"StoreConfig": {
"Engine": "postgres"
},
"EmbeddedIdP": {
"Storage": {
"Type": "postgres",
"Config": {
"DSN": "host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require"
}
}
}
Then, on the management service in docker-compose.yml, replace NETBIRD_STORE_ENGINE_MYSQL_DSN with:
environment:
- NETBIRD_STORE_ENGINE_POSTGRES_DSN=host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require
- NB_ACTIVITY_EVENT_STORE_ENGINE=postgres
- NB_ACTIVITY_EVENT_POSTGRES_DSN=host=<PG_HOST> port=5432 user=<PG_USER> password=<PG_PASSWORD> dbname=<PG_DB> sslmode=require
Step 5: Start and Verify
docker compose up -d
Log in to the dashboard and confirm that your peers, users, and activity events are there. Then delete the backup directory.
Roll Back
The tool doesn't change MySQL, so to roll back, revert the store settings from Step 4 and restart the management server. Changes made while running on PostgreSQL aren't carried back to MySQL.
A failed run leaves no data in PostgreSQL, so you can fix the problem and run the tool again.
Troubleshooting
Tool exits with "target already holds data"
The PostgreSQL database for the store named in the error already holds NetBird data. If an earlier run of the tool finished, your data is already there, so continue with Step 4. Otherwise, point the tool at an empty database.
Tool exits with "tls error: server refused TLS connection"
The DSN requires TLS (sslmode=require or verify-full), but PostgreSQL doesn't accept TLS connections. Enable TLS on PostgreSQL. If PostgreSQL runs on the same host or Docker network as the management server, you can use sslmode=disable instead, in the tool's DSNs and in the Step 4 settings.
Tool exits with "source lacks columns"
Your data was last written by an older release than the tool. Start the management server once against MySQL on the same release as the tool, then repeat Step 2 so your backup holds the updated events.db and idp.db, and run the tool again.
Tool exits with "no NetBird tables found in the source"
The error names the store it came from. For management, check the database name in --mysql-dsn. For activity or auth, check that --events-db or --auth-db points to the events.db or idp.db in your backup.
Management server doesn't start: "activityStore.dsn is required when activityStore.engine is postgres"
The activityStore block in config.yaml sets engine: "postgres" without a dsn. Add the DSN as shown in Step 4. You can see this error with docker compose logs netbird-server.

