Metabase H2 to PostgreSQL migration

A practical guide to moving a Metabase install from the embedded H2 application database to PostgreSQL or MySQL with load-from-h2, covering version matching, backups, downtime and verification.

On this page

Why a Metabase H2 to PostgreSQL migration is a one-way move that needs planning

Moving a Metabase install off its embedded H2 application database is a one-time load-from-h2 operation, and it must be run with the same Metabase version that last wrote the H2 file. Metabase documents this command as the supported route to PostgreSQL or MySQL, so the task is not a file copy or a reconfiguration. Before anything else, confirm which exact version last touched the H2 file, because the load depends on that match. Metabase ships H2 for demos and recommends PostgreSQL for production. MySQL 8.4.0 and MariaDB 10.6.0 and later are also supported, provided the database uses utf8mb4. The H2 database holds saved questions, dashboards, users, groups and permissions, so the choice of target is effectively a choice of where your analytics configuration will live for years. Confirm the target database engine and version against Metabase's application database documentation before provisioning anything. The move is harder than it looks for four reasons. The version must match. You need a clean, verified backup of the H2 file before you start, because the load is not something you can simply retry against a damaged source. The instance must be stopped or otherwise kept from writing to H2 during the load, which means someone has to agree on a window with the people who use dashboards every day. Finally, the target database becomes the system of record. Once Metabase runs against PostgreSQL, the H2 file is a fallback snapshot, not a live copy, and any changes made afterward live only in the new database. Plan for the one-way nature of this move. Decide who owns the cutover, who signs off that users, permissions and collections look correct, and how long the H2 backup will be retained. Ask your database team to size the PostgreSQL instance, create a dedicated database and role for Metabase, and agree on backup schedules before the first load attempt. Rehearsing the load on a copy of the H2 file in a non-production environment is the most practical way to find version or permission problems before the real window. The cost of getting this wrong is mostly time and trust rather than license fees. A failed load that leaves users locked out, or a dashboard set that silently differs from what people used before, will be noticed quickly. Treat the migration as a scoped change with a rollback plan, a named owner and a test checklist, and confirm each of these items with your implementation partner or internal team before the window is booked.

Which Metabase components and settings the H2 to PostgreSQL move touches

The move changes where Metabase keeps its own application data, not the databases your teams query. Each card below is a component or setting that either changes during the cutover or must be confirmed as unchanged before you switch.

Application database storage

The H2 file is the embedded application database that holds users, collections, saved questions, dashboards and settings. The move copies that content into a new PostgreSQL or MySQL database, and Metabase is then pointed at it. Confirm the target engine and version against the application database docs before you provision anything, and confirm the target uses utf8mb4 if you choose MySQL or MariaDB.

Metabase JAR and exact version

The one-time load-from-h2 command must run with the same Metabase version that last updated the H2 file. Record the exact version string from the running instance and keep that JAR available for the load step. Running a different build against the H2 file is the most common reason the load fails, so treat version matching as a precondition, not a detail to adjust later.

MB_DB_* connection settings

After the data is loaded, the Metabase instance must be configured with the MB_DB_* environment variables or equivalent configuration that identify the new engine, host, database name, port and credentials. Review every variable on the new host, including any that carry over from a previous install, and confirm the instance reads the new settings on restart. Keep credentials out of shell history and general configuration files.

Encryption key for stored credentials

Metabase encrypts the credentials it stores for data source connections using an encryption key. The loaded application database only decrypts those credentials if the same key is configured on the new instance. Confirm the key source and value with your own records before cutover, store it in your secrets manager, and test a saved data source connection immediately after the switch.

Data source connections that stay unchanged

The databases that teams query through Metabase are not moved by this procedure. Their hosts, accounts and network paths remain as they were, and the Metabase server still needs network access to each of them. Build an inventory of every connection, confirm its reachability from the new host, and check the firewall or allow-list rules that reference the old Metabase server address.

Edition-specific settings to confirm

The open source edition under the AGPL and the commercial editions share the same backend JAR, so the procedure is the same for both. Settings that differ by edition, such as licensing or plan-linked features, must still be confirmed in the Metabase documentation for your edition before the cutover. Ask for that confirmation in writing from your Metabase contact or check it against the current docs.

