Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

PostgreSQL Backup and Restore with pg_dump

By Kokil Thapa | Last reviewed: September 2026

Production data loss rarely starts with a dramatic server crash. It starts with a bad migration, a mistaken DROP TABLE, or a restore plan nobody tested. PostgreSQL backup and restore with pg_dump is the first tool I reach for on small and mid-size servers because it ships with PostgreSQL, needs no extra daemons, and produces portable dumps you can inspect before disaster strikes. If you run PostgreSQL 18 alongside a Laravel application on PostgreSQL, this workflow belongs in your deployment checklist—not as an afterthought on launch day.

What is pg_dump and when should you use it?

pg_dump creates a logical backup of one database. It reads tables, indexes, constraints, sequences, and permissions, then writes SQL or a custom archive file. Unlike file-level copies of /var/lib/postgresql, logical dumps survive version upgrades and cross-platform moves more easily.

On real client projects I treat pg_dump as the baseline layer. It does not replace PostgreSQL point-in-time recovery with WAL archiving when you need minute-level RPO. For many Laravel booking apps, legal portals, and eCommerce backends, nightly pg_dump plus off-site storage is enough.

pg_dump Logical Backup FlowLive DBPostgreSQL 18pg_dumpSQL or customArchive.sql or .dumpOff-site copyS3, rsync, resticRestore targetpsql / pg_restoreLogical dumps travel across hosts and minor version upgrades
PostgreSQL backup and restore with pg_dump: from live database through archive to off-site storage and restore target

Choose pg_dump when you need portability, selective restore, or a simple cron job on Ubuntu. Skip it as your only strategy when you require continuous replication or sub-hour recovery windows. Pair it with monitoring and documented runbooks instead.

How do you create a PostgreSQL backup with pg_dump?

Install client tools matching your server major version. On Ubuntu 24.04 with PostgreSQL 18, the package is typically postgresql-client-18. Always dump from a user with sufficient privileges—often a dedicated backup role, not the application user.

Plain SQL dump (best for small databases)

Plain SQL is human-readable. You can grep it, edit it, and restore with psql. It works well for schemas under a few hundred megabytes.

export PGHOST=127.0.0.1
export PGPORT=5432
export PGUSER=backup_user
export PGPASSWORD='your-secure-password'

pg_dump \
  --dbname=myapp_production \
  --format=plain \
  --no-owner \
  --no-acl \
  --file=/var/backups/postgresql/myapp_$(date +%F).sql

gzip -9 /var/backups/postgresql/myapp_$(date +%F).sql

The --no-owner and --no-acl flags avoid restore failures when target roles differ. That matters when you restore to staging with different usernames.

Custom format dump (best for production)

Custom format (-Fc) compresses data and supports parallel restore. This is my default on production Laravel apps backed by PostgreSQL.

pg_dump \
  --dbname=myapp_production \
  --format=custom \
  --compress=6 \
  --file=/var/backups/postgresql/myapp_$(date +%F_%H%M).dump

pg_dump \
  --dbname=myapp_production \
  --schema-only \
  --file=/var/backups/postgresql/myapp_schema_$(date +%F).sql

Keep a separate schema-only dump. After a bad migration, restoring structure without data is often faster than rolling back application code alone.

Globals, roles, and pg_dumpall

pg_dump does not export cluster-wide objects. Use pg_dumpall for roles, tablespaces, and database-level grants.

pg_dumpall \
  --globals-only \
  --file=/var/backups/postgresql/globals_$(date +%F).sql

Store globals alongside per-database dumps. Restoring a database without matching roles produces permission errors that look like application bugs.

Which pg_dump format should you choose: plain SQL vs custom vs directory?

Format choice affects restore speed, storage size, and operational flexibility. The table below summarises what I use in practice on Linux-managed PostgreSQL servers.

FormatFlagRestore toolParallel restoreBest for
Plain SQL-FppsqlNoSmall DBs, quick inspection, version control of schema
Custom-Fcpg_restoreYes (-j)Production nightly backups, selective table restore
Directory-Fdpg_restoreYesVery large databases, file-level dedup with restic
Tar-Ftpg_restoreYesLegacy scripts; custom format is usually simpler

For databases above 10 GB, directory format plus parallel dump can cut backup windows significantly. Below that threshold, custom format keeps scripts simple without sacrificing restore options.

pg_dump Format DecisionDatabase size?Under 10 GBPlain SQLEasy auditCustom -FcProduction defaultDirectory -FdHuge DBsNeed table-level restore?Use custom or directory
Choosing a pg_dump format for PostgreSQL backup and restore based on database size and restore flexibility

How do you restore a PostgreSQL database from pg_dump?

Restore is where untested backups die. Read the dump header first. Never pipe an unknown file straight into production.

Restore plain SQL with psql

createdb -O app_user myapp_restore_test

gunzip -c /var/backups/postgresql/myapp_2026-09-09.sql.gz | \
  psql --dbname=myapp_restore_test --set ON_ERROR_STOP=on

ON_ERROR_STOP=on halts on the first error. Without it, psql keeps going and you get a half-restored database that passes smoke tests but fails on edge cases.

Restore custom format with pg_restore

createdb -O app_user myapp_restore_test

pg_restore \
  --dbname=myapp_restore_test \
  --jobs=4 \
  --verbose \
  --no-owner \
  --no-acl \
  /var/backups/postgresql/myapp_2026-09-09.dump

Restore into an empty database. If objects already exist, you will see duplicate-key and relation-exists errors that waste hours.

Selective restore of one table

Custom format shines here. List contents, then restore a single table.

pg_restore --list /var/backups/postgresql/myapp_2026-09-09.dump | grep orders

pg_restore \
  --dbname=myapp_production \
  --table=orders \
  --data-only \
  /var/backups/postgresql/myapp_2026-09-09.dump

Use selective restore carefully on production. Foreign keys and sequences can desynchronise if you restore data without matching related tables.

Restore globals and roles

psql -f /var/backups/postgresql/globals_2026-09-09.sql postgres

Run globals before database restore on a fresh cluster. On an existing server, review the SQL file first—blindly re-applying roles can reset passwords.

Restore Sequence1. Globalspg_dumpall2. Create DBcreatedb3. Restorepsql / pg_restore4. Verifycounts + queriesCommon restore failuresMissing extensions (pg_trgm, uuid-ossp)Role / owner mismatch after --no-ownerRestoring into non-empty schemaForgot to reindex or analyse after bulk load
Recommended restore order for PostgreSQL backup and restore with pg_dump and typical failure points

How do you automate pg_dump backups on a production server?

Manual dumps fail the first busy week. Automate with cron, log every run, and push copies off the server the same night. A backup on the same disk as PostgreSQL data is not a backup.

Example cron script

Save this as /usr/local/bin/pg_backup_myapp.sh. Make it executable and restrict permissions to root or a backup user.

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/var/backups/postgresql/myapp"
RETAIN_DAYS=14
DBNAME="myapp_production"
TIMESTAMP="$(date +%F_%H%M%S)"
LOG="/var/log/postgresql/backup-myapp.log"

mkdir -p "$BACKUP_DIR"
export PGHOST=127.0.0.1 PGPORT=5432 PGUSER=backup_user
export PGPASSWORD="$(cat /etc/postgresql/backup-myapp.password)"

pg_dump --dbname="$DBNAME" --format=custom --compress=6 \
  --file="$BACKUP_DIR/${DBNAME}_${TIMESTAMP}.dump"

pg_dumpall --globals-only \
  --file="$BACKUP_DIR/globals_${TIMESTAMP}.sql"

find "$BACKUP_DIR" -type f -mtime +"$RETAIN_DAYS" -delete

rsync -az "$BACKUP_DIR/" backup@offsite:/srv/backups/postgresql/myapp/

echo "$(date -Is) OK ${DBNAME}_${TIMESTAMP}.dump" >> "$LOG"

Schedule it during low-traffic hours. On Nepal-hosted apps, 02:30 NPT often works well.

30 2 * * * /usr/local/bin/pg_backup_myapp.sh

For Laravel apps, also review Laravel Spatie backup automation. Spatie wraps pg_dump cleanly and can push to S3. I still keep a shell script on the database host as a fallback when the app layer is down.

Off-site options include rsync over SSH, restic, or S3-compatible storage. See automated server backup setup and Ubuntu server backup strategies for wider context beyond PostgreSQL alone.

Locking, consistency, and replication

By default, pg_dump takes an MVCC snapshot. Reads stay consistent without long exclusive locks on InnoDB-style tables. Heavy write workloads may see slightly longer dump duration, not write blocking.

On a replica, run pg_dump against a standby node to keep load off the primary. Confirm the replica is caught up first. Stale replicas produce stale backups.

For zero-downtime requirements beyond logical dumps, add PostgreSQL replication and high availability. Logical backup plus streaming replication covers most SMB budgets I see in Nepal—roughly Rs 3,000–8,000/month (~USD 22–60) for a modest VPS plus storage.

What restore testing should you actually perform after pg_dump?

A backup file that never restores is expensive disk usage. Monthly restore drills catch permission drift, missing extensions, and broken cron paths before an incident.

  1. Restore the latest dump to an isolated database name such as myapp_restore_drill.
  2. Compare row counts on critical tables: orders, users, payments.
  3. Run three application queries or report queries that finance or ops rely on.
  4. Check sequences: SELECT last_value FROM orders_id_seq; versus MAX(id).
  5. Document elapsed time. If restore exceeds your RTO, adjust format, parallel jobs, or hardware.
  6. Delete the drill database after sign-off to free space.

This mirrors the checklist in database restore testing you should actually do. On a production Laravel booking platform I maintain, a missed extension restore once broke full-text search silently. Row counts looked fine. Search returned nothing.

Validate JSON exports with a JSON formatter when debugging API snapshot tables. Corrupted JSON in a dump usually means the source row was already bad—not a pg_dump bug.

Production pg_dump GotchasBefore backupCron uses correct PGHOSTPassword file mode 0600Disk space headroom checkedAfter backupFile size not zero bytesOff-site copy confirmedLog line appendedMonthly drillRestore to scratch DB on separate hostRun app health checks against restoreRecord RTO and fix gaps
Operational checklist for reliable PostgreSQL backup and restore with pg_dump in production

Compare pg_dump against MySQL workflows if you are migrating stacks. PostgreSQL vs MySQL for production and database migration from MySQL to PostgreSQL both assume you can prove data integrity with restorable dumps.

For application-level context, read PostgreSQL administration essentials and database backup strategies for small servers. Small teams often skip WAL archiving initially. That is acceptable when pg_dump restores are tested and RPO of 24 hours is agreed with the business.

Official references remain the source of truth. The PostgreSQL pg_dump documentation and PostgreSQL backup dump chapter describe every flag. The pg_restore manual covers parallel jobs and selective restore in detail.

On hosted legal-tech and booking projects such as Adventure Third Pole Trek, PostgreSQL holds reservations, payments, and supplier data. Losing a night of bookings hurts operations immediately. Tested pg_dump automation plus off-site copies is the minimum viable safety net before budget allows full HA.

If you want hands-off monitoring, pair dumps with ongoing server support and maintenance. Alerts on missing backup files catch cron regressions after OS upgrades—a pattern I have seen repeatedly when PHP or PostgreSQL packages shift paths.

Key Takeaways

  • Use custom format (-Fc) for production PostgreSQL backup and restore with pg_dump; keep plain SQL for small schemas and schema-only snapshots.
  • Always dump globals with pg_dumpall --globals-only and store them beside per-database archives.
  • Restore to an empty database with ON_ERROR_STOP=on for SQL dumps; use pg_restore -j for faster custom-format recovery.
  • Automate via cron, log every run, retain 7–30 days locally, and copy off-site the same night.
  • Run monthly restore drills with row-count checks, sequence validation, and timed RTO measurements.
  • Layer replication or PITR when RPO requirements drop below one backup interval—pg_dump alone is not continuous protection.

People Also Ask

Does pg_dump lock tables in PostgreSQL?

pg_dump uses MVCC snapshots for consistent reads. It does not require exclusive locks that block normal writes on ordinary tables. Very long dumps can increase bloat if vacuum cannot reclaim dead tuples quickly, so monitor disk and autovacuum on busy systems.

Can you restore a pg_dump backup to a newer PostgreSQL version?

Yes, within reason. Logical dumps restore cleanly to the same or newer major versions— for example PostgreSQL 17 to 18. Restoring to an older major version often fails. Test on staging before production cutovers.

What is the difference between pg_dump and pg_dumpall?

pg_dump backs up one database. pg_dumpall exports all databases in a cluster plus global objects like roles and tablespaces. Use both: pg_dumpall for globals, pg_dump per application database.

How often should you run pg_dump backups?

Nightly pg_dump suits most web apps with acceptable RPO of 24 hours. High-transaction systems may need twice-daily dumps or WAL archiving. Match frequency to business tolerance and always verify off-site copies.

Build a backup workflow you can trust under pressure

PostgreSQL backup and restore with pg_dump is boring engineering—and that is the point. Custom-format nightly dumps, globals export, off-site sync, and monthly restore drills cover most production apps I run on Ubuntu without enterprise budgets. Start tonight with one database, one cron job, and one test restore. Then expand to replicas, monitoring, and PITR when the business needs tighter recovery windows. Need help auditing an existing PostgreSQL server or wiring backups into a Laravel deploy pipeline? Contact us to review your setup before the first real incident.

Frequently Asked Questions

pg_dump creates a logical backup of one PostgreSQL database—tables, indexes, constraints, sequences, and permissions—as plain SQL or a custom archive file.

Choose pg_dump when you need portability, selective restore, or a simple cron job on Ubuntu. It is the baseline layer on small and mid-size servers for Laravel booking apps, legal portals, and eCommerce backends where nightly dumps plus off-site storage meet a 24-hour RPO. Skip it as your only strategy when you need continuous replication or sub-hour recovery windows. Logical backup plus streaming replication covers most SMB budgets in Nepal—roughly Rs 3,000–8,000/month (~USD 22–60) for a modest VPS plus storage—before investing in full point-in-time recovery with WAL archiving.

Custom format (-Fc) with --compress=6 is my production default because it compresses data and supports parallel restore via pg_restore -j. Plain SQL (-Fp) suits small schemas under a few hundred megabytes where you want human-readable files restorable with psql and gzip. Directory format (-Fd) helps databases above 10 GB where parallel dump cuts backup windows. Tar format (-Ft) appears mainly in legacy scripts. Keep a separate schema-only plain SQL dump so you can restore structure alone after a bad migration without rolling back application code.

Install client tools matching your server major version—postgresql-client-18 on Ubuntu 24.04 with PostgreSQL 18. Export PGHOST, PGPORT, PGUSER, and PGPASSWORD, then dump from a dedicated backup role with sufficient privileges, not the application user. For production, run pg_dump with --format=custom, --compress=6, and a timestamped file under /var/backups/postgresql. Run pg_dumpall --globals-only in the same job because pg_dump does not export cluster-wide roles, tablespaces, or database-level grants. Store globals beside per-database archives.

They strip ownership and ACL statements from the dump file. That prevents restore failures when the target server uses different role names—a common situation when you restore production PostgreSQL data to a Laravel staging environment with its own usernames. Without these flags, pg_restore or psql may error on missing roles or attempt to assign objects to users that do not exist on the destination cluster, producing permission errors that look like application bugs after restore.

Create an empty target database first with createdb -O app_user, then pipe the decompressed file into psql with ON_ERROR_STOP=on so the restore halts on the first error. Without ON_ERROR_STOP, psql keeps going and you get a half-restored database that passes smoke tests but fails on edge cases. Never restore into a database that already contains objects, or duplicate-key and relation-exists errors waste hours. Read the dump header before touching production—never pipe an unknown file straight into a live database.

Create an empty database, then run pg_restore with --jobs=4 for parallel restore, --verbose for logging, and the same --no-owner and --no-acl flags used at backup time. Custom format supports selective restore: run pg_restore --list, grep the table name, then restore with --table and optionally --data-only. Use selective restore carefully on production—foreign keys and sequences can desynchronise if you restore data without matching related tables. Validate sequences after restore by comparing last_value from the sequence against MAX(id) on the table.

No. pg_dump uses MVCC snapshots for consistent reads without exclusive locks that block normal writes on ordinary tables.

pg_dump backs up one database—all tables, indexes, constraints, sequences, and permissions inside that database. pg_dumpall exports cluster-wide globals: roles, tablespaces, and database-level grants across the entire cluster. Run both in production automation: pg_dumpall --globals-only for globals, pg_dump per application database. Restoring a database without matching roles produces permission errors that look like application bugs in your Laravel app. On a fresh cluster, restore globals with psql before the database dump.

Nightly pg_dump suits most web apps with an acceptable RPO of 24 hours. High-transaction systems may need twice-daily dumps or WAL archiving.

Save a bash script with set -euo pipefail under /usr/local/bin, restrict permissions to root or the backup user, dump to /var/backups/postgresql, log every run to /var/log/postgresql/, retain 14 days locally with find -mtime, and rsync off-site the same night. Schedule during low traffic—02:30 NPT works well on Nepal-hosted apps. Read PGPASSWORD from a protected file such as /etc/postgresql/backup-myapp.password, not inline in cron. For Laravel apps, Spatie backup can wrap pg_dump and push to S3, but keep a shell script on the database host as fallback when the app layer is down.

Yes, on heavy write workloads. Running pg_dump on a standby replica keeps load off the primary during long dumps. Confirm the replica is fully caught up first—a stale replica produces stale backups. pg_dump still takes an MVCC snapshot on whichever node you target, so reads stay consistent without blocking writers on that node. Very long dumps on busy systems can increase bloat if autovacuum cannot reclaim dead tuples quickly, so monitor disk and autovacuum alongside replica lag alerts.

Run monthly restore drills to an isolated database such as myapp_restore_drill. Compare row counts on critical tables like orders, users, and payments. Run three application or finance queries the business relies on. Check sequences with SELECT last_value FROM orders_id_seq versus MAX(id). Document elapsed restore time against your RTO—if restore exceeds it, adjust format, parallel jobs, or hardware. On a production Laravel booking platform I maintain, a missed extension restore broke full-text search silently even though row counts looked fine. Delete the drill database after sign-off.

Yes. Logical dumps restore cleanly to the same or newer major versions, such as PostgreSQL 17 to 18. Downgrades to an older major version often fail.

A schema-only dump captures table structures, indexes, constraints, and sequences without row data. After a bad migration on a production Laravel application, restoring structure without data is often faster than rolling back application code alone. Generate it with pg_dump --schema-only as a plain SQL file stored beside your custom-format nightly dumps. Pair schema-only snapshots with version-controlled migration history so you can compare what changed and decide whether to restore structure, data, or both during incident recovery.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: