Moving Cloud SQL out of Google Cloud? WZ-IT supports Google Cloud migration to Hetzner with target architecture, data transfer and a cutover plan. Book an initial consultation
A Cloud SQL database is only part of a migration. Logins, extensions, applications and subsequent operations must also fit. This guide covers Cloud SQL for PostgreSQL to self-operated PostgreSQL on a Hetzner VM, with a planned write freeze. It is not a MySQL guide and does not describe a test carried out on a customer environment.
As of 19 October 2026. Commands are Bash templates. Replace hosts, roles, database names, certificates and paths before rehearsal. We recommend an isolated restore first, followed by a production procedure with documented acceptance criteria.
Table of contents
- Which migration method fits?
- Check versions, roles and extensions
- Prepare source access and target
- Create and restore the final dump
- Check data and application permissions
- Define cutover and rollback
- Our approach at WZ-IT
- Further guides
Which migration method fits?
Google documents exporting Cloud SQL with pg_dump. A dump exports a database, not an entire Google project. We recommend the following distinctions:
| Condition | Approach | Clarify first |
|---|---|---|
| Write freeze for transfer and acceptance is acceptable | Dump and restore as described here | Rehearsal, data volume, maintenance approval |
| Applications must write during initial transfer | Separate logical replication plan | Source configuration, network, tables and catch-up |
| Other services remain on Google Cloud | Partial migration | Latency, connectivity, identities and traffic |
Logical replication is not a complete database clone: DDL and sequence values are not replicated automatically; large objects are also excluded (PostgreSQL logical replication restrictions). Cloud SQL configuration therefore belongs in a separate plan. This procedure does not configure replication.
For the target decision, we recommend assigning responsibility for updates, backups, recovery, monitoring and incidents before moving. Ordering a VM does not replace this operational plan. The Cloud Exit Check assesses these dependencies before a migration is commissioned.
Check versions, roles and extensions
Collect at least the following from the source:
SHOW server_version;
SELECT current_database(), pg_size_pretty(pg_database_size(current_database()));
SELECT extname, extversion FROM pg_extension ORDER BY extname;
SELECT schemaname, tablename, policyname, roles FROM pg_policies;
SELECT datname, pg_encoding_to_char(encoding), datcollate, datctype
FROM pg_database WHERE datname = current_database();
The major version of pg_dump must not be older than the source server major version. Dumps are intended for transfer to newer versions; a downgrade is not guaranteed (pg_dump compatibility). For this procedure, we recommend the same supported major version on the target; an upgrade can be planned separately. Use pg_dump and pg_restore from the same installation of that major version. A newer pg_dump does not guarantee SQL compatible with an older target server, even when its major version matches the source.
| Inventory | Target decision |
|---|---|
| Extensions and versions | Check available packages and upgrade paths |
| Owners, login roles, memberships | Create an explicit application role model |
| Row-level security and SECURITY DEFINER | Accept ownership and permissions through application tests |
| Databases and connections | Record each database plus FDW and dblink destinations |
| Encoding and collation | Check target settings and possible sorting differences |
Cloud SQL supports a defined extension catalogue. Availability there does not establish that the same extension and version are installed on the Hetzner VM. We recommend preparing required packages and preload settings before restore and documenting the result in rehearsal.
The Cloud SQL administrator does not have unrestricted PostgreSQL superuser privileges; Cloud SQL roles and IAM logins are not recreated automatically on the target. An individual pg_dump does not include cluster-wide roles (SQL dump). Do not blindly import a global role export containing Google-internal accounts.
Prepare source access and target
The example uses Cloud SQL Auth Proxy v2 on the migration machine, bound to loopback. Prerequisites include an installed proxy, enabled Cloud SQL Admin API, suitable Google credentials and the IAM permission to connect. For a private-IP-only instance, the machine also needs a network path to the VPC; the proxy does not establish that path (proxy configuration).
cloud-sql-proxy --address 127.0.0.1 --port 55432 PROJECT:REGION:INSTANCE
For a connection through the private IP, add --private-ip to the proxy command; a network path to the VPC is still required (use private IP). Keep the proxy running in a separate terminal. This example uses a regular PostgreSQL password login. Store the password in .pgpass or an approved secret mechanism, not in the shell command. On Unix the password file must have restrictive permissions, such as chmod 600 ~/.pgpass (password file).
Define connections without passwords in the Bash session:
export SRC_DSN='host=127.0.0.1 port=55432 dbname=appdb user=export_user sslmode=disable'
export DST_ADMIN='host=db.internal.example dbname=postgres user=migration_admin sslmode=verify-full sslrootcert=/secure/target-ca.pem'
export DST_DSN='host=db.internal.example dbname=appdb_migration user=migration_admin sslmode=verify-full sslrootcert=/secure/target-ca.pem'
Here sslmode=disable applies only to the local hop to the proxy; its Cloud SQL connection is TLS-protected. The target example assumes a reachable private host with a valid certificate, matching DNS name and prepared administrative privileges. Do not expose PostgreSQL generally to the internet for this guide.
Create a dedicated owner role and a new, empty database on the target. app_owner deliberately has no login. migration_admin must be allowed to assume that role. Decide encoding and locale beforehand; template0 provides the basis for an empty database.
psql "$DST_ADMIN" -X -v ON_ERROR_STOP=1 -c 'CREATE ROLE app_owner NOLOGIN;'
createdb --maintenance-db="$DST_ADMIN" --template=template0 --owner=app_owner appdb_migration
Create the role only once; if it exists, inspect its attributes. Use another empty database for a repeated rehearsal. Consolidating ownership as shown is suitable only if multiple original owners can legitimately map to one role. Otherwise, prepare a detailed role mapping first.
Before restore, inventory all roles referenced in the dump definitions and create them on the target according to the role plan. The pg_policies query above identifies the target roles of RLS policies; also inspect the archive schema output using pg_restore --schema-only --no-owner --no-privileges --file=cloudsql-schema.sql cloudsql-final.dump once the dump is available. For example, CREATE POLICY ... TO app_reader requires an existing app_reader role during restore (CREATE POLICY). Keep logins that are not yet needed disabled with NOLOGIN. --no-privileges suppresses GRANT/REVOKE, not policy definitions (pg_restore). Configure credentials and additional permissions after restore.
Create and restore the final dump
Before the final dump, we recommend a documented gate: stop web applications, workers, cron jobs, imports and administrative access; inspect open write transactions; freeze schema changes. A read-only banner alone does not establish that every process has stopped writing.
pg_dump produces a consistent snapshot even during concurrent operation. That snapshot does not contain later changes (pg_dump). In this procedure the source therefore remains without writes from the final dump until target approval.
umask 077
pg_dump --dbname="$SRC_DSN" --format=custom --file=cloudsql-final.dump
pg_restore --list cloudsql-final.dump > cloudsql-final.toc
Check exit status and error messages after every command. Do not continue without a successful dump. The export user needs access to all objects being backed up. Review RLS in particular: a restricted export must not silently capture only a subset of business data (row security).
Read the custom archive with pg_restore. This example deliberately does not restore original ownership and ACLs, leaving them to the prepared role model. --exit-on-error stops at the first SQL error (pg_restore options):
pg_restore --dbname="$DST_DSN" --role=app_owner --no-owner --no-privileges --exit-on-error cloudsql-final.dump
There is no --clean against an existing production database here. A failed restore is not an approved partial database. Fix the error and restart on a new empty target. If an extension requires privileged installation, an administrator must prepare it appropriately; do not bypass missing objects by commenting them out indiscriminately.
The archive contains application data. For transfer and storage, we recommend encrypted connections, restricted access, an integrity check after transfer and a defined deletion deadline. The examples run dump and restore from the same migration machine, requiring no additional file transfer.
Check data and application permissions
After restore, add new credentials, required memberships, database and schema permissions, and default privileges to the application roles already created. Permissions omitted using --no-privileges must be replaced deliberately. In particular, the application should not permanently run as administrator merely for migration convenience.
For acceptance, we recommend a recorded checklist:
| Check | Acceptance criterion |
|---|---|
| Tables and data | Expected tables, exact row counts for selected critical tables and business totals agree while the source is frozen |
| Constraints, indexes and extensions | Expected objects present, no unresolved restore errors |
| Sequences and write tests | New records in isolated testing cause no key conflicts |
| Permissions and RLS | Application sees and changes only intended data |
| Application and integrations | Reads, writes, background jobs and external connections checked |
| Operations | Target backup, restore and alert paths checked |
Rebuild optimizer statistics after restoration (PostgreSQL SQL dump restoration). Run analysis explicitly as app_owner: permission to use SET ROLE does not automatically imply privilege inheritance (role membership). Example after restore:
psql "$DST_DSN" -X -v ON_ERROR_STOP=1 \
-c 'SET ROLE app_owner;' \
-c 'ANALYZE VERBOSE;'
SET ROLE and ANALYZE run in the same psql session (SET ROLE). ANALYZE VERBOSE builds statistics and reports the tables processed; inspect output and warnings for skipped application tables and resolve missing permissions before acceptance (ANALYZE). Database size alone does not prove data equivalence. Rehearsal write tests belong on an isolated test target. The final target must accept production changes only after approval.
Define cutover and rollback
We recommend deciding in advance who approves the cutover and which findings trigger an abort. For this dump procedure:
- Stop all writers and inspect the source.
- Create the final dump, restore into an empty target and accept the data.
- Switch application connection settings and secrets to the target.
- Run controlled checks first; explicitly approve writes.
- Activate workers and jobs in order; observe errors, connections and database load.
Rollback before write approval: If the source is unchanged and the target has accepted no new production data, applications can return to the source after checks.
Rollback after write approval: Changing the address alone would hide new target data. Stop writers again and decide how to transfer data back or reconcile it. Plan that return path before cutover. Do not promise an automatic lossless rollback.
We recommend retaining the old instance, protected against writes, until documented acceptance. Cancellation and deletion are a separate step after acceptance, with retention and backup requirements clarified.
Our approach at WZ-IT
Google Cloud migration to Hetzner assesses Cloud SQL together with the surrounding services. We clarify target architecture, data transfer, tests and cutover. The database focus is PostgreSQL; optional ongoing operations are scoped through Managed Operations. Scope and responsibilities are defined for the specific environment.
Further guides
- AWS RDS and Aurora PostgreSQL to Hetzner: a different source service, a similar toolchain.
- Azure PostgreSQL to Hetzner: an Azure-specific starting point.
- GCP egress and Hetzner traffic: assess data transfer in the cost picture.
- Migration overview: related offers and other source systems.
Plan Cloud SQL transfer and target operations. Discuss the database, connected applications and cutover conditions for your GCP migration. Book an initial consultation
Sources
- Google Cloud: Export with pg_dump
- Google Cloud: Cloud SQL Auth Proxy
- Google Cloud: PostgreSQL users
- Google Cloud: Extensions
- Google Cloud: Logical replication
- PostgreSQL: pg_dump
- PostgreSQL: pg_restore
- PostgreSQL: createdb
- PostgreSQL: Password file
- PostgreSQL: SQL dump and restore
- PostgreSQL: pg_policies
- PostgreSQL: CREATE POLICY
- PostgreSQL: SET ROLE
- PostgreSQL: ANALYZE
- PostgreSQL: Role membership
- PostgreSQL: Row security
- PostgreSQL: Logical replication restrictions