Choosing the target database for the Metabase migration

This table compares PostgreSQL, MySQL, MariaDB and the embedded H2 database as the home for Metabase's application data. Read each row against your team's existing database skills, backup tooling and hosting model. PostgreSQL is the default we recommend for production, and the other engines fit when a specific organizational constraint points to them.

TargetWhen it fitsOperational costDecision
PostgreSQLMetabase docs recommend it for production. Fits teams with PostgreSQL skills, backup tooling or a managed service.Someone patches the server, runs backups and restores, and plans connection limits for every Metabase node.Default for production. Confirm your exact version with Metabase docs and your managed provider.
MySQL 8.4.0 or laterFits a team that already runs MySQL well and has its own backup and clustering practice.Same patching, backup and restore duties. Use utf8mb4 and confirm the character set before the load.Supported when an existing MySQL standard applies. Confirm the engine and character set before loading.
MariaDB 10.6.0 or laterFits organizations standardized on MariaDB, with database staff who manage it and a provider that supports it.Same duties as MySQL. Confirm the minimum version, utf8mb4 setting and any managed-service limits first.Supported when MariaDB is already the standard. Verify the version and character set before the load.
Embedded H2 (test only)Fits a short-lived demo or evaluation that will be discarded. Not a fit for a production instance.Lowest setup effort, but data lives in a file on the Metabase host, so backups and moves are manual.Use only for a throwaway test. Plan the migration step if the test becomes production.
One-time load-from-h2 stepNeeded for any move off H2, whichever engine you choose. Run it with the Metabase version that last updated the H2 file.Rehearse in a copy first. Back up the H2 file and confirm the target is ready before running.Required step for any move off H2. Confirm the version match before you start.

How the load-from-h2 command moves a Metabase install step by step

This sequence moves a Metabase instance from its embedded H2 application database to PostgreSQL or MySQL without losing users, saved questions or dashboards. Each step ends with a check that the team records before moving on, so the cutover can be verified or reversed.

  1. Confirm the version that last wrote H2

    The team that runs the instance confirms which Metabase release last updated the H2 application database, then obtains the same release JAR for the load. Metabase documents that the one-time command must run with that version, so a newer or older JAR is the first mismatch to rule out. The step closes when the version is written down next to the H2 file, and the JAR checksum matches the release it was downloaded from. Ask your implementation partner or internal platform owner to confirm this before any downtime is scheduled.

  2. Provision an empty target database

    A database administrator creates an empty database for the Metabase application and a dedicated role that owns it, with no other applications sharing it. Metabase recommends PostgreSQL for production, and MySQL 8.4.0 or MariaDB 10.6.0 and later are supported with utf8mb4. For PostgreSQL, confirm the required encoding and collation in the Metabase application database documentation before creating the database. The step closes when the role connects with its own credentials, the database holds no tables, and the encoding matches the documented requirement.

  3. Back up H2 and stop writes

    The operator stops all writes to the instance, including user edits and scheduled jobs, then copies the H2 file to storage outside the host. To verify the copy, restore it to a scratch location running the same Metabase version and confirm it opens and lists the expected users, questions and dashboards. Record a checksum for the copy. The step closes when the restore test passes and the verified copy is named as the rollback point. Do not restart the original instance for normal use until the move is finished or rolled back, because new edits would diverge from the copy.

  4. Run the load-from-h2 command

    With the instance stopped and the matching JAR in place, the operator runs the load-from-h2 command against the new database, using the H2 file and target connection details as described in the Metabase migration documentation for that release. Watch the output until the command reports completion with no errors. Then compare row counts for users, groups, collections, saved questions and dashboards between the H2 copy and the target. The step closes when every compared count matches and the log shows no failed statements.

  5. Point the instance at the new database

    The operator updates the instance configuration so the application database connection uses the new PostgreSQL or MySQL database and the dedicated role, then starts Metabase on the same version. On startup, Metabase checks the database and runs any migrations for that release, so the startup log needs review for errors. The step closes when the instance starts cleanly, the web application loads, and the administrator account opens the admin pages backed by the new database.

  6. Verify sign-in, data sources and content

    The team tests sign-in with an administrator account and a standard user account, including any Google sign-in or LDAP login already configured. It then opens a saved dashboard that queries a live data source and confirms it renders current results, and runs a saved question from a collection with restricted permissions. Permissions on that collection should behave as they did before the move. The step closes when the sign-ins, the live dashboard and the restricted question all behave as before, and the business owner signs off on a short checklist.

  7. Keep the rollback copy until acceptance

    The team keeps the verified H2 copy and its checksum in storage until users and the business owner accept the new instance. For this move, rollback means restoring the verified H2 copy, running the same Metabase version against it and pointing the instance back at that file. Confirm this path with the Metabase documentation and your partner before cutover, because it is a different procedure from rolling back an upgrade. The step closes with written acceptance and an agreed date for retiring the copy.

What goes wrong when the Metabase version does not match the H2 file

The load-from-h2 command has to run with the same Metabase version that last wrote the H2 file. The H2 file records the schema and data as that version left them, so a different JAR can refuse to start the load, copy only part of the data, or write a result that is hard to diagnose later. Metabase's documentation sets the matching-version requirement explicitly, which makes this the most avoidable failure in the move. Treat any run with a mismatched JAR as a failed attempt. Restore the target PostgreSQL database to its empty state, then rerun the load with the correct build.

Start by identifying the version that last touched the H2 file, which is not always the JAR you run today. Check the file name or container image tag of the current deployment, the version line in the startup logs, and the version information shown in the Metabase admin area if the instance still starts. Then check the history: if the JAR was replaced in place or the container image was changed after the last clean start, the file may have been written by the earlier build. Record the exact build string in your migration runbook and confirm it against your own install, since the version can differ from the one you expect.

If the original JAR is no longer on hand, do not upgrade the instance to get a matching binary. An upgrade runs application database migrations and changes the H2 file, which makes the match harder to recover. Instead, retrieve the release that matches the recorded version from the Metabase GitHub repository or the release artifacts for that version, verify the artifact came from an official source, and check the approach against the H2 migration documentation before running anything. Test the retrieved build on a copy of the H2 file, not on the only copy you have.

What drives the effort and cost of a Metabase H2 to PostgreSQL migration

This table lists the factors that change how much work a move off the embedded H2 database involves, how each one can be kept down, and what a quote should itemize. Read it before scoping, because most of the effort comes from content volume, credentials, environment count and the window the business will accept rather than from the database command itself.

DriverWhy it changes effort or costHow to keep it down
Size of the H2 file and saved contentA larger file takes longer to load and verify, and more saved questions, dashboards and collections mean more items to check after the switch.Rehearse on a copy of the file, archive or remove unused content before the move, and script item counts so the before and after comparison is automatic.
Number of users and groupsEach user, group membership and permission mapping must survive the load, and mismatches usually surface only when a specific person logs in.Export a permissions inventory before the move, script a comparison of users and group memberships, and sample logins for each group.
Data source connections and encrypted credentialsDatabase connections and their stored credentials must still decrypt after the move, which depends on the encryption key being kept unchanged in the new environment.Confirm the encryption key handling in the rehearsal, test every data source with a query, and record which connections use which credentials.
Number of environments (dev, test, production)The same procedure must run in each environment with its own version match, backups and checks, so effort multiplies with each copy.Write one runbook with environment-specific variables, run it first in the lowest environment, and reuse the same check scripts in each stage.
Maintenance window the business will acceptA shorter window leaves less room for a failed load, a rollback or a slow verification, which pushes work into rehearsal and off-hours planning.Freeze content changes during the window, measure the load time in rehearsal, and agree a rollback point before the window opens.
Availability of a rehearsal environmentWithout a production-like copy, the first real run becomes the test, so problems found late cost more to fix and may force a second window.Build a copy from a recent production backup, match the Metabase version exactly, and run the full procedure at least once before the change.
Compliance evidence the change must leave behindAuditors or internal control owners may expect records of the version used, the backup taken, who approved the change and the post-change checks.Agree the evidence list in advance, capture command output and check results into a change record, and store backups with their restore test results.
Version match and upgrade pathThe load must run with the Metabase version that last updated the H2 file, so an older or newer binary adds a version alignment step.Record the exact version from the running instance before starting, and confirm the version support status with Metabase before choosing a target release.

How to handle credentials, encryption keys and copied data during the switch

The H2 to PostgreSQL switch creates several copies of sensitive material: the application database, the backup file, and the configuration that unlocks stored data source credentials. Each card below covers one of these items so the cutover team can decide who holds what, where it lives, and how long it stays before go-live is signed off.

Least-privilege role for the application database

Create a dedicated PostgreSQL role for Metabase that owns only the application database it needs, rather than reusing a superuser or a shared administrator login. Confirm the role can connect, create and alter its own schema objects, and cannot read other databases on the same server. Ask the DBA to document the grants so the load-from-h2 run and later upgrades use the same account.

Encryption key and database secrets handling

The Metabase encryption key and the new PostgreSQL password should be supplied through environment variables or a secrets manager at process start, not typed into shell commands or written into files that get committed. Shell history, CI logs and infrastructure-as-code repositories are the usual leak points. Confirm the key is backed up separately from the database, because losing it can leave encrypted stored credentials unreadable after the move.

Access control on the H2 backup file

The H2 file holds user accounts, password hashes and stored data source credentials, so it is as sensitive as the production database itself. Restrict read access to the migration operator and the account running the load, store it on encrypted storage, and log who copies it. Do not attach it to tickets, chat threads or shared drives during troubleshooting.

Retention and disposal of the H2 backup

Decide before cutover how long the H2 file is kept as a rollback point and who approves its deletion. Keep it until the PostgreSQL instance has passed the post-switch checks and at least one normal operating cycle, then destroy it using the organization's media sanitization procedure. Record the deletion date, because an undocumented copy of credentials is a common audit finding.

TLS between Metabase and PostgreSQL

Require encrypted connections from the Metabase process to the PostgreSQL application database, and verify the server certificate rather than accepting any certificate. The JDBC connection string should name the required SSL mode and the trusted CA bundle. Check that the setting survives the first restart after cutover, since a connection that silently falls back to plaintext is easy to miss.

Ongoing controls after go-live

The steps above cover the switch itself. Ongoing controls such as credential rotation, permission reviews and audit logging belong to the security hardening work for the live environment. Plan that work as a separate item so the cutover checklist stays focused on handling the copied data and secrets.

How to confirm the Metabase migration worked and how to roll back if it did not

Treat the migration as unproven until you have compared the new PostgreSQL application database with the H2 source. Before the cutover, record the number of user accounts, groups and group memberships, along with the list of active collections. After the switch, check the same figures in the admin people and groups views. Any difference should be explained before users return. Missing accounts often point to a failed load, a sign-in source that changed, or accounts that were deactivated in the old system. Write down each count and each explanation so the change record shows that the numbers were reconciled rather than assumed to match.

Next, open a sample of saved questions and dashboards that business users rely on most, and confirm they render with live data from the connected databases. Also confirm that scheduled subscriptions and alerts are set up and that their next run times appear correctly. Send a test subscription to an internal mailbox if the schedule would otherwise run outside your working hours. Then spot-check collections and permissions by role. Sign in as a representative administrator, a data analyst and a read-only viewer, and confirm each one sees the same collections, data sources and questions it saw before the migration.

Sign-in needs its own test. For each authentication method in use, sign in with a user who depends on that method and confirm the correct group membership and permissions apply. Google sign-in and LDAP are available on the open source and Starter plans. If your deployment uses SAML, OIDC, JWT or SCIM, those require the Pro or Enterprise plans, so test them only where they are configured. Also confirm that the sign-in pages point to the new server address and that certificates and redirect URLs still match. Record which account was used, the method, the result and the time of each test.

Rollback is the step most teams hope not to need, so plan it before cutover. The upgrading documentation recommends restoring the pre-upgrade application database backup as the rollback route. It also describes running the migrate down command with the newer JAR as an alternative. Confirm with Metabase's documentation or support how these steps apply to a move from H2, and whether returning to the H2 file is supported. Keep the H2 backup file and the PostgreSQL backup taken after the load until the business accepts the new system. Any content created in PostgreSQL after cutover would be lost if you restore the older backup, so decide who approves rollback and by when.

