Deployment guide

pgBackRest 2.60.0

Overview

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.

pgBackRest is a single command-line program on its own, so this image ships it as a complete, working PostgreSQL backup host. It combines PostgreSQL 18.6 with WAL archiving into pgBackRest, an encrypted repository on a dedicated volume and backup timers. A first full backup is taken and verified before you log in.

This guide covers launching and verifying pgBackRest 2.60.0 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened Adminer & Database Admin and Backup Tools AMIs listing on AWS Marketplace, and 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.

What you get:

  • 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
  • pgBackRest and PostgreSQL both installed 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.
  • Defaults: default user ubuntu, x86_64 architecture, and volumes of 16 GiB gp3 root plus 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. 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, and 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. Its access rules allow only local and 127.0.0.1 logins with scram-sha-256, and 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 restores the passphrase-free configuration, 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, with no passwords and 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 size the repository volume for your retention.

Connecting to your instance

The pgBackRest 2.60.0 on Ubuntu 24.04 version runs Ubuntu 24.04 LTS. Connect over SSH as the ubuntu user.

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened Adminer & Database Admin and Backup Tools AMIs listing on AWS Marketplace. 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.large recommended) and your key pair.
  • Configure the security group to allow SSH (22) from your own IP address only. Do not open 5432, because PostgreSQL listens on localhost.
  • Keep the second (64 GiB) volume. It holds the backup repository, and you can make it larger now or later.
  • Choose your VPC and subnet and leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.

Option B: AWS CLI

Run the following on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and replace 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, because 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.

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 and checks WAL archiving. It then takes a full backup, verifies it and writes the credentials file.

Show the connection details. The command below leaves out the two secret lines. 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://secureanycloud.com/stacks/pgbackrest-2-60-0/

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.

3

Confirm pgBackRest and PostgreSQL are running

Check the pgBackRest and PostgreSQL versions, the service state, the listening ports and the repository volume:

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 and 127.0.0.54 is the operating system's local DNS stub. The repository volume is mounted at /var/lib/pgbackrest.

4

Inspect the repository

pgBackRest runs as the postgres system user. The info command 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 that first boot took is already there, and the WAL range shows that archiving is working. The check command 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. Confirm it:

sudo head -c 8 /var/lib/pgbackrest/repo/backup/main/backup.info; echo
Salted__
5

Take a backup

The timers take backups on schedule, and you can take one at any time. Set --type to 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). After each backup, older backups and the WAL they need are expired automatically.

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 that a wrong one is refused. It confirms that the repository cannot be read with a wrong passphrase. It writes a known row, takes and verifies an incremental backup, drops the row, restores PostgreSQL from the repository and checks that the row came back.

PostgreSQL is stopped for a few seconds during the restore. Run the self-test 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.
7

Restore

A restore replaces the PostgreSQL data directory from the repository, so stop PostgreSQL first. The --delta option 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

This 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

To restore a particular backup, use --set=<backup-label> with a label from info. After a point-in-time restore, PostgreSQL starts a new timeline and pgBackRest keeps archiving on it. Confirm this with pgbackrest --stanza=main check.

8

Scheduled backups

List the backup timers:

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.
  • pgbackrest-full.timer: Sunday 01:00 UTC, runs pgbackrest --stanza=main --type=full backup
  • pgbackrest-diff.timer: Monday to Saturday 01:00 UTC, runs --type=diff backup
  • pgbackrest-incr.timer: every hour at :30, runs --type=incr backup
  • pgbackrest-verify.timer: Sunday 03:00 UTC, runs pgbackrest --stanza=main verify

Each timer waits up to five minutes at random and catches up after downtime (Persistent=true). To see the result of a run, use journalctl -u pgbackrest-incr.service.

To change a schedule, run sudo systemctl edit pgbackrest-full.timer. Set a new OnCalendar= under [Timer], adding an empty OnCalendar= line first to clear the old one. Then run sudo systemctl daemon-reload.

9

Check the security controls

Run the checks below to confirm three things. Every PostgreSQL and backup unit carries the metadata block. Only postgres has a password. 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
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 the password in ~/.pgpass (mode 0600) rather than on a command line, for example 127.0.0.1:5432:*:postgres:<POSTGRES_PASSWORD>, then run psql -h 127.0.0.1 -U postgres.

From your workstation, use an SSH tunnel with 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, make these changes:

  • Set listen_addresses in /etc/postgresql/18/main/conf.d/10-sac.conf.
  • Add a hostssl ... scram-sha-256 line for your application subnet to pg_hba.conf.
  • Turn ssl on with your own certificate, then restart PostgreSQL.
  • 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 so backups also land in Amazon S3. Configure it with repo2-type=s3, repo2-s3-bucket, repo2-s3-region, repo2-s3-endpoint, repo2-path and repo2-cipher-type=aes-256-cbc with its own repo2-cipher-pass. 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 instead, limited to that bucket, with repo2-s3-key and repo2-s3-key-secret in /etc/pgbackrest/pgbackrest.conf (0640, root:postgres). Then run sudo -u postgres pgbackrest --stanza=main stanza-create and take a full backup.
  • Back up another PostgreSQL server. pgBackRest can back up a remote cluster over SSH (pg1-host, pg1-host-user) or through its TLS server. Both need network access and keys that you configure yourself. See the pgBackRest user guide at pgbackrest.org.
  • Repository passphrase. The passphrase is fixed for the life of a repository, and 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, plus Ubuntu security updates. Unattended security upgrades are on. Restart PostgreSQL after a PostgreSQL update.
  • Logs. pgBackRest writes one log per command to /var/log/pgbackrest/. PostgreSQL logs to /var/log/postgresql/postgresql-18-main.log. For first boot, use journalctl -u pgbackrest-firstboot.service.

What SecureAnyCloud 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 it lives on its own EBS volume. The upstream default is unencrypted, under /var/lib/pgbackrest on the root disk.
  • PostgreSQL ships a fresh cluster with no build-time passwords. It 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, because EC2 instances have no firmware that the Linux Vendor Firmware Service can update.

Support

SecureAnyCloud supports this image by email at sales@secureanycloud.com. Include the instance ID, the version (pgBackRest 2.60.0 on Ubuntu 24.04) and a description of 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. SecureAnyCloud is not affiliated with or endorsed by these organisations.

Deploy on AWS

Launch pgBackRest 2.60.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS