11.11. Backup and Restore
Table of Contents
Why Backup and Restore Matter
Your database contains your users, orders, payments, logs, and more. If you lose it, your application is often useless.
Typical things that can go wrong:
- A developer runs
DROP TABLE users;on production. - A disk fails on your server.
- A bug deletes or corrupts data.
- You need to move to a new server or cloud provider.
Backups and restores let you:
- Recover from disasters.
- Roll back to a known good point in time.
- Clone data to staging / test environments.
- Migrate between servers or versions of PostgreSQL.
**Always treat backups as critical infrastructure.
A backup that cannot be restored is the same as no backup.
You must regularly test restore procedures.**
In this chapter we focus on PostgreSQL specific tools and workflows, not generic backup theory.
Types of PostgreSQL Backups
There are two main categories of backups in PostgreSQL:
| Type | Tool examples | Pros | Cons |
|---|---|---|---|
| Logical backup | pg_dump, pg_dumpall | Portable, flexible, selective, easy to inspect | Slower on huge DBs, larger size, slower restore |
| Physical backup | pg_basebackup, file system snapshots | Fast for large DBs, exact copy, good for replication | Tied to version and OS, less flexible, more complex |
You will use logical backups most often as a backend developer, especially for:
- Project development.
- Regular small to medium database backups.
- Migrations between environments.
Physical backups are more common in production setups handled by DevOps / DBAs, but you should know the basics.
Logical Backups with pg_dump
pg_dump creates a logical backup of a single PostgreSQL database. It does not back up the whole cluster, only one database at a time.
Basic pg_dump usage
Common options:
-hhost-pport-Uuser-Fformat (plain, custom, directory, tar)-foutput file-ddatabase name (or given as last argument)
Example: simple SQL backup
This creates a text .sql file that can be opened in a text editor and restored with psql:
pg_dump -h localhost -p 5432 -U myuser -d mydb -F p -f mydb.sql
-F p means "plain" SQL.
Example contents of mydb.sql (simplified):
--
-- PostgreSQL database dump
--
CREATE TABLE public.users (
id integer NOT NULL,
email text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT now()
);
INSERT INTO public.users (id, email, created_at) VALUES
(1, 'alice@example.com', '2024-01-01 10:00:00'),
(2, 'bob@example.com', '2024-01-02 12:00:00');This is human readable and easy to move between PostgreSQL versions.
Password handling
pg_dump uses the same authentication as psql. Typical patterns:
- Use
.pgpassfile for passwords. - Or set
PGPASSWORDenvironment variable:
PGPASSWORD='secret' pg_dump -h localhost -U myuser -d mydb -f mydb.sqlDo not hardcode passwords in scripts or share them.
Dumping only the schema or only the data
Often you want only the structure (tables, indexes) or only the contents.
- Schema only:
pg_dump -h localhost -U myuser -d mydb --schema-only -f mydb_schema.sql- Data only:
pg_dump -h localhost -U myuser -d mydb --data-only -f mydb_data.sqlUse cases:
- Copy schema to a new environment but load limited data.
- Compare schemas between versions.
- Refresh test data without dropping and recreating everything.
Dumping specific tables
You can back up just a subset of tables:
pg_dump -h localhost -U myuser -d mydb \
-t public.users \
-t public.orders \
-f users_orders.sql
The -t option can be repeated for multiple tables.
Excluding tables
Sometimes you want to dump everything except large log tables:
pg_dump -h localhost -U myuser -d mydb \
-T public.audit_logs \
-f mydb_without_logs.sql
Use -T to exclude tables by name pattern.
Dump formats: plain vs custom vs directory
pg_dump supports several formats:
| Format | Option | Description | How to restore |
|---|---|---|---|
| plain | -F p | Text SQL file | psql |
| custom | -F c | Compressed binary, flexible | pg_restore |
| directory | -F d | Directory with one file per table, flexible | pg_restore |
| tar | -F t | Tar archive, less used | pg_restore |
For regular developer usage, two formats are most important:
Plain SQL example
pg_dump -h localhost -U myuser -d mydb -F p -f mydb.sqlRestore:
psql -h localhost -U myuser -d targetdb -f mydb.sqlCustom format example (recommended for larger DBs)
pg_dump -h localhost -U myuser -d mydb -F c -f mydb.dumpAdvantages of custom format:
- Compressed.
- Can restore only selected objects.
- Can run restores in parallel.
Restore with pg_restore (shown later).
Directory format example
pg_dump -h localhost -U myuser -d mydb -F d -f mydb_backup_dir
This creates a directory mydb_backup_dir with many files. Useful for very large databases and parallel restore.
Including roles and privileges
By default, pg_dump includes object ownership and privileges for the database objects, but not the roles themselves. To back up all roles, you use pg_dumpall (covered later).
For a single database, you can:
- Preserve ownership and grants with
--no-ownerturned off (default). - Or ignore them if you restore to a different username:
pg_dump -h localhost -U myuser -d mydb \
--no-owner --no-privileges \
-f mydb.sqlThis makes it easier to restore into another environment with different users.
Restoring Logical Backups
How you restore depends on the backup format.
Restoring plain SQL with psql
If you used -F p (or default) with pg_dump, you restore with psql.
Example: restore into an existing empty database
First create the target database:
createdb -h localhost -U myuser newdbThen restore:
psql -h localhost -U myuser -d newdb -f mydb.sqlOr using shell redirection:
psql -h localhost -U myuser -d newdb < mydb.sql
If the backup file contains CREATE DATABASE ..., it may try to create the database itself. In that case, run:
psql -h localhost -U myuser -f mydb.sql
without specifying -d, so it connects to the default database (often postgres) and executes the CREATE DATABASE.
Restoring custom or directory backups with pg_restore
If you used -F c, -F d, or -F t with pg_dump, you restore with pg_restore.
Example: restore into a fresh database
- Create the database:
createdb -h localhost -U myuser newdb- Run
pg_restore:
pg_restore -h localhost -U myuser -d newdb mydb.dumpThis recreates schemas, tables, data, and indexes.
Dropping existing objects before restore
If the target database already has tables, you may want to drop them first:
pg_restore -h localhost -U myuser -d newdb --clean mydb.dump
--clean adds DROP statements for objects before recreating them.
You can also use --if-exists to avoid errors if objects are missing:
pg_restore -h localhost -U myuser -d newdb --clean --if-exists mydb.dumpSelective restore with pg_restore
One nice feature of custom and directory formats is selective restore.
Restore only one table:
pg_restore -h localhost -U myuser -d newdb \
-t public.users \
mydb.dumpExclude a table:
pg_restore -h localhost -U myuser -d newdb \
-T public.audit_logs \
mydb.dumpOr list the contents of a backup:
pg_restore -l mydb.dumpThis prints an index of all objects in the dump file.
Parallel restore
You can speed up large restores with multiple jobs:
pg_restore -h localhost -U myuser -d newdb -j 4 mydb.dump
-j 4 means use 4 parallel jobs. This works with custom or directory formats, not with plain SQL.
Common restore problems and fixes
Problem: "role 'myuser' does not exist"
Your dump contains objects owned by myuser, but that role does not exist in the target cluster.
Quick fix: create the role before restore:
createuser -h localhost -U postgres myuser
More robust approach: on production, dump roles with pg_dumpall --globals-only (see later) and restore them first.
Problem: "permission denied for database"
User does not have privileges to create tables or other objects.
Fix: connect as a superuser (often postgres) or grant necessary privileges to the user.
Problem: "database 'mydb' already exists"
The dump script contains CREATE DATABASE mydb, but the DB already exists.
Options:
- Drop and recreate:
dropdb -h localhost -U postgres mydb
psql -h localhost -U postgres -f mydb.sql- Edit the dump file and remove
CREATE DATABASEand\connectlines. - Or create a new database with a different name and restore there.
Backing Up Entire Clusters with pg_dumpall
pg_dump handles a single database. pg_dumpall creates a logical backup of:
- All databases in the cluster.
- Global objects like roles and tablespaces.
Dumping the whole cluster
pg_dumpall -h localhost -U postgres -f cluster.sqlThis file contains:
CREATE ROLEstatements for all roles.CREATE DATABASEfor each database.\connectcommands, thenCREATE TABLE,INSERT, etc.
Example snippet:
--
-- Roles
--
CREATE ROLE myuser LOGIN PASSWORD '********';
--
-- Databases
--
CREATE DATABASE mydb WITH TEMPLATE = template0 ENCODING = 'UTF8';
\connect mydb
CREATE TABLE public.users (...);
INSERT INTO public.users ...;Dumping only global objects
To back up only roles and tablespaces, not the database contents:
pg_dumpall -h localhost -U postgres --globals-only -f globals.sqlYou often use this when setting up a new cluster:
- Create roles with
globals.sql. - Restore databases with individual
pg_dumpdumps.
Restoring pg_dumpall dumps
You restore pg_dumpall outputs with psql:
psql -h localhost -U postgres -f cluster.sqlThis will re-create roles, databases, and then the data.
To restore globals only:
psql -h localhost -U postgres -f globals.sqlPhysical Backups with pg_basebackup
Physical backups are exact copies of the data directory at the file system level. They are used for:
- Setting up streaming replication.
- Full cluster backup for large databases.
- Point in time recovery.
The main tool is pg_basebackup.
Warning about physical backups
Physical backups are more sensitive to PostgreSQL version, OS, and configuration.
Physical backups must be restored on the same major PostgreSQL version and compatible OS and architecture. They are not portable between different versions like logical backups.
Basic pg_basebackup usage
pg_basebackup connects to a running cluster and outputs a physical copy of the data directory.
Example:
pg_basebackup -h localhost -p 5432 -U replicator \
-D /backups/base_2024_01_01 \
-Ft -z -PExplanation:
-U replicatoruses a user that hasREPLICATIONprivilege.-D /backups/base_2024_01_01target directory.-F toutput as tar files (otherwise default is a directory).-zcompress.-Pshow progress.
You must configure pg_hba.conf and server settings to allow replication connections to use pg_basebackup. This is usually done by DevOps or DBAs.
File system level backups
Another way to do physical backups is to stop PostgreSQL, copy the data directory, and start it again. This is not usually used in production because it requires downtime, but you may see it in simple setups.
Example rough steps:
- Stop PostgreSQL:
sudo systemctl stop postgresql- Copy the data directory:
sudo cp -a /var/lib/postgresql/16/main /backups/main_backup- Start PostgreSQL:
sudo systemctl start postgresqlRestoring such a backup means replacing the data directory with this copy and ensuring permissions are correct.
Restoring from Physical Backups
Restoring a physical backup basically means:
- Stop PostgreSQL.
- Replace the data directory with the backup.
- Ensure permissions and ownership are correct.
- Start PostgreSQL again.
A very simplified example:
sudo systemctl stop postgresql
sudo mv /var/lib/postgresql/16/main /var/lib/postgresql/16/main_old
sudo cp -a /backups/main_backup /var/lib/postgresql/16/main
sudo chown -R postgres:postgres /var/lib/postgresql/16/main
sudo systemctl start postgresqlIn serious production setups, this is usually combined with WAL archiving and point in time recovery, not just a single base backup copy.
Point in Time Recovery (Overview)
Point in time recovery (PITR) lets you restore the database to an exact time, for example "just before a bug deleted rows from the orders table."
PITR uses:
- A base physical backup.
- Write Ahead Log (WAL) archive files.
At a high level:
- You configure PostgreSQL to archive WAL files.
- You take a base backup with
pg_basebackupor filesystem copy. - If disaster happens, you restore the base backup.
- You replay WAL files up to a specific time.
Example recovery configuration in postgresql.conf:
restore_command = 'cp /wal_archive/%f "%p"'
recovery_target_time = '2024-01-10 15:23:00'Then when PostgreSQL starts, it reads WAL files and stops at the target time.
As a backend developer, you usually do not implement PITR infrastructure yourself, but you should understand:
- That PITR exists.
- It requires continuous WAL archiving.
- It provides finer control than just nightly backups.
Do not rely on PITR without someone responsible for properly configuring and monitoring it.
Backup Strategies and Schedules
Having tools like pg_dump is not enough. You need a plan that fits your application.
Common backup strategy patterns
For small to medium applications, a common pattern is:
- Nightly full logical backup using
pg_dumporpg_dumpall. - More frequent incremental or differential backup at infrastructure level (for example file system snapshots).
- Store backups in remote storage like S3 or another server.
- Keep multiple days or weeks of history.
A simple daily logical backup script:
#!/bin/bash
set -e
DATE=$(date +%Y-%m-%d)
BACKUP_DIR="/var/backups/postgres"
DB_NAME="mydb"
USER="backup_user"
mkdir -p "$BACKUP_DIR"
PGPASSWORD='secret' pg_dump -h localhost -U "$USER" -F c -d "$DB_NAME" \
-f "$BACKUP_DIR/${DB_NAME}_${DATE}.dump"
# Optional: compress older backups or upload to cloud
Combined with cron:
crontab -eAdd:
0 2 * * * /usr/local/bin/pg_backup.sh >> /var/log/pg_backup.log 2>&1This runs the backup every night at 02:00.
Example retention policy
You might keep:
- Last 7 daily backups.
- Last 4 weekly backups.
- Last 12 monthly backups.
You can implement this with a simple shell script that deletes old files based on filename date.
A good backup strategy must consider:
- Backup frequency.
- Retention period.
- Off-site storage.
- Restore time and complexity.
Do not keep all backups on the same server only. If the server is lost, you lose both the database and the backups.
Testing Your Backups
Backups that have never been restored are not trustworthy.
You should regularly perform test restores:
- Spin up a temporary PostgreSQL instance (Docker is great for this).
- Restore your latest backup.
- Run basic checks:
- All expected databases exist.
- Key tables are present.
- Simple queries return expected data.
- Optionally run your application in "staging" mode against the restored DB.
Example: restore test using Docker
- Run a fresh PostgreSQL container:
docker run --name pg-test -e POSTGRES_PASSWORD=secret -p 5433:5432 -d postgres:16- Copy the backup file inside the container (or mount as volume):
docker cp mydb_latest.dump pg-test:/backup.dump- Restore inside the container:
docker exec -it pg-test bash -c "
createdb -U postgres testdb && \
pg_restore -U postgres -d testdb /backup.dump
"- Connect and run a quick check:
psql -h localhost -p 5433 -U postgres -d testdb -c 'SELECT COUNT(*) FROM users;'This verifies that:
- The backup file is not corrupted.
- Roles / permissions work as expected.
- Your restore commands are correct and repeatable.
Document this procedure in your project docs so it is easy to repeat.
Backup and Restore in Development Workflows
Beyond disaster recovery, backups are also useful for everyday development.
Cloning production data to staging
A common workflow:
- Create a logical backup from production:
pg_dump -h prod-host -U backup_user -F c -d mydb -f prod_mydb.dump- Transfer the file to staging environment.
- Restore into a staging database:
createdb -h staging-host -U staging_user mydb_staging
pg_restore -h staging-host -U staging_user -d mydb_staging prod_mydb.dumpIf production has sensitive data, you should:
- Mask or anonymize data after restore.
- Or have a script that cleans personal information.
Sharing test data with teammates
You can capture a snapshot of your local development database and share it:
pg_dump -h localhost -U devuser -F c -d devdb -f devdb_template.dumpYour teammate restores:
createdb -h localhost -U devuser devdb
pg_restore -h localhost -U devuser -d devdb devdb_template.dumpNow you both work on the same starting data set.
Migrating between PostgreSQL versions
To move from PostgreSQL 14 to 16:
- Create a logical dump from the old version:
pg_dump -h old-host -U postgres -F c -d mydb -f mydb_v14.dump- Install PostgreSQL 16 and create a new empty database:
createdb -h new-host -U postgres mydb- Restore:
pg_restore -h new-host -U postgres -d mydb mydb_v14.dumpLogical dumps handle many version differences automatically, while physical backups do not.
Security Considerations for Backups
Backups often contain all your sensitive data. You must secure them.
Where backups are stored
Do not leave backups:
- In world readable directories.
- In public cloud buckets.
- In public Git repositories.
Use:
- Proper file permissions, for example directory owned by
postgreswith0700. - Private, access controlled cloud storage buckets.
Example:
mkdir -m 700 /var/backups/postgres
chown postgres:postgres /var/backups/postgresEncryption
Consider encrypting backups at rest, especially for production data.
Options:
- Encrypt at storage layer (encrypted S3 bucket, encrypted disk).
- Encrypt files before upload, for example using
gpg:
pg_dump -h localhost -U backup_user -F c -d mydb \
| gpg --symmetric --cipher-algo AES256 \
-o mydb.dump.gpgTo restore:
gpg -d mydb.dump.gpg | pg_restore -h localhost -U postgres -d mydbKeep encryption keys safe and backed up also, otherwise you cannot restore.
Access control
- Use a dedicated
backup_userrole with minimal privileges needed to read data. - Avoid using superuser accounts where not necessary.
- Limit who can read or restore from backups.
Also remember legal and compliance requirements, such as GDPR, which may affect how long you can keep user data.
Summary
Key ideas from this chapter:
- Use
pg_dumpfor logical backups of individual databases. - Use
pg_dumpallfor cluster wide or global (roles) backups. - Restore plain SQL with
psql, custom and directory dumps withpg_restore. - Physical backups with
pg_basebackupare important for large systems and PITR, but are more complex and less portable. - Schedule regular automated backups and store them safely, ideally off site.
- Regularly test restoring your backups, preferably using automated scripts and staging environments.
- Treat backups as sensitive data, protect them with proper permissions, storage, and encryption.
With a solid understanding of these tools and principles, you can design backend systems that are much more resilient to mistakes, failures, and migrations.
Views: 7
KAHIBARO