The change record should let an auditor or a future engineer retrace what happened. Keep the load command output, the Metabase version used for both the load and the running server, and the reconciliation counts with their explanations. Add the check results, the names of the test accounts and roles, dated screenshots of key dashboards, and the sign-in test log. Record backup file names, sizes, checksums and storage locations, plus the retention date your policy requires. Finally, keep the written acceptance from the system owner and the rollback decision criteria, so the final state of the migration is clear.

What to back up, monitor and document once PostgreSQL holds the Metabase data

After the switch, the PostgreSQL application database becomes the system of record for Metabase users, saved questions, dashboards and permissions. The controls below keep that database recoverable, observable, documented and owned, so the next upgrade does not depend on knowledge that only the migration team holds.

Application database backup and restore test

Schedule regular backups of the PostgreSQL application database, separate from the H2 file you kept for rollback. A backup only counts once a restore has been tested into a scratch instance running the same Metabase version, and the test should confirm that users can sign in and that a sample of saved questions and dashboards loads. Record how long the restore took so you know your real recovery window.

Database connection monitoring

Watch the number of open connections from the Metabase application to PostgreSQL and compare it against the server's configured limit. Set alerts for sustained growth, which often points to connection leaks or to several instances sharing one database without a pool sized for them. Confirm the pool settings with your DBA before the first production week, because the defaults may not match your user count.

Storage, availability and query health

Track free disk space, table growth and replication or failover status for the PostgreSQL instance that hosts the application database. Metabase stores query results metadata, logs of activity and cached items there, so growth can be gradual and easy to miss. Agree who receives the alerts, the response time expected, and the point at which the database team escalates to the Metabase owner.

Change record with migration details

Keep a change record that names the Metabase version used for the load-from-h2 command, the migration date, the target engine and its version, the connection method used, and each check that was run with its result. Attach the backup identifiers and the rollback decision. Auditors and the next upgrade team will ask for this record first, so store it with the infrastructure change system rather than in a personal document.

Runbook for the next upgrade

Revise the upgrade runbook for a world where Metabase runs application database migrations automatically on startup. The runbook should require a verified backup before any upgrade, reduce a cluster to a single node for major upgrades because of the migration lock, and step through releases in order from older versions. Metabase LTS versions receive support for 14 months and non-LTS versions usually reach end of life after about 2 months, so plan a review of this runbook at each version decision, not only when something breaks.

Database ownership after handover

Name one owning team for the PostgreSQL instance, one for the Metabase application, and a named person for each database role, including who holds the credentials used by Metabase and who can approve schema-affecting changes. Document who is paged for an outage and who signs off on restores. Without named owners, the database tends to drift between infrastructure and analytics teams, and the first failed upgrade becomes the first ownership discussion.

Questions teams ask before moving Metabase off H2

Can our internal team move Metabase off H2 without a partner?

Yes. Metabase documents a one-time load-from-h2 command that moves the H2 application database to PostgreSQL or MySQL, so an internal team with shell access and database administration skills can run it. Two things need a named owner before the work starts. The version match: the command must run with the same Metabase version that last updated the H2 file. The rollback plan: someone must decide what is kept, where the backups live and who signs off on returning to H2 if the cutover fails. Teams that lack a rehearsal environment or have not run a Metabase upgrade before often ask for outside help with the rehearsal and the checks after the switch, which can be scoped separately.

How long does a Metabase H2 to PostgreSQL migration take?

The duration depends on the drivers, not on the command itself. The main drivers are the size of the H2 file, how many environments need the move (development, staging and production usually each need a run), whether a rehearsal copy is required, how many connected systems such as SSO, reverse proxies and monitoring must be checked, and how quickly change approvals can be obtained for the outage window. A written quote should state the assumed window in calendar terms, list the environments included and name the rehearsal count. If a provider cannot explain the assumptions behind a timeline, treat the timeline as unverified and ask for the assumptions in writing before you compare offers.

What does a Metabase database migration cost?

The cost has several components, and only some of them are one-time. One-time: staff time for planning, the rehearsal run, the production cutover, validation of questions, dashboards and permissions, and the rollback test. Any outside engagement is also one-time and is fixed-scope, with deliverables agreed in writing before work starts. Recurring: the PostgreSQL or MySQL platform you run, its backups, monitoring and patching. Costs rise with the number of environments, the size of the application database, the need for a rehearsal copy, change windows that fall outside normal hours and the number of systems that depend on the instance. Ask for a quote that separates labor, environment costs and any recurring database charges, and that states what is excluded.

Will moving Metabase to PostgreSQL cause downtime?

Yes, the documented pattern is stop-and-load, so plan for a window in which Metabase is unavailable. The application is stopped, the H2 file is loaded into the target database with the matching Metabase version, and Metabase is then started against the new database. Users cannot sign in or run questions during that period, and any scheduled subscriptions or alerts that fire inside the window will not run as expected. Schedule the window with the business owners, announce it, and agree in advance who decides whether to continue or roll back if the load or the first start fails. Confirm the expected duration of the load with a rehearsal on a copy of your data, because the size of your H2 file is the main variable.

Can we roll back a Metabase migration from H2 to PostgreSQL?

Yes, provided you preserve the original H2 file and a backup of the target database before cutover. The H2 file is your fallback: if the switch fails or the new database does not perform as expected, you stop Metabase, point it back at the untouched H2 file and start it again. The key limit is data written after cutover. Dashboards, questions, users and permissions created or changed on PostgreSQL will not exist in the H2 copy, so the rollback decision must be made quickly, or those changes must be captured and replayed. Test the rollback in the rehearsal environment, record the steps and name the person who can authorize it. Confirm the exact procedure against the migration documentation for your Metabase version.

What do we need to provide for a Metabase H2 migration engagement?

A provider needs four things from your side to plan and execute the work. Access to the instance: shell access to the host running Metabase, the JAR and its configuration, and the H2 application database file. The database platform: a PostgreSQL or MySQL server with credentials, backup tooling and a network path from the Metabase host. A rehearsal copy: a non-production Metabase and a copy of the H2 file so the load and the rollback can be tested before production. A named approver: one person who owns the change window, accepts the cutover decision and signs off the post-switch checks. Agree these inputs in the written scope, because missing access is one of the most common causes of schedule slippage.

Which database should Metabase use after leaving H2?

For production, Metabase recommends PostgreSQL as the application database. MySQL is also supported: MySQL 8.4.0 or MariaDB 10.6.0 and later, using the utf8mb4 character set. Choose based on what your operations team already runs and can back up, monitor and patch. Whichever you choose, confirm the exact version against the application database documentation before you build the target, set utf8mb4 explicitly if you use MySQL or MariaDB, and give the Metabase database user only the privileges it needs. Also confirm that your backup method can restore the application database independently of the H2 file, since that restore is the path you will rely on for upgrades and rollbacks later.

How DeployOpen plans, runs and hands over a Metabase database migration

DeployOpen scopes the move from the embedded H2 application database to PostgreSQL in writing before work starts, then runs it against a tested backup in your own environment. The engagement ends with a documented cutover, verification checks and a handover your team can repeat for future upgrades.

How we scope

  • Confirm the Metabase version that last wrote the H2 file, since the one-time load-from-h2 command must run with that same version.
  • Record the target PostgreSQL or MySQL instance, including version, character set and the network path from the Metabase host.
  • Agree in writing which downtime window, rollback point and acceptance checks apply, and what happens to any post-cutover changes.
  • Engagements are fixed-scope, with deliverables and timelines confirmed in writing before work starts; ongoing support is available as a retainer.

How we build

  • Take and verify a copy of the H2 file and the running configuration before any migration step touches production.
  • Run load-from-h2 in a staging copy first, then review row counts and the admin, database and collection records it loaded.
  • Set the production application database credentials with least privilege and confirm the connection from the Metabase JAR.
  • Repeat the rehearsal until the runbook gives the same result twice, then document the exact commands used.

How we hand over

  • Deliver a runbook with the pre-migration backup steps, cutover sequence, rollback path and the commands to run in order.
  • Provide a checklist of post-switch checks covering login, data source connections, scheduled jobs, subscriptions and sharing settings.
  • Walk your operations team through the procedure and record who owns each step, the backup location and the retention approach.

How we support

  • Support the first days after cutover with a defined issue log, so database errors and login problems are triaged against the runbook.
  • Plan future upgrades around the rule that a PostgreSQL application database is backed up before each Metabase upgrade and rollback.
  • Offer ongoing support, upgrades and testing as a retainer, with the scope of that retainer agreed in writing.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.