Databases AWS

pgBackRest 2.60.0 on AWS User Guide

| Product: pgBackRest 2.60.0 on Ubuntu 24.04 on AWS

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.conf with one stanza, main
  • PostgreSQL 18.6 (postgresql@18-main.service, PostgreSQL Licence) on 127.0.0.1:5432 only, with archive_mode = on and archive_command = 'pgbackrest --stanza=main archive-push %p'
  • The repository /var/lib/pgbackrest/repo on 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:5432 only and its access rules allow only local and 127.0.0.1 logins with scram-sha-256 (the postgres system 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

  1. An AWS account and permission to launch EC2 instances and create security groups
  2. An EC2 key pair in the Region you launch in
  3. 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

  1. 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.
  2. Once the subscription is active, choose Launch your software.
  3. 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.
  4. Pick an instance type (m5.large recommended) and your key pair.
  5. Security group: allow SSH (22) from your own IP address only. Do not open 5432: PostgreSQL listens on localhost.
  6. Keep the second (64 GiB) volume: it holds the backup repository. You can make it larger now or later.
  7. 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 resize2fs on 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=s3 with repo2-s3-bucket, repo2-s3-region, repo2-s3-endpoint, repo2-path and repo2-cipher-type=aes-256-cbc with its own repo2-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 with repo2-s3-key and repo2-s3-key-secret in /etc/pgbackrest/pgbackrest.conf (0640, root:postgres), limited to that bucket. Then run sudo -u postgres pgbackrest --stanza=main stanza-create and 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, run stanza-create and take a new full backup.
  • Updates. sudo apt update && sudo apt upgrade installs 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, and journalctl -u pgbackrest-firstboot.service for 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/pgbackrest on the root disk).
  • PostgreSQL ships a fresh cluster with no build-time passwords, listens on localhost only, uses scram-sha-256 and 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-refresh timer 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.