Move PostgreSQL Off the OS Disk onto Its Volume
Stop the cluster, rsync the data directory onto a dedicated High-Speed SSD volume, and point PostgreSQL 16 at that new data location safely.
Netbay Developer Relations
Netbay Engineering
On this page
The OS disk is for the kernel, packages, and logs. The PostgreSQL data directory is a growing heap plus WAL plus indexes. Leaving both on one volume means a verbose log, a dump, or a REINDEX can fill the same filesystem the postmaster needs for WAL, and the cluster pauses. Move PGDATA onto its own High-Speed SSD volume. The procedure is stop, rsync, repoint, start, verify. It is a maintenance window, not a live migration.
This walkthrough uses Ubuntu 24.04, PostgreSQL 16, and a second disk already attached to the VPS in Lucknow DC01. Intel Xeon Platinum does not care which volume holds the heap. The filesystem does.
Prepare the volume and a filesystem
Identify the new disk, partition it if needed, create ext4, and mount it on /srv/pgdata. Use a UUID in fstab so a reboot does not race the mount.
lsblk
sudo mkfs.ext4 -L pgdata /dev/vdb
sudo mkdir -p /srv/pgdata
echo 'LABEL=pgdata /srv/pgdata ext4 noatime,errors=remount-ro 0 2' | sudo tee -a /etc/fstab
sudo mount /srv/pgdata
df -h /srv/pgdata /var/lib/postgresql
sudo -u postgres psql -c "SHOW data_directory;"noatime avoids an atime write on every read. errors=remount-ro is safer than continuing to write on a bad volume. Size the volume for the heap, indexes, WAL, plus a REINDEX CONCURRENTLY copy, plus room for growth. A 20 GB database that barely fits a 25 GB volume will fill during the first concurrent reindex.
If the platform presents the disk as /dev/sda or /dev/vdc, use that name. Do not copy device names from this post without looking at lsblk.
Stop, rsync, then repoint
PostgreSQL must be down. A rsync of a live data directory is not a backup and is not a move. Stop the unit, rsync with permissions, confirm the copy, then tell the cluster where home is.
Ubuntu's PGDG layout stores the data directory in /var/lib/postgresql/16/main and the config in /etc/postgresql/16/main. You can move only the data directory. Keep the config on the OS disk; it is small and you want it in the same backup as /etc.
sudo systemctl stop postgresql@16-main
sudo rsync -aHAX --info=progress2 /var/lib/postgresql/16/main/ /srv/pgdata/16/main/
sudo du -sh /var/lib/postgresql/16/main /srv/pgdata/16/main
sudo chown -R postgres:postgres /srv/pgdata
sudo chmod 700 /srv/pgdata/16/mainThe trailing slashes on rsync matter. You want the contents of main, not a nested main/main. data_directory in postgresql.conf should become /srv/pgdata/16/main. Alternatively, replace /var/lib/postgresql/16/main with a symlink to the new path. The config edit is clearer for the next person.
sudo python3 - <<'PY'
from pathlib import Path
p = Path("/etc/postgresql/16/main/postgresql.conf")
lines = []
for line in p.read_text().splitlines():
if line.startswith("data_directory") or line.startswith("#data_directory"):
lines.append("data_directory = '/srv/pgdata/16/main'")
else:
lines.append(line)
p.write_text("\n".join(lines) + "\n")
PY
sudo grep -n "^data_directory" /etc/postgresql/16/main/postgresql.conf
sudo systemctl start postgresql@16-main
sudo -u postgres psql -c "SHOW data_directory;"
sudo ss -lntp | grep 5432SHOW data_directory must print /srv/pgdata/16/main. If it still prints the old path, you started a different cluster or the conf.d file overrode you. Fix that before you delete the old copy.
Only then remove the old directory
Keep the old data directory until you have: cluster running, SHOW data_directory correct, a query against appdb, and a reboot test. After reboot, systemd must mount /srv/pgdata from fstab before postgresql@16-main starts. If Postgres starts first, it will fail to find PGDATA. Order that with a systemd drop-in RequiresMountsFor.
sudo mkdir -p /etc/systemd/system/postgresql@16-main.service.d
printf '[Unit]\nRequiresMountsFor=/srv/pgdata\n' | sudo tee /etc/systemd/system/postgresql@16-main.service.d/mount.conf
sudo systemctl daemon-reload
sudo rebootAfter reboot: mount | grep pgdata, systemctl is-active postgresql@16-main, SHOW data_directory. Then, and only then, sudo rm -rf /var/lib/postgresql/16/main. Until that reboot works, the old directory is your undo.
WAL, dumps, and the archive directory should not all return to the OS disk. Put pg_wal on the data volume (the default once PGDATA moves). Put dumps and WAL archives on a third path if you have it, or at least not in /var/lib/postgresql on the OS disk. The whole point of the move is isolation: OS fills must not freeze WAL.
A dedicated volume also makes snapshots and restores cleaner. Snapshot the data volume, not the OS image, when you want a crash-consistent disk copy. That is still not a substitute for pg_dump and WAL archive, but it is a faster rollback of the heap when the volume is separate.
Plan capacity with the extra files PostgreSQL will write. WAL during a bulk load, a base backup, a concurrent reindex, and a dump can all land on the data volume if you are not careful. Keep dumps and WAL archives on a different mount. Leave at least 40 percent free on PGDATA so a maintenance job cannot freeze the cluster by filling the only disk the postmaster can write.
Takeaway
Stop the cluster, rsync PGDATA to a dedicated High-Speed SSD volume, set data_directory, require the mount in systemd, reboot once, then remove the old copy. Attach a second volume on a Netbay Ubuntu VPS in Lucknow DC01 and finish this move before the OS disk is the thing that fills — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance