Backups, Restore, and Basic Maintenance: Protecting Your PostgreSQL Data

Welcome back.
In the previous lesson, you learned how PostgreSQL connects to real applications.
You learned about:
- host;
- port;
- database name;
- username;
- password;
- connection strings;
- environment variables;
.envfiles;- application users;
- permissions;
- Docker host names;
- common connection errors.
Very good.
Now your database can work with real applications.
That is powerful.
But power creates responsibility.
And sometimes panic.
Because once your application stores real data, one question becomes very important:
What happens if something goes wrong?
A table is deleted.
A server crashes.
A bad migration runs.
A developer types DELETE without WHERE.
A laptop dies.
A disk fails.
Someone says:
I only changed one small thing.
Very dangerous sentence.
Today we will learn about backups, restore, and basic maintenance.
This is not the most glamorous part of databases.
Nobody opens a database course and says:
I cannot wait to learn backup strategy.
But backups are boring only until the day you need them.
Then they become beautiful.
Like a parachute.
Not exciting during normal walking.
Very important during falling.
What You Will Learn
In this lesson, you will learn:
- why backups are important;
- what
pg_dumpdoes; - how to create a plain SQL backup;
- how to restore a plain SQL backup;
- how to create a custom-format backup;
- how to restore with
pg_restore; - how to back up one database;
- how to restore into a new database;
- why testing backups matters;
- basic backup strategy;
- what
VACUUMdoes; - what
ANALYZEdoes; - what
REINDEXdoes; - basic maintenance habits;
- common mistakes with backups.
By the end of this lesson, you will understand how to protect your PostgreSQL data.
Because creating data is good.
Using data is useful.
Losing data is character-building.
But let us avoid too much character-building.
Why Backups Matter
A backup is a copy of your database that you can use if something goes wrong.
Simple idea.
Very important.
Without backups, data loss can be permanent.
Examples of problems:
Someone deletes rows by mistake.
A migration breaks tables.
A server disk fails.
A database becomes corrupted.
A wrong command drops a table.
A deploy goes badly.
A production database is overwritten.
Some of these sound dramatic.
Some sound stupid.
In real life, both happen.
Computers fail.
Humans fail.
Developers fail creatively.
A backup gives you a way back.
It is not magic.
But it is hope in file form.
Backup Is Not Real Until Restore Works
Very important rule:
A backup you never tested is only a theory.
You may have a backup file.
But can you restore it?
Does it contain the data you expect?
Is it complete?
Is it too old?
Does it require a password you forgot?
Does it restore to a working database?
If you do not know, you do not really have a backup strategy.
You have a lucky charm.
And PostgreSQL is not impressed by lucky charms.
So always remember:
Backup is step one.
Restore test is step two.
Both matter.
Prepare a Demo Database
Open PostgreSQL:
sudo -iu postgres psql
Create a demo database:
CREATE DATABASE backup_demo_db;
Connect to it:
\c backup_demo_db
Create a table:
CREATE TABLE notes (
id SERIAL PRIMARY KEY,
title VARCHAR(150) NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Insert some data:
INSERT INTO notes (title, content)
VALUES
('First Note', 'This is the first note.'),
('Backup Note', 'This row should survive backup and restore.'),
('PostgreSQL Note', 'Databases remember things. Sometimes too well.');
Check the data:
SELECT * FROM notes;
Now we have a small database to back up.
Not very important data.
But good for practice.
Practice with fake data.
Not with production.
Production is not a playground.
Production is where small mistakes wear expensive shoes.
What Is pg_dump?
pg_dump is a PostgreSQL tool that creates a backup of a database.
It can export:
table structure
data
indexes
constraints
sequences
database objects
It does not back up the entire PostgreSQL server by default.
It backs up one database.
Basic idea:
pg_dump reads a database and writes a backup file.
You run pg_dump from the terminal.
Not inside psql.
That is important.
Inside psql, you write SQL commands.
In the terminal, you run tools like:
pg_dump
psql
createdb
dropdb
pg_restore
Different places.
Different tools.
Same database adventure.
Create a Plain SQL Backup
Exit psql first:
\q
Now run this from your terminal:
pg_dump -U postgres -d backup_demo_db > backup_demo_db.sql
This creates a file:
backup_demo_db.sql
This is a plain SQL backup.
It contains SQL commands that can recreate the database structure and data.
You can inspect it:
less backup_demo_db.sql
You may see commands like:
CREATE TABLE
COPY
ALTER TABLE
CREATE SEQUENCE
A plain SQL backup is human-readable.
That is nice.
It can be restored with psql.
Simple.
Useful.
Very beginner-friendly.
Backup with Host and Port
Sometimes you need to specify host and port:
pg_dump -h localhost -p 5432 -U postgres -d backup_demo_db > backup_demo_db.sql
This means:
-h localhost connect to localhost
-p 5432 use port 5432
-U postgres connect as user postgres
-d backup_demo_db back up this database
If you use an application user, replace postgres with that user.
Example:
pg_dump -h localhost -p 5432 -U app_user -d app_db > app_db.sql
The user must have permission to read the database.
PostgreSQL cannot back up what the user cannot see.
Very fair.
Very strict.
Very PostgreSQL.
Restore a Plain SQL Backup
Now let us restore the backup into a new database.
Create a new database:
createdb -U postgres backup_demo_restore_db
Now restore:
psql -U postgres -d backup_demo_restore_db < backup_demo_db.sql
This reads the SQL file and runs it inside the new database.
Now connect:
psql -U postgres -d backup_demo_restore_db
Check the data:
SELECT * FROM notes;
You should see the rows.
Good.
You created a backup.
You restored it.
You tested it.
This is the correct path.
Backup without restore test is confidence theater.
Restore test is reality.
Reality matters.
Annoying but true.
Drop and Restore Example
Let us simulate a disaster.
Connect to the restored database:
psql -U postgres -d backup_demo_restore_db
Drop the table:
DROP TABLE notes;
Now check:
SELECT * FROM notes;
PostgreSQL will complain:
relation "notes" does not exist
Very sad.
Very educational.
Exit:
\q
Restore again:
psql -U postgres -d backup_demo_restore_db < backup_demo_db.sql
If the table already exists, you may get errors unless the backup includes drop commands.
For cleaner restore practice, you can recreate the database:
dropdb -U postgres backup_demo_restore_db
createdb -U postgres backup_demo_restore_db
psql -U postgres -d backup_demo_restore_db < backup_demo_db.sql
Then check again:
psql -U postgres -d backup_demo_restore_db
SELECT * FROM notes;
The data is back.
This is the magic feeling of backups.
Not actual magic.
Better.
Tested procedure.
Create a Backup with --clean
You can create a backup that includes commands to drop existing objects before recreating them.
Example:
pg_dump -U postgres --clean --if-exists -d backup_demo_db > backup_demo_clean.sql
Options:
--clean include DROP commands
--if-exists avoid errors if objects do not exist
This can make restoring over an existing database easier.
But be careful.
A backup with DROP commands can remove existing objects during restore.
That may be what you want.
Or it may be how your day becomes very exciting.
Always know what your backup file does.
SQL files are not decoration.
They execute.
PostgreSQL obeys.
No emotional support included.
Custom Format Backups
PostgreSQL also supports custom-format backups.
Create one with:
pg_dump -U postgres -F c -d backup_demo_db -f backup_demo_db.dump
Here:
-F c custom format
-f file_name output file
This creates:
backup_demo_db.dump
This file is not plain readable SQL.
But it is flexible.
You restore it with pg_restore.
Custom format is useful because you can:
restore selected tables
restore with more options
use parallel restore in larger cases
list backup contents
For many real projects, custom format is a good choice.
Less readable.
More powerful.
Like a database toolbox in compressed form.
Restore a Custom Format Backup
Create a new database:
createdb -U postgres backup_demo_custom_restore_db
Restore with pg_restore:
pg_restore -U postgres -d backup_demo_custom_restore_db backup_demo_db.dump
Now connect:
psql -U postgres -d backup_demo_custom_restore_db
Check the data:
SELECT * FROM notes;
You should see your data.
Good.
Now you know both styles:
plain SQL backup -> restore with psql
custom backup -> restore with pg_restore
Simple rule.
Very useful.
Plain SQL vs Custom Format
Plain SQL backup:
pg_dump -U postgres -d backup_demo_db > backup_demo_db.sql
Restore:
psql -U postgres -d new_db < backup_demo_db.sql
Good for:
simple backups
learning
human-readable files
small databases
easy inspection
Custom format backup:
pg_dump -U postgres -F c -d backup_demo_db -f backup_demo_db.dump
Restore:
pg_restore -U postgres -d new_db backup_demo_db.dump
Good for:
larger databases
more flexible restore
selected object restore
production-style workflows
Both are useful.
Do not turn this into a religious war.
Databases already have enough drama.
List Contents of a Custom Backup
You can inspect a custom backup:
pg_restore -l backup_demo_db.dump
This lists what is inside the backup.
You may see tables, sequences, constraints, and data sections.
This is useful if you want to understand what the backup contains.
Because “I hope it has the table” is not a strategy.
Check.
Verify.
Then sleep better.
Maybe.
Backup File Names
Use clear file names.
Bad:
backup.sql
new_backup.sql
backup_final.sql
backup_final_REAL.sql
backup_please_work.sql
Good:
backup_demo_db_2026-05-03.sql
shop_db_2026-05-03_22-30.dump
portfolio_db_before_migration_2026-05-03.dump
Include:
database name
date
time if useful
reason if useful
Example:
pg_dump -U postgres -F c -d shop_db -f shop_db_2026-05-03.dump
Good names save brains.
Bad names create archaeology.
And nobody wants to excavate backup files at 2 AM.
Compress a Plain SQL Backup
Plain SQL backups can be large.
You can compress them:
pg_dump -U postgres -d backup_demo_db | gzip > backup_demo_db.sql.gz
Restore compressed backup:
gunzip -c backup_demo_db.sql.gz | psql -U postgres -d backup_demo_restore_db
This is useful for saving disk space.
But remember:
Compressed files should also be tested.
A compressed broken backup is still broken.
Just smaller.
Very efficient sadness.
Backup Before Dangerous Changes
Before doing risky operations, create a backup.
Examples of risky operations:
large migration
dropping columns
changing data types
mass update
mass delete
schema redesign
production deploy
importing external data
Example backup:
pg_dump -U postgres -F c -d shop_db -f shop_db_before_migration_2026-05-03.dump
Then do the dangerous change.
If something goes wrong, you have a restore point.
This is professional.
This is calm.
This is how you avoid making database history in a bad way.
The Dangerous DELETE Example
This is dangerous:
DELETE FROM customers;
It deletes all rows from customers.
Maybe you meant:
DELETE FROM customers
WHERE id = 5;
Small difference.
Huge result.
Before running dangerous commands, use a transaction when possible:
BEGIN;
Run your command:
DELETE FROM customers
WHERE id = 5;
Check:
SELECT * FROM customers;
If correct:
COMMIT;
If wrong:
ROLLBACK;
Transactions can save you from some mistakes.
Backups can save you from bigger mistakes.
Both are useful.
Like seatbelts and airbags.
Do not choose only one because you feel brave.
Basic Backup Strategy
A simple backup strategy should answer:
How often do we back up?
Where do we store backups?
How long do we keep backups?
Who can access backups?
How do we restore?
Have we tested restore?
For a small project, you might do:
daily backup
keep last 7 daily backups
keep weekly backup for 1 month
store backups outside the server
test restore regularly
For more serious projects, you need a stronger plan.
But even a simple tested plan is much better than:
I think the server provider has something.
Maybe they do.
Maybe they do not.
Maybe it costs extra.
Maybe it was never enabled.
Maybe it restores the whole server but not the one database you need.
Check.
Do not assume.
Assumption is the cousin of disaster.
Store Backups Somewhere Safe
If your database and backup are on the same server, and the server dies, both may be gone.
That is bad.
Better backup locations:
another server
external storage
secure cloud storage
encrypted backup storage
offline copy for important data
At minimum, avoid keeping the only backup next to the original database.
That is like keeping your spare house key inside the burning house.
Technically a spare.
Practically not helpful.
Protect Backup Files
Backups contain data.
Sometimes sensitive data.
That means backup files must be protected.
Rules:
do not expose backups publicly
limit access
use strong permissions
consider encryption
delete old backups safely
do not email database dumps casually
A database backup can be as sensitive as the live database.
Sometimes more sensitive.
Because people forget it exists.
Attackers do not.
Very rude of them.
But true.
Basic Maintenance: VACUUM
PostgreSQL uses a system where updated and deleted rows leave old row versions behind.
This is part of how PostgreSQL handles concurrency.
Very clever.
But it means PostgreSQL needs cleanup.
That cleanup is called:
VACUUM
Run:
VACUUM;
This cleans up dead rows in the current database.
Most PostgreSQL installations have autovacuum enabled.
Autovacuum runs automatically.
Good.
Because manually remembering everything is how humans lose.
Still, it is useful to know what VACUUM does.
It helps PostgreSQL keep tables healthy.
Like cleaning a workshop.
Not glamorous.
Very necessary.
VACUUM ANALYZE
You can run:
VACUUM ANALYZE;
This does two things:
VACUUM cleans dead row versions.
ANALYZE updates planner statistics.
Planner statistics help PostgreSQL choose good query plans.
If statistics are outdated, PostgreSQL may make bad choices.
Like using the wrong index.
Or scanning too much.
Or generally acting confused.
ANALYZE helps PostgreSQL understand the data better.
A database with fresh statistics is a happier database.
Probably.
PostgreSQL does not smile.
But performance can improve.
ANALYZE Alone
You can run:
ANALYZE;
This updates statistics without vacuuming.
You can also analyze one table:
ANALYZE products;
This is useful after large data changes.
Example:
imported many rows
deleted many rows
updated many rows
changed data distribution
PostgreSQL uses statistics to decide how to execute queries.
Bad statistics can produce bad plans.
Bad plans produce slow queries.
Slow queries produce sad developers.
The chain is clear.
REINDEX
Indexes can sometimes become bloated or need rebuilding.
PostgreSQL has:
REINDEX
Example:
REINDEX DATABASE backup_demo_db;
Or one index:
REINDEX INDEX index_name;
Or one table:
REINDEX TABLE table_name;
Do not run REINDEX randomly every five minutes.
This is not brushing your teeth.
Use it when needed.
For beginners, just know:
REINDEX rebuilds indexes.
In real maintenance, you should understand why you are doing it.
Blind maintenance is still blind.
Even if it wears a serious hat.
Check Database Size
You can check database size:
SELECT pg_size_pretty(pg_database_size('backup_demo_db'));
Check table sizes:
SELECT
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;
This helps you understand what is taking space.
Sometimes the biggest table is expected.
Sometimes it is a log table that nobody cleaned for three years.
Databases collect history.
Sometimes too much history.
Like a drawer full of old cables.
Check Active Connections
You can see active connections:
SELECT
pid,
usename,
datname,
state,
query
FROM pg_stat_activity;
This shows who is connected and what they are doing.
Useful for debugging.
Maybe an application has too many connections.
Maybe a query is stuck.
Maybe someone opened psql and went for lunch.
PostgreSQL knows.
PostgreSQL remembers.
PostgreSQL judges silently.
Find Slow Queries
PostgreSQL can log slow queries if configured.
A common setting is:
log_min_duration_statement
Example idea:
log queries slower than 1000 ms
This is usually configured in PostgreSQL configuration files.
For beginners, the important idea is:
Slow queries should be observed, not guessed.
Use tools like:
EXPLAIN ANALYZE
logs
monitoring
pg_stat_statements
pg_stat_statements is an extension that can help track query performance.
That is more advanced.
But good to know.
Performance work without observation is database astrology.
And we already do not trust database astrology.
Backup and Maintenance Checklist
A simple checklist:
Create regular backups.
Store backups outside the database server.
Protect backup files.
Test restore.
Back up before migrations.
Use app-specific users.
Keep PostgreSQL updated.
Monitor disk space.
Watch slow queries.
Let autovacuum run.
Run ANALYZE after big imports if needed.
Document restore steps.
Documentation matters.
A restore procedure should not live only in your head.
Especially if your head is tired.
Or on vacation.
Or drinking coffee while production burns.
Write the steps down.
Future you deserves mercy.
Common Backup Mistakes
Never Testing Restore
This is the biggest mistake.
A backup file is not enough.
You must know it restores.
Test restore on another database.
Example:
createdb -U postgres restore_test_db
pg_restore -U postgres -d restore_test_db shop_db_2026-05-03.dump
Then check the data.
Trust, but verify.
Actually, with backups:
Do not trust.
Verify.
Keeping Backups Only on the Same Server
If the server dies, you may lose database and backup together.
Bad.
Store copies elsewhere.
A backup should survive the failure of the original machine.
That is the point.
Backing Up Too Rarely
If you back up once per month, you may lose a month of data.
Maybe acceptable for a toy project.
Not acceptable for a real shop.
Choose backup frequency based on how much data you can afford to lose.
This question has a serious name:
Recovery Point Objective
But simple version:
How much data loss is acceptable?
If the answer is “almost none”, you need a serious strategy.
Not Knowing How Long Restore Takes
A backup may exist.
But restore may take time.
For small databases, maybe seconds.
For huge databases, maybe hours.
If your application is down, restore time matters.
This question has another serious name:
Recovery Time Objective
Simple version:
How long can the system be down?
Business people suddenly become very interested in databases here.
Funny how that works.
Backing Up the Wrong Database
Classic mistake.
You think you backed up production.
Actually, you backed up local development.
The backup contains three test users and a product called “banana”.
Check:
SELECT current_database();
Check backup file names.
Check restore result.
Do not assume.
Practice
Create a backup of backup_demo_db:
pg_dump -U postgres -d backup_demo_db > backup_demo_db.sql
Create a restore database:
createdb -U postgres backup_demo_restore_db
Restore:
psql -U postgres -d backup_demo_restore_db < backup_demo_db.sql
Check:
psql -U postgres -d backup_demo_restore_db
SELECT * FROM notes;
Create a custom backup:
pg_dump -U postgres -F c -d backup_demo_db -f backup_demo_db.dump
Create another restore database:
createdb -U postgres backup_demo_custom_restore_db
Restore:
pg_restore -U postgres -d backup_demo_custom_restore_db backup_demo_db.dump
Check:
psql -U postgres -d backup_demo_custom_restore_db
SELECT * FROM notes;
Run maintenance:
VACUUM ANALYZE;
Check database size:
SELECT pg_size_pretty(pg_database_size(current_database()));
This is practical.
This is not glamorous.
This is useful.
Like a good screwdriver.
Again.
Databases love screwdrivers.
Metaphorically.
Mini Challenge
Create a backup plan for a small blog database.
Assume:
database name: blog_app_db
data changes every day
the blog is small
losing more than one day of data is bad
you deploy changes weekly
Write a simple backup plan:
Backup frequency:
Backup format:
Backup location:
Retention:
Restore test frequency:
Backup before migrations:
Who can access backups:
Example answer:
Backup frequency: daily
Backup format: custom format with pg_dump -F c
Backup location: external secure storage
Retention: keep daily backups for 7 days and weekly backups for 1 month
Restore test frequency: once per month
Backup before migrations: always
Who can access backups: only the administrator
Now create a backup command:
pg_dump -U postgres -F c -d blog_app_db -f blog_app_db_2026-05-03.dump
Create a restore test command:
createdb -U postgres blog_app_restore_test_db
pg_restore -U postgres -d blog_app_restore_test_db blog_app_db_2026-05-03.dump
Then write the check query:
SELECT COUNT(*) FROM posts;
This is how you start thinking like someone responsible for data.
A little scary.
Very useful.
Very adult.
Summary
Today you learned:
- backups protect your data from mistakes and failures;
pg_dumpcreates PostgreSQL backups;- plain SQL backups can be restored with
psql; - custom-format backups can be restored with
pg_restore; - backup files should have clear names;
- compressed backups can save space;
- backups should be stored safely;
- backups should be protected like sensitive data;
- restore testing is essential;
- backup before dangerous migrations or large changes;
VACUUMcleans dead row versions;ANALYZEupdates planner statistics;VACUUM ANALYZEdoes both;REINDEXrebuilds indexes when needed;- database size and connections can be inspected;
- slow queries should be measured;
- a backup strategy should include frequency, location, retention, and restore testing.
This lesson is extremely important.
Maybe not flashy.
But important.
A developer who can create tables is useful.
A developer who can query data is better.
A developer who can protect and restore data is much more trustworthy.
Because real projects are not only about building.
They are also about surviving mistakes.
And backups are one of the best ways to survive.
Course Summary
You have reached the end of this PostgreSQL course.
You learned:
- what databases are;
- how PostgreSQL works;
- how to create databases;
- how to create tables;
- how to insert data;
- how to read data with
SELECT; - how to filter and sort data;
- how to update and delete rows;
- how data types work;
- how constraints protect data;
- how primary keys identify rows;
- how foreign keys connect tables;
- how relationships work;
- how
JOINmakes related data readable; - how aggregate functions summarize data;
- how
GROUP BYandHAVINGwork; - how indexes help performance;
- how to build a practical shop database;
- how applications connect to PostgreSQL;
- how backups and restore protect your work.
That is a lot.
You did not just learn commands.
You learned how to think about data.
That matters.
Because applications come and go.
Frameworks change.
Frontend trends change every fifteen minutes.
But data stays important.
PostgreSQL is a serious tool.
Now you know enough to start using it seriously.
Carefully.
Practically.
And with backups.
Always with backups.
Final Words
PostgreSQL will not always be easy.
Sometimes it will complain.
Sometimes it will reject your query.
Sometimes it will say:
syntax error at or near...
and you will stare at one comma for ten minutes.
This is normal.
You are learning a real tool.
A powerful tool.
A tool used in real applications, real companies, real APIs, real dashboards, and real systems where data matters.
Keep practicing.
Create small projects.
Break things.
Restore them.
Write queries.
Use EXPLAIN ANALYZE.
Make backups.
Test backups.
Respect data.
And remember:
Browsers forget.
Databases remember.
Backups forgive.
Very PostgreSQL.
Very practical.
Very good.