Encrypting the PostgreSQL data directory at rest reduces exposure from stolen disks, leaked block snapshots, or retired storage, because the database files remain unreadable without the encryption key.
PostgreSQL stores tables, indexes, and write-ahead log (WAL) as regular files under its data_directory, so at-rest encryption is commonly applied below the database by encrypting the underlying block device with dm-crypt and LUKS.
At-rest encryption only protects powered-off storage, not a running server or accounts with filesystem access, so key management, boot-time unlocking, planned downtime, and backup encryption need deliberate planning before migrating a production cluster.
Related: How to secure a PostgreSQL server \\
Related: How to create a PostgreSQL database backup
$ sudo -u postgres psql -tAc "SHOW data_directory;" /var/lib/postgresql/16/main
$ lsblk -o NAME,SIZE,TYPE,MOUNTPOINTS NAME SIZE TYPE MOUNTPOINTS sda 100G disk ├─sda1 1G part /boot └─sda2 99G part / sdb 200G disk └─sdb1 200G part
Use a dedicated partition or logical volume for the database volume.
$ sudo cryptsetup luksFormat --type luks2 /dev/sdb1 WARNING! ======== This will overwrite data on /dev/sdb1 irrevocably. Are you sure? (Type 'YES' in capital letters): YES Enter passphrase for /dev/sdb1: Verify passphrase:
Selecting the wrong device permanently erases data.
$ sudo cryptsetup open /dev/sdb1 pgdata_crypt Enter passphrase for /dev/sdb1:
The mapper name pgdata_crypt is referenced by /etc/crypttab and /etc/fstab.
$ sudo cryptsetup status pgdata_crypt /dev/mapper/pgdata_crypt is active. type: LUKS2 cipher: aes-xts-plain64 keysize: 512 bits device: /dev/sdb1 mode: read/write
$ sudo mkfs.ext4 -L pgdata /dev/mapper/pgdata_crypt mke2fs 1.46.5 (30-Dec-2021) Creating filesystem with 52424832 4k blocks and 13107200 inodes Filesystem UUID: 2a3b4c5d-6e7f-8901-2345-6789abcdef01 ##### snipped #####
$ sudo systemctl stop postgresql
$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
Active: inactive (dead)
##### snipped #####
$ sudo mv /var/lib/postgresql/16/main /var/lib/postgresql/16/main.unencrypted
Replace /var/lib/postgresql/16/main with the data_directory value from the first step.
$ sudo mkdir -p /var/lib/postgresql/16/main
$ sudo mount /dev/mapper/pgdata_crypt /var/lib/postgresql/16/main
$ sudo chown postgres:postgres /var/lib/postgresql/16/main
$ sudo chmod 0700 /var/lib/postgresql/16/main
$ sudo rsync -aHAX --numeric-ids --info=progress2 /var/lib/postgresql/16/main.unencrypted/ /var/lib/postgresql/16/main/
2,188,767,232 35% 92.41MB/s 0:00:15 (xfr#124, to-chk=0/980)
##### snipped #####
SELinux systems may require restorecon -RF on the data directory after copying.
$ sudo blkid /dev/sdb1 /dev/sdb1: UUID="11111111-2222-3333-4444-555555555555" TYPE="crypto_LUKS" PARTUUID="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
/etc/crypttab pgdata_crypt UUID=11111111-2222-3333-4444-555555555555 none luks
A passphrase prompt at boot uses none, while unattended boot typically uses a protected key file or hardware-backed unlock.
$ sudo blkid /dev/mapper/pgdata_crypt /dev/mapper/pgdata_crypt: LABEL="pgdata" UUID="2a3b4c5d-6e7f-8901-2345-6789abcdef01" TYPE="ext4"
/etc/fstab UUID=2a3b4c5d-6e7f-8901-2345-6789abcdef01 /var/lib/postgresql/16/main ext4 defaults 0 2
An invalid /etc/fstab entry can drop the system into emergency mode during boot.
$ sudo systemctl daemon-reload
$ sudo systemctl start postgresql
$ findmnt -no SOURCE,TARGET /var/lib/postgresql/16/main /dev/mapper/pgdata_crypt /var/lib/postgresql/16/main
$ sudo -u postgres psql -c "SELECT now();"
now
-------------------------------
2025-12-15 11:12:34.12345+00
(1 row)
$ sudo rm -rf /var/lib/postgresql/16/main.unencrypted
Removing the backup is irreversible, but keeping it leaves an unencrypted copy of the database on disk.
$ sudo reboot
A passphrase-based crypttab entry prompts on the system console during startup.