Move a PostgreSQL cluster only while it is stopped, copy the complete directory with its ownership and metadata intact, point the service that actually starts the cluster at the new path, then verify SQL access, tablespaces, security policy, and application behavior before deleting the original. A backup or storage snapshot is your recovery point; a live file copy is not a consistent migration.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
This guide covers package-managed Debian and Ubuntu clusters as well as manually managed installations. It assumes you are relocating an existing cluster to another mounted filesystem, not creating an empty cluster.
As an Amazon Associate I earn from qualifying purchases.
What PostgreSQL’s data directory contains
PGDATA is the cluster directory initialized by initdb. It contains the databases and the state required to start the server, including PG_VERSION, base, global, pg_wal, pg_tblspc, configuration files (unless your packaging keeps them elsewhere), and other control, statistics, and temporary-state directories. PostgreSQL documentation describes this as the traditional location for a cluster’s data and configuration files.
Moving PGDATA is therefore different from moving one database or changing a client connection string. The server must be stopped, every file must be copied consistently, and the service supervisor must be changed to use the new directory. If the cluster uses tablespaces, some data is outside PGDATA and needs a separate plan.
#1 Best Overall
Choose a relocation method before touching the old directory
| Method | Downtime | Rollback | When it fits |
|---|---|---|---|
| Direct directory relocation | Time to stop, copy, configure, and validate | Simple while the untouched old copy remains available | Same PostgreSQL major version and a destination filesystem that can preserve the cluster’s files and metadata |
| New cluster plus backup/restore | Restore duration, with a possible logical cutover plan | Old cluster remains an independent source | Changing major versions, redesigning tablespaces, changing ownership or authentication deliberately, or moving between incompatible filesystems |
| Storage-level volume move or snapshot | Usually the stop and attach time, subject to the storage platform | Snapshot or detached original volume | A dedicated local, virtual-machine, or managed block-storage volume is being replaced or reattached |
A direct move preserves the cluster exactly, but it also preserves problems: an incorrect owner, an overlooked tablespace link, or an unsupported mount option comes across unchanged. A backup/restore takes longer and requires more planning, yet it is often safer when the destination changes the storage design or the PostgreSQL major version.
Pre-move inventory and safety checks
Do this inventory while the old cluster is healthy. Record the PostgreSQL major version, cluster name, service unit, current data directory, operating-system owner, mount point, free space, replication role, and backup status. Confirm that the destination volume will be mounted automatically before PostgreSQL starts after a reboot.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Find the active cluster and data path
Connect as a role with permission to inspect the server and save the results:
psql -d postgres -c 'SELECT version();'
psql -d postgres -c 'SHOW data_directory;'
psql -d postgres -c 'SHOW config_file;'
psql -d postgres -c 'SELECT pg_is_in_recovery();'
psql -d postgres -c 'l'
psql -d postgres -c 'du'
psql -d postgres -c 'db+'
SHOW data_directory reports the directory used by the running server. The database and role listings provide a baseline for post-move checks. The tablespace listing is essential because user-defined tablespaces are represented by symbolic links under PGDATA/pg_tblspc but store relation files elsewhere.
Inspect the operating-system and filesystem state
stat -c '%U %G %a %n' /path/to/current/PGDATA
findmnt -T /path/to/current/PGDATA
findmnt -T /path/to/new/storage
df -h /path/to/current/PGDATA /path/to/new/storage
ps -ef | grep '[p]ostgres'
readlink -f /path/to/current/PGDATA/pg_tblspc/*
Use the real paths returned by your server; the paths above are examples, not directories to copy literally. Check that the destination has enough capacity for the entire cluster plus expected growth, supports the required file ownership and permissions, and allows symbolic links. If your policy relies on ACLs or extended attributes, confirm that the filesystem and mount options support them.
Check replication and backup implications
For a primary with replicas, document how the replica will learn the new storage location and whether replication monitoring assumes a particular path. For a standby, use the same controlled stop and copy procedure and verify its recovery configuration. Do not treat a directory copy as a backup: retain a tested backup or snapshot that predates the move.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Stop PostgreSQL cleanly
Stop only the intended cluster. On Debian and Ubuntu, multiple major versions or cluster names can coexist, so stopping a generic service may affect more than the instance you mean to move.
Debian or Ubuntu with a registered cluster
sudo pg_ctlcluster <version> <cluster> stop
sudo systemctl status postgresql@<version>-<cluster>
The pg_ctlcluster wrapper selects the matching cluster and its data path. When run as root, it can redirect control to systemd supervision. Substitute the actual values shown by your host’s cluster listing; do not stop another production cluster by guessing its name.
Manual installation or explicit service unit
sudo -u postgres pg_ctl -D /path/to/current/PGDATA stop -m fast
ps -ef | grep '[p]ostgres'
Use the installation’s supported control path. A fast shutdown lets active sessions finish and then stops the server; an immediate shutdown should be reserved for an emergency because it causes crash recovery at the next start. Continue only when no PostgreSQL server processes still have files open in the old directory. If your host uses a custom systemd unit, stop that unit rather than a similarly named package service.
Prepare and mount the destination
Mount the new PostgreSQL storage volume at a stable path before copying. Configure the mount in the host’s normal boot mechanism and test it after a controlled remount or reboot window. PostgreSQL should not start against an empty mount-point directory if the volume fails to mount: that can create a confusing second cluster or produce a startup failure.
Recommended Free Tools
Create the destination with the cluster owner, normally the operating-system account postgres, and restrictive permissions. The exact mode can vary with packaging, but the data directory must not be writable by untrusted users.
sudo mkdir -p /path/to/new/PGDATA
sudo chown postgres:postgres /path/to/new/PGDATA
sudo chmod 700 /path/to/new/PGDATA
sudo findmnt -T /path/to/new/PGDATA
If the destination directory is the root of a mounted filesystem, create it only after the mount is active. Verify available space with df -h and inode capacity with df -i. Also check whether local security policy requires a particular mount label or whether the path is beneath a directory that denies traversal to the postgres user.
Copy the stopped cluster with metadata intact
Copy the complete old data directory, including hidden files, symbolic links, hard links where present, ACLs, and extended attributes required by your environment. A commonly suitable tool on Linux is:
sudo rsync -aHAX --info=progress2 /path/to/current/PGDATA/ /path/to/new/PGDATA/
The trailing slash on the source means “copy the contents into the destination.” The -a option preserves ordinary ownership, permissions, timestamps, and links; -H preserves hard links; -A preserves POSIX ACLs; and -X preserves extended attributes. Use those options only when supported by both filesystems and allowed by local policy. If your platform uses a different metadata-preserving copy tool, configure its equivalent behavior.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsVerify the copy before changing configuration
sudo rsync -aHAXn --delete /path/to/current/PGDATA/ /path/to/new/PGDATA/
sudo stat -c '%U %G %a %n' /path/to/new/PGDATA
sudo ls -la /path/to/new/PGDATA/pg_tblspc
sudo readlink -f /path/to/new/PGDATA/pg_tblspc/*
A dry-run with --delete reports differences without modifying either tree; use it only after checking both source and destination paths carefully. Compare file counts and total sizes as additional sanity checks. A non-empty difference report means the copy needs investigation before cutover.
Do not flatten symbolic links with an archive extraction or a copy option that follows links. A link in pg_tblspc must remain a link, and its target directory must still exist at the path PostgreSQL recorded. Moving a tablespace target is a separate operation.
Point the real startup mechanism at the new path
Changing an environment variable in your shell is not enough if systemd or a distribution wrapper starts PostgreSQL. Change the configuration that owns this instance’s startup path, then inspect the effective unit before starting.
Debian and Ubuntu cluster configuration
Package-managed clusters commonly keep per-cluster configuration under:
/etc/postgresql/<version>/<cluster>/
Inspect postgresql.conf and its data_directory setting. If the setting is absent, the wrapper may be using the package’s default data path. You can register an already initialized directory with pg_createcluster and set its data_directory, but do not create a new empty cluster by accident. The documented default layout is under /var/lib/postgresql/%v/%c; an existing cluster moved elsewhere must be configured explicitly.
sudo grep -n '^[[:space:]]*data_directory' /etc/postgresql/<version>/<cluster>/postgresql.conf
sudo systemctl cat postgresql@<version>-<cluster>
sudo systemctl daemon-reload
Do not edit a generated unit blindly. Package upgrades can regenerate it, and the durable setting may belong in the cluster configuration or a package-supported override. Confirm with systemctl cat which command and environment the unit actually uses.
Manual PostgreSQL service
A manually managed instance may obtain its path from the PGDATA environment variable, a pg_ctl -D argument, an initdb -D path used when the cluster was created, or an explicit systemd ExecStart. Update the one that starts this server:
sudo systemctl cat postgresql-custom.service
sudo systemctl edit postgresql-custom.service
If you use a systemd drop-in, set the new path in the appropriate Environment=PGDATA=... or ExecStart=... directive, following systemd’s requirement to clear an existing ExecStart before replacing it. Keep a record of the old value for rollback.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteHandle tablespaces, WAL, and external paths
Tablespaces
PostgreSQL stores user-defined tablespace locations as symbolic links below PGDATA/pg_tblspc. Copying PGDATA does not copy the directories those links target. Before cutover, list every tablespace and verify that each target path is mounted and readable by postgres. If a tablespace is also being moved, move its directory while the cluster is stopped, preserve its ownership and metadata, and recreate the link or update the tablespace location using a PostgreSQL-supported procedure rather than leaving a dangling link.
After startup, compare db+ output with the pre-move inventory and query representative tables stored in each non-default tablespace. A cluster can start successfully while a missing tablespace remains unnoticed until an application touches a relation there.
WAL and other separately mounted directories
The pg_wal directory is normally inside PGDATA, but some installations place it on another filesystem through a symbolic link or mount arrangement. Inventory it before copying and verify the destination retains the same layout. Do not delete an old WAL location until the new instance has started and completed its normal recovery or replication checks.
Apply AppArmor or SELinux policy
Ubuntu AppArmor
Ubuntu’s PostgreSQL AppArmor profile can deny access even when Unix ownership and modes are correct. Add a local profile rule covering the new directory tree with the read, write, and lock access required by PostgreSQL, then reload AppArmor. Keep the rule narrow enough to cover only the intended data path.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →sudo aa-status
sudo journalctl -k | grep -i apparmor
sudo systemctl reload apparmor
Use the kernel or AppArmor logs to confirm whether a denial names the new path. Reloading the profile is required after editing a local rule; restarting PostgreSQL alone does not load the change.
SELinux
SELinux requires the PostgreSQL database file context on the new tree. This is especially important when the destination is beneath /home, where parent-directory search permissions can block access even after the files are labeled.
Best Value
ls -Zd /path/to/new/PGDATA
sudo semanage fcontext -a -t postgresql_db_t '/path/to/new/PGDATA(/.*)?'
sudo restorecon -Rv /path/to/new/PGDATA
ls -Z /path/to/new/PGDATA
If semanage is unavailable, install the host’s policy-management package according to its normal administration procedure. Check audit logs for denials and verify every parent directory allows the postgres process to search it.
Start the cluster and validate it at three levels
Start through the same supervisor you will use in production. Do not start the copied directory manually if systemd or pg_ctlcluster will later start a different path.
sudo systemctl start postgresql@<version>-<cluster>
sudo systemctl status postgresql@<version>-<cluster> --no-pager
sudo journalctl -u postgresql@<version>-<cluster> -b --no-pager
For a manual instance, use its configured service or the matching pg_ctl -D /path/to/new/PGDATA start command. A clean service status is only the first check.
Server and filesystem checks
psql -d postgres -c 'SHOW data_directory;'
psql -d postgres -c 'SELECT current_database(), current_user;'
psql -d postgres -c 'l'
psql -d postgres -c 'du'
psql -d postgres -c 'db+'
stat -c '%U %G %a %n' /path/to/new/PGDATA
SHOW data_directory must report the new path. Confirm expected databases, roles, extensions, and tablespaces. Check the server log for permission, missing-file, recovery, or tablespace errors rather than assuming a successful process start proves the move worked.
Read/write and application checks
Run a representative transaction in a safe test table or an application-approved health-check path. Verify reads from important databases, a controlled write and rollback, extension availability, scheduled jobs, connection pooling, replication status, and monitoring. If the application uses a Unix socket path or a service discovery setting tied to the old host configuration, validate that separately; moving PGDATA does not automatically change client settings.
Keep a rollback path
Do not remove the old directory immediately. Protect it from accidental writes, but retain it intact until SQL checks, logs, application checks, replication checks, and a fresh backup or snapshot have passed.
Free tools Windows power users keep installed
One-click scans. No signup required.
- Stop the new cluster using its normal supervisor.
- Restore the original
data_directory,PGDATA,-D, orExecStartsetting. - Undo or preserve the security-policy rules needed for the old path.
- Start the original cluster and verify its reported data directory and database state.
- Document any writes accepted after the attempted cutover. Reconcile them before treating the rollback as a complete recovery.
Once the new location has remained healthy for the retention period required by your operations policy, take a fresh backup or snapshot and then remove the old copy according to your data-disposal rules. Never delete it merely because the service started once.
Troubleshooting by symptom
The service starts the old location
- Run
SHOW data_directory;and inspect the active unit withsystemctl cat. - Check the exact cluster name and major version; package-managed hosts can have several clusters.
- Inspect
postgresql.conffor an overridingdata_directoryvalue. - Confirm that the mount containing the new path was present before the service started.
Permission denied on Ubuntu
- Verify Unix owner and mode on every parent directory and on the copied tree.
- Check mount options and ACLs.
- Review AppArmor denial messages, add a local rule for the new subtree, and reload AppArmor.
- Retry only after confirming the process is using the intended path.
Permission denied with SELinux enabled
- Inspect labels with
ls -Z. - Apply the PostgreSQL database context using
semanage fcontextandrestorecon. - Check audit logs and parent-directory search permission, particularly for paths below
/home.
Databases or tablespaces appear to be missing
- Compare
l,du, anddb+with the pre-move inventory. - Inspect
PGDATA/pg_tblspcand resolve each symbolic link. - Verify that external tablespace and WAL mounts are present at boot and readable by
postgres. - Check logs for a relation or directory error that may not appear until a particular database is accessed.
The new disk was not mounted
Stop PostgreSQL immediately if it started against an unintended empty directory. Do not initialize that path. Mount the correct volume, restore the intended configuration, and compare the directory contents with your backup or preserved copy before restarting.
Quick Recap
Operational checklist
- Record version, cluster name, service unit, current path, owner, mounts, tablespaces, replication, and backup status.
- Confirm the destination volume is mounted at boot and has adequate space, inodes, and metadata support.
- Stop the intended cluster cleanly and confirm no PostgreSQL processes use the old path.
- Copy with metadata-preserving options and verify symbolic links, ACLs, and extended attributes as required.
- Change the path in the actual startup mechanism, not only in an interactive shell.
- Apply AppArmor or SELinux policy before starting.
- Validate service logs,
SHOW data_directory, databases, roles, extensions, tablespaces, read/write behavior, and application health. - Keep the old copy and a recovery point until the rollback window and retention requirements are satisfied.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




