pgBackRest 2.60.0 on AWS User Guide
Overview
This guide covers launching and verifying pgBackRest 2.60.0 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Adminer & Database Admin and Backup Tools AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other tools in the listing are described on the listing's guide hub.
pgBackRest is a backup and restore tool for PostgreSQL, developed and maintained by Crunchy Data. It takes full, differential and incremental backups, archives the write-ahead log (WAL) continuously, compresses and encrypts everything it stores, verifies what it has written and restores a cluster to a chosen point in time.
On its own pgBackRest is a single command-line program, so this image ships it as a complete, working PostgreSQL backup host: PostgreSQL 18.6 with WAL archiving into pgBackRest, an encrypted repository on a dedicated volume, backup timers, and a first full backup taken and verified before you log in. Both come from the official PostgreSQL (PGDG) apt repository, whose signing key is pinned to the fingerprint postgresql.org publishes. The packages are not held, so apt upgrade keeps delivering PGDG and Ubuntu security releases.
What is on the image:
- pgBackRest 2.60.0 (MIT licence), configured in
/etc/pgbackrest/pgbackrest.confwith one stanza,main - PostgreSQL 18.6 (
postgresql@18-main.service, PostgreSQL Licence) on127.0.0.1:5432only, witharchive_mode = onandarchive_command = 'pgbackrest --stanza=main archive-push %p' - The repository
/var/lib/pgbackrest/repoon its own 64 GiB gp3 EBS volume mounted at/var/lib/pgbackrest, encrypted with aes-256-cbc and compressed with zstd - systemd timers for a weekly full, daily differential and hourly incremental backup, and a weekly repository verify
/usr/local/sbin/pgbackrest-selftest.sh, which proves a real backup, verify and restore on demand
Default user: ubuntu · Architecture: x86_64 · Volumes: 16 GiB gp3 root + 64 GiB gp3 repository
Security posture
- No default password or passphrase. The image ships with no password on any database role, no repository passphrase and an empty repository, and no backup timer can run until first boot has finished. First boot generates a unique PostgreSQL password and a unique repository passphrase and writes them to
/root/pgbackrest-credentials.txt, readable by root only. - Encrypted repository. Every file pgBackRest writes to the repository is encrypted with aes-256-cbc using this instance's passphrase; a wrong passphrase cannot read it. The passphrase never ships in the image, so no two instances share one.
- One open port, SSH. PostgreSQL listens on
127.0.0.1:5432only and its access rules allow only local and127.0.0.1logins withscram-sha-256(thepostgressystem user also logs in over the local socket). No pgBackRest TLS server is configured or running. The security group needs SSH (22) only. - No metadata access for the database or the backups. PostgreSQL (including
archive_command), the first-boot service and every backup service are blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). Amazon Time Sync (169.254.169.123) still works. - IMDSv2 required. The AMI is registered with
ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests. - Fails closed. If first boot cannot finish (password, passphrase, stanza or first backup), it puts the passphrase-free configuration back, stops PostgreSQL and tries again on the next boot; the backup timers stay switched off until it succeeds.
- No secrets in logs. The password and passphrase are never passed on a command line or written to a log; pgBackRest shows the passphrase as
<redacted>. - Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance; SSH accepts keys only (no passwords, no root login).
Prerequisites
- An AWS account and permission to launch EC2 instances and create security groups
- An EC2 key pair in the Region you launch in
- A VPC subnet, with a public IP on the instance if you connect over the internet
Recommended instance type: m5.large. pgBackRest uses process-max=2; size the instance for your PostgreSQL workload and the repository volume for your retention.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| pgBackRest 2.60.0 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened Adminer & Database Admin and Backup Tools AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose pgBackRest 2.60.0 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended) and your key pair. - Security group: allow SSH (22) from your own IP address only. Do not open 5432: PostgreSQL listens on localhost.
- Keep the second (64 GiB) volume: it holds the backup repository. You can make it larger now or later.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:
SG=$(aws ec2 create-security-group --group-name pgbackrest \
--description "pgBackRest backup host" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=pgbackrest}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required. The repository volume comes from the AMI's block device mapping; add --block-device-mappings only to change its size.
Step 2: Connect and read your credentials
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot finishes within a minute of launch. It generates this instance's PostgreSQL password and repository passphrase, writes the passphrase into the pgBackRest configuration, creates the stanza, checks WAL archiving, takes a full backup, verifies it and then writes the credentials file. Show the connection details (the two secret lines are left out here; read them with sudo cat /root/pgbackrest-credentials.txt and keep them in your secrets manager):
sudo grep -vE '^(POSTGRES_PASSWORD|PGBACKREST_REPO_CIPHER_PASS)=' /root/pgbackrest-credentials.txt
# pgBackRest 2.60.0 + PostgreSQL 18 - generated on first boot by pgbackrest-firstboot.service.
# These values are unique to this instance. Store them somewhere safe.
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
POSTGRES_USER=postgres
PGBACKREST_STANZA=main
PGBACKREST_REPO_PATH=/var/lib/pgbackrest/repo
PGBACKREST_REPO_CIPHER=aes-256-cbc
# PostgreSQL listens on 127.0.0.1 only; the only inbound port is SSH 22.
# The repository passphrase is also repo1-cipher-pass in /etc/pgbackrest/pgbackrest.conf. Without it no backup in
# the repository can be read: copy it somewhere safe, and never change it on a repository that holds backups.
# Backups: sudo -u postgres pgbackrest --stanza=main info
# Schedule: systemctl list-timers 'pgbackrest-*'
# Self-test: sudo /usr/local/sbin/pgbackrest-selftest.sh
# User guide: https://www.cloudimg.co.uk/guides/pgbackrest-2-60-0-on-ubuntu-24-04-aws/
Keep the repository passphrase. Store PGBACKREST_REPO_CIPHER_PASS outside the instance. If the instance is lost and you still have an EBS snapshot of the repository volume, the passphrase is the only way to read the backups on it.
Step 3: Confirm pgBackRest and PostgreSQL are running
pgbackrest version
psql --version
systemctl is-active postgresql@18-main.service
sudo ss -Hltn
df -h /var/lib/pgbackrest
pgBackRest 2.60.0
psql (PostgreSQL) 18.6 (Ubuntu 18.6-1.pgdg24.04+2)
active
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
LISTEN 0 200 127.0.0.1:5432 0.0.0.0:*
LISTEN 0 4096 [::]:22 [::]:*
Filesystem Size Used Avail Use% Mounted on
/dev/nvme1n1 63G 8.8M 60G 1% /var/lib/pgbackrest
PostgreSQL listens on 127.0.0.1:5432 only and SSH is the only port reachable from the network; port 53 on 127.0.0.53/127.0.0.54 is the operating system's local DNS stub. The repository volume is mounted at /var/lib/pgbackrest.
Step 4: Inspect the repository
pgBackRest runs as the postgres system user. info lists the stanza, its encryption and every backup:
sudo -u postgres pgbackrest --stanza=main info
stanza: main
status: ok
cipher: aes-256-cbc
db (current)
wal archive min/max (18): 000000010000000000000001/000000010000000000000004
full backup: 20261107-210950F
timestamp start/stop: 2026-11-07 21:09:50+00 / 2026-11-07 21:09:53+00
wal start/stop: 000000010000000000000003 / 000000010000000000000004
database size: 22.5MB, database backup size: 22.5MB
repo1: backup set size: 2.7MB, backup size: 2.7MB
The full backup first boot took is already there, and the WAL range shows archiving is working. check confirms the configuration and archives a WAL segment end to end:
sudo -u postgres pgbackrest --stanza=main check
P00 INFO: check command begin 2.60.0: ... --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/var/lib/pgbackrest/repo --stanza=main
P00 INFO: check repo1 configuration (primary)
P00 INFO: check repo1 archive for WAL (primary)
P00 INFO: WAL segment 000000010000000000000005 successfully archived to '/var/lib/pgbackrest/repo/archive/main/18-1/...' on repo1
P00 INFO: check command end: completed successfully
The files in the repository are ciphertext. Each starts with OpenSSL's Salted__ header instead of pgBackRest's readable [backrest] text:
sudo head -c 8 /var/lib/pgbackrest/repo/backup/main/backup.info; echo
Salted__
Step 5: Take a backup
The timers take backups on schedule, and you can take one at any time. --type is full, diff (changes since the last full) or incr (changes since the last backup of any type):
sudo -u postgres pgbackrest --stanza=main --type=incr backup
sudo -u postgres pgbackrest --stanza=main info | grep -E '^ +(full|diff|incr) backup:'
full backup: 20261107-210950F
incr backup: 20261107-210950F_20261107-211502I
Retention is two full backups (repo1-retention-full=2) and six differentials (repo1-retention-diff=6); older backups and the WAL they need are expired automatically after each backup.
Step 6: Prove a restore with the self-test
The image ships a self-test that exercises the whole path on this instance: it logs in with this instance's password (and checks a wrong one is refused), confirms the repository cannot be read with a wrong passphrase, writes a known row, takes and verifies an incremental backup, drops the row, restores PostgreSQL from the repository and checks the row came back. PostgreSQL is stopped for a few seconds during the restore, so run it before you load production data or in a maintenance window:
sudo /usr/local/sbin/pgbackrest-selftest.sh
== pgBackRest backup-host self-test ==
OK: credentials file 0600 root; the repository passphrase in pgbackrest.conf matches it
OK: PostgreSQL listens on 127.0.0.1:5432 only
OK: per-VM password logs in; a wrong password is refused (and the right one still works)
OK: repository is aes-256-cbc encrypted (a wrong passphrase cannot read it); pgbackrest check passes
== round trip: backup -> verify -> destroy -> restore ==
OK: known row written, incremental backup taken and verified
OK: probe table DROPPED - the row now exists only inside the repository
OK: restored from the repository; the dropped row came back intact
PGBACKREST_SELFTEST_OK: per-VM password logs in and a wrong one is refused, PostgreSQL is loopback-only, the repository is aes-256-cbc encrypted with this instance's passphrase, and a backup/verify/drop/restore round trip returned the known row.
Step 7: Restore
A restore replaces the PostgreSQL data directory from the repository, so stop PostgreSQL first. --delta reuses the files that are already correct, which makes the restore much faster:
sudo systemctl stop postgresql@18-main.service
sudo -u postgres pgbackrest --stanza=main --delta restore
sudo systemctl start postgresql@18-main.service
That restores the latest backup and replays all archived WAL. To go back to a point in time instead, add a target and promote when it is reached:
sudo -u postgres pgbackrest --stanza=main --delta \
--type=time "--target=2026-11-07 21:30:00+00" --target-action=promote restore
Use --set=<backup-label> (a label from info) to restore a particular backup. After a point-in-time restore PostgreSQL starts a new timeline; pgBackRest keeps archiving on it, and you can confirm with pgbackrest --stanza=main check.
Step 8: Scheduled backups
systemctl list-timers 'pgbackrest-*' --no-pager
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sat 2026-11-07 21:32:13 UTC 20min - - pgbackrest-incr.timer pgbackrest-incr.service
Sun 2026-11-08 01:02:58 UTC 3h 51min - - pgbackrest-full.timer pgbackrest-full.service
Sun 2026-11-08 03:01:01 UTC 5h 49min - - pgbackrest-verify.timer pgbackrest-verify.service
Mon 2026-11-09 01:04:59 UTC 1 day 3h - - pgbackrest-diff.timer pgbackrest-diff.service
4 timers listed.
| Timer | Schedule | Runs |
|---|---|---|
pgbackrest-full.timer |
Sunday 01:00 UTC | pgbackrest --stanza=main --type=full backup |
pgbackrest-diff.timer |
Monday to Saturday 01:00 UTC | --type=diff backup |
pgbackrest-incr.timer |
Every hour at :30 | --type=incr backup |
pgbackrest-verify.timer |
Sunday 03:00 UTC | pgbackrest --stanza=main verify |
Each timer waits up to five minutes at random and catches up after downtime (Persistent=true). See the result of a run with journalctl -u pgbackrest-incr.service. To change a schedule, run sudo systemctl edit pgbackrest-full.timer, set a new OnCalendar= under [Timer] (add an empty OnCalendar= line first to clear the old one) and run sudo systemctl daemon-reload.
Step 9: Check the security controls
Every PostgreSQL and backup unit carries the metadata block, only postgres has a password, and PostgreSQL accepts only local and loopback logins:
systemctl show -p IPAddressDeny postgresql@18-main.service pgbackrest-firstboot.service pgbackrest-incr.service
sudo -u postgres psql -tAc "SELECT rolname, rolsuper, rolpassword IS NOT NULL AS has_password FROM pg_authid WHERE rolcanlogin ORDER BY 1;"
sudo grep -vE '^[[:space:]]*(#|$)' /etc/postgresql/18/main/pg_hba.conf
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
postgres|t|t
local all postgres peer
local all all scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
Step 10: Connect to PostgreSQL
As the postgres system user you need no password:
sudo -u postgres psql -c "SELECT current_setting('server_version') AS server_version, current_setting('archive_mode') AS archive_mode;"
server_version | archive_mode
----------------------------------+--------------
18.6 (Ubuntu 18.6-1.pgdg24.04+2) | on
(1 row)
Applications on the instance connect to 127.0.0.1:5432 with POSTGRES_PASSWORD. Put it in ~/.pgpass (mode 0600) rather than on a command line, for example 127.0.0.1:5432:*:postgres:<POSTGRES_PASSWORD>, then psql -h 127.0.0.1 -U postgres. From your workstation, use an SSH tunnel: ssh -N -L 5432:127.0.0.1:5432 ubuntu@<public-ip>, then connect your client to 127.0.0.1:5432.
To serve clients on your VPC instead, set listen_addresses in /etc/postgresql/18/main/conf.d/10-cloudimg.conf, add a hostssl ... scram-sha-256 line for your application subnet to pg_hba.conf, turn ssl on with your own certificate, restart PostgreSQL and open 5432 in the security group to that subnet only. Create a separate, non-superuser role for each application.
Maintenance
- Grow the repository. Enlarge the volume in the EC2 console (Volumes, Modify), then run
sudo resize2fson its device (findmnt -no SOURCE /var/lib/pgbackrest). No downtime is needed. - Off-instance copies. Take EBS snapshots of the repository volume, or add a second repository (
repo2-type=s3withrepo2-s3-bucket,repo2-s3-region,repo2-s3-endpoint,repo2-pathandrepo2-cipher-type=aes-256-cbcwith its ownrepo2-cipher-pass) so backups also land in Amazon S3. The image blocks the metadata service for PostgreSQL and the backup services, so they cannot use the instance's IAM role: give pgBackRest a dedicated IAM user's access key withrepo2-s3-keyandrepo2-s3-key-secretin/etc/pgbackrest/pgbackrest.conf(0640, root:postgres), limited to that bucket. Then runsudo -u postgres pgbackrest --stanza=main stanza-createand a full backup. - Back up another PostgreSQL server. pgBackRest can back up a remote cluster over SSH (
pg1-host,pg1-host-user) or its TLS server. Both need network access and keys you configure yourself; see the pgBackRest user guide at pgbackrest.org. - Repository passphrase. The passphrase is fixed for the life of a repository: backups written with it can be read only with it. To change it, create a new repository (or empty this one), set the new
repo1-cipher-pass, runstanza-createand take a new full backup. - Updates.
sudo apt update && sudo apt upgradeinstalls pgBackRest and PostgreSQL 18 minor releases from PGDG and Ubuntu security updates; unattended security upgrades are on. Restart PostgreSQL after a PostgreSQL update. - Logs.
/var/log/pgbackrest/(one log per command),/var/log/postgresql/postgresql-18-main.log, andjournalctl -u pgbackrest-firstboot.servicefor first boot.
What cloudimg changed from the upstream defaults
- pgBackRest and PostgreSQL come from the PGDG apt repository with its signing key pinned to the published fingerprint (
B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8) in its own keyring; no key is added to apt's global trust. - The repository is encrypted (
repo1-cipher-type=aes-256-cbc) with a passphrase generated on your instance, and lives on its own EBS volume (upstream default: unencrypted, under/var/lib/pgbackreston the root disk). - PostgreSQL ships a fresh cluster with no build-time passwords, listens on localhost only, uses
scram-sha-256and archives WAL into pgBackRest; the first full backup is taken and verified on first boot. - The package's sample
/etc/pgbackrest.conf(a[demo]stanza) is replaced by a one-line pointer to/etc/pgbackrest/pgbackrest.conf. - The
fwupd-refreshtimer is masked: EC2 instances have no firmware that the Linux Vendor Firmware Service can update.
Support
cloudimg supports this image by email at support@cloudimg.co.uk. Include the instance ID, the version (pgBackRest 2.60.0 on Ubuntu 24.04) and what you were doing.
pgBackRest is developed and maintained by Crunchy Data. PostgreSQL is a trademark of the PostgreSQL Community Association of Canada. Ubuntu is a trademark of Canonical Ltd. cloudimg is not affiliated with or endorsed by these organisations.