WAL-G 3.0.9
Overview
WAL-G is the open-source archival and restoration tool for PostgreSQL. It takes compressed, encrypted base backups (wal-g backup-push) and archives every WAL segment as PostgreSQL writes it (wal-g wal-push, run by archive_command). It restores a backup plus the WAL needed to replay to any point after it (wal-g backup-fetch and wal-g wal-fetch), and it writes to Amazon S3 and S3-compatible storage, or to a directory.
This guide covers launching and verifying WAL-G 3.0.9 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. You pick it as the software version when you subscribe and launch. The guide hub for the listing describes the other tools.
SecureAnyCloud builds WAL-G 3.0.9 from the upstream v3.0.9 tag (commit 3e493188db28) with Go 1.26.10. Brotli compression and libsodium encryption are compiled in, and LZO is left out. Six Go dependencies are raised to patched releases within their own major versions, so that govulncheck reports no reachable vulnerability. The scan reports ship in the image. PostgreSQL 18.6 comes from the official PostgreSQL (PGDG) apt repository, with the repository's signing key pinned.
WAL archiving is off when the instance starts. The image ships no bucket, no credential and no storage setting. It makes no outbound connection for backups until you choose your storage in /etc/wal-g/wal-g.env and run sudo walg-enable.
That one command checks the storage, switches on continuous archiving, takes the first base backup and enables the daily backup and weekly retention timers.
What you get:
- PostgreSQL 18.6 (
postgresql@18-main.service) listening on 127.0.0.1:5432 only, withwal_level = replicaand archiving off - WAL-G 3.0.9 (
/usr/local/bin/wal-g, a wrapper that loads/etc/wal-g/wal-g.env). Its licence, the licences of the libraries built into it, its Go module list and thegovulncheckreports are in/usr/local/share/doc/wal-g/ walg-enable, which switches archiving on (or off with--disable) and shows the state with--statuswalg-backup.timer(daily base backup, 01:00) andwalg-delete.timer(weekly retention keeping 7 full backups, Sunday 03:00), both disabled untilwalg-enablewalg-role-credentials.timer, which fetches the IAM role credentials of the instance for S3 every 15 minutes and is disabled untilwalg-enable --instance-role- A libsodium encryption key and a
postgrespassword, both generated on the first boot of your instance - An egress backstop (
walg-egress-guard.service) that controls where thepostgressystem user may connect, with an allow-list for storage inside your VPC - A self-test,
sudo /usr/local/sbin/walg-selftest.sh, that verifies a backup, an archive, the encryption and a point-in-time restore in a throwaway cluster
Default user: ubuntu. Architecture: x86_64. Root volume: 8 GiB gp3.
Security posture
- No default password and no shared key. The image ships with no password on any PostgreSQL role and no encryption key. First boot generates a unique
postgrespassword and a unique 32-byte libsodium key. It writes both to/root/wal-g-credentials.txt, readable by root only. The key is also in/etc/wal-g/libsodium.key(mode 0640, root:postgres) for WAL-G to read, and in no other file. - Encrypted before it leaves the instance. Every base backup and WAL segment is encrypted with your instance's key before it is written to storage. Keep a copy of the key off the instance. Without it, nothing this instance archived can be restored.
- No credentials in the image. No bucket, access key or role is configured. For S3 you either attach an IAM role to the instance and run
walg-enable --instance-role, or put an access key in/etc/wal-g/wal-g.env(mode 0640, root:postgres). - No metadata access for the database. systemd blocks PostgreSQL (and so
archive_command), the backup and retention services and first boot from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). A separate root-only service fetches the instance-role credentials and hands them to WAL-G in a file on memory-backed storage (/run/sac-walg/role-credentials.env, mode 0640). Amazon Time Sync (169.254.169.123) still works. - Egress backstop for the database. The
postgressystem user (PostgreSQL and every WAL-G command) cannot connect to instance metadata, link-local or reserved ranges, IPv6, other loopback ports, or any address inside your VPC except the hosts you add to/etc/sac/wal-g-egress-allow.conf(Step 7). Public endpoints, including Amazon S3 and an S3 gateway endpoint, stay reachable. - PostgreSQL is local only. It listens on 127.0.0.1. The
pg_hba.conffile acceptsscram-sha-256(or peer authentication for thepostgressystem user on the local socket) and has notrust,md5orpasswordrule. The security group of the listing opens SSH 22 only. - WAL-G opens no port. WAL-G is a command-line tool and its services run once and exit. It never listens on a socket.
- 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, it stops PostgreSQL rather than leave a half-configured server, and it tries again on the next boot. The backup services refuse to run (exit 78, not retried) until storage is configured. They retry only a temporary failure, such as PostgreSQL restarting.
- No secrets in logs. The password is set over standard input with statement logging switched off, and no credential is passed on a command line, so none appears in the PostgreSQL log or the journal.
- 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
Before you launch, make sure you have:
- 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
- Somewhere to keep backups: an S3 bucket (and permission to create an IAM role or access key for it), or a directory such as a separate EBS volume
Recommended instance type: m5.large. Size the instance and its EBS volume for your PostgreSQL workload.
Connecting to your instance
Version Operating system SSH user
WAL-G 3.0.9 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu
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.
- When 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 WAL-G 3.0.9 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largeis recommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Check the security group. The default group of the listing allows SSH (22) only. Restrict it to your own IP address.
- To back up to S3 with an IAM role, choose the instance profile of the role under Advanced details > IAM instance profile. Step 5 shows the policy. You can also attach the role 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 these commands on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair and CIDR with your own. Leave out --iam-instance-profile if you will use an access key or a directory:
SG=$(aws ec2 create-security-group --group-name wal-g-host \
--description "WAL-G PostgreSQL host (SSH only)" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-ip>/32
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 \
--iam-instance-profile Name=walg-backup-role \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wal-g-host}]'You do not need --metadata-options, because the AMI already requires IMDSv2.
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 sets the postgres password, creates the encryption key and writes the credentials file.
Show the file without the two secret lines:
sudo grep -vE '^(POSTGRES_PASSWORD|WALG_LIBSODIUM_KEY)=' /root/wal-g-credentials.txt# WAL-G 3.0.9 + PostgreSQL 18 - generated on first boot by wal-g-firstboot.service.
# These values are unique to this instance. Store them somewhere safe, OFF this instance: without
# WALG_LIBSODIUM_KEY nothing this instance archives can ever be restored. POSTGRES_HOST=127.0.0.1 POSTGRES_PORT=5432 POSTGRES_USER=postgres WALG_LIBSODIUM_KEY_FILE=/etc/wal-g/libsodium.key WALG_LIBSODIUM_KEY_TRANSFORM=hex
# WAL archiving is OFF until you point /etc/wal-g/wal-g.env at your storage (S3 or a directory) and run:
#
sudo walg-enable (add --instance-role to use this instance's IAM role for S3) # Status:
sudo walg-enable --status
# Self-test:
sudo /usr/local/sbin/walg-selftest.sh
# User guide: https://secureanycloud.com/stacks/wal-g-3-0-9/Read the two secret lines with sudo cat /root/wal-g-credentials.txt. Store them in your secrets manager, off this instance.
Confirm PostgreSQL and the default state
Check that PostgreSQL is running and first boot has finished. The backup timers are installed but not enabled:
systemctl is-active postgresql@18-main.service walg-egress-guard.service wal-g-firstboot.service
systemctl show -p UnitFileState --value walg-backup.timer walg-delete.timer walg-role-credentials.timeractive active active disabled disabled disabledCheck the versions and the WAL-G build. The binary records how it was built:
sudo -u postgres psql -tAc 'SELECT version()'
sudo -u postgres wal-g --version grep -P 'build\t(-tags|GOEXPERIMENT|CGO_ENABLED)' /usr/local/share/doc/wal-g/buildinfo.txtPostgreSQL 18.6 (Ubuntu 18.6-1.pgdg24.04+2) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, 64-bit wal-g version v3.0.9 3e49318 2026.11.09_21:35:26 PostgreSQL build -tags=brotli,libsodium build CGO_ENABLED=1 build GOEXPERIMENT=jsonv2Run walg-enable --status. It shows that archiving is off and that WAL-G is not configured yet. Exit code 78 means "not configured":
sudo walg-enable --statuswalg-enable: archive_mode=off archive_command=none walg-enable: pg_stat_archiver: archived=0 failed=0 last=- walg-enable: storage: none (no WALG_*_PREFIX set in /etc/wal-g/wal-g.env) walg-enable: walg-backup.timer: disabled / inactive walg-enable: walg-delete.timer: disabled / inactive walg-enable: walg-role-credentials.timer: disabled / inactive walg-run: NOT CONFIGURED: /etc/wal-g/wal-g.env must set exactly one WALG_*_PREFIX (it sets 0); see the user guide walg-enable: walg-run check: exit 78 (0 = configured, 78 = not configured, 75 = transient)Run the self-test
Run the self-test. It verifies the appliance end to end without touching your server or your storage.
The test checks the credentials file and the key. It logs in to PostgreSQL with the right password and confirms that a wrong one is refused. Then, in a throwaway PostgreSQL 18 cluster, it takes a base backup, writes a row after it and archives the WAL. It confirms that the archive holds no plaintext and that a wrong key cannot decrypt it, then restores both rows with your key:
sudo /usr/local/sbin/walg-selftest.sh== WAL-G self-test == OK: credentials file 0600 root; this instance's libsodium key is in /etc/wal-g/libsodium.key (0640 root:postgres) and nowhere else in the configuration OK: PostgreSQL listens on 127.0.0.1:5432 only; the per-VM password logs in, a wrong one is refused, the right one still works OK: archiving on this server: off == WAL-G round trip in a throwaway cluster (this instance's key; its own directory storage) == OK: scratch root /run/sac-walg-selftest.IwjxYu is root-owned 0711; the runners and the wrong key were written as uid postgres (owner postgres) inside the postgres-owned /run/sac-walg-selftest.IwjxYu/pg OK: backup-push, then a row written after the backup; WAL through 000000010000000000000003 archived by wal-push OK: the archive holds no plaintext; a wrong key cannot decrypt a segment, this instance's key can OK: the scratch clusters' postgresql.conf appends ran as uid postgres (root wrote nothing inside /run/sac-walg-selftest.IwjxYu/pg) OK: backup-fetch + WAL replay restored the row from the backup AND the row written after it OK: scratch tree /run/sac-walg-selftest.IwjxYu removed WALG_SELFTEST_OK: per-VM postgres password logs in on 127.0.0.1 only and a wrong one is refused; WAL-G backed up, archived, refused a wrong key and restored to a point after the backup with this instance's key.Choose where backups go
Edit /etc/wal-g/wal-g.env and uncomment exactly one storage prefix with its settings. The file already points WAL-G at the key of your instance and at the local PostgreSQL:
sudo nano /etc/wal-g/wal-g.envOption A: Amazon S3 with the instance's IAM role (recommended)
Create an IAM role for EC2 with a policy limited to your bucket and prefix. Attach it to the instance with Actions > Security > Modify IAM role in the EC2 console. For example, for the bucket my-walg-bucket and the prefix pg/host1:
{ "Version": "2012-10-17", "Statement": [ {"Effect": "Allow", "Action": ["s3:ListBucket", "s3:GetBucketLocation"], "Resource": "arn:aws:s3:::my-walg-bucket", "Condition": {"StringLike": {"s3:prefix": ["pg/host1/*"]}}}, {"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::my-walg-bucket/pg/host1/*"} ] }In /etc/wal-g/wal-g.env, uncomment and set the bucket and its Region:
WALG_S3_PREFIX=s3://my-walg-bucket/pg/host1 AWS_REGION=eu-west-2Then switch archiving on with the instance role. walg-enable fetches the credentials of the role (as root, over IMDSv2) and enables their 15-minute refresh. It then checks the bucket, turns on archiving, takes the first base backup and enables the timers:
sudo walg-enable --instance-roleOption B: Amazon S3 with an access key
Create an IAM user with the same policy and an access key. Put the key in /etc/wal-g/wal-g.env with the bucket. Only root and the postgres group can read the file:
WALG_S3_PREFIX=s3://my-walg-bucket/pg/host1 AWS_REGION=eu-west-2 AWS_ACCESS_KEY_ID=<your-access-key-id> AWS_SECRET_ACCESS_KEY=<your-secret-access-key>sudo walg-enableOption C: A directory (for example a separate EBS volume)
Mount the volume and create a directory owned by postgres with mode 0700. Then point WAL-G at it:
sudo install -d -o postgres -g postgres -m 0700 /mnt/walg/pgWALG_FILE_PREFIX=/mnt/walg/pgsudo walg-enableA successful run ends like this (shown here with a directory):
walg-enable: checking the storage (wal-g backup-list as postgres) walg-enable: switching continuous WAL archiving on (PostgreSQL restarts once) walg-enable: WAL archiving works (000000010000000000000005 archived) walg-enable: taking the first base backup (walg-backup.service) walg-enable: base backups in the storage: 1 walg-enable: daily base backup (walg-backup.timer, 01:00) and weekly retention (walg-delete.timer, Sunday 03:00) enabled WALG_ENABLE_OKIf the storage cannot be reached, walg-enable stops before changing anything and prints the WAL-G error. If the first WAL segment does not arrive within three minutes, it switches archiving off again.
Check the backups and the schedule
Show the archiving status and the backup timers:
sudo walg-enable --status systemctl list-timers 'walg-*'Once storage is configured, --status also lists the newest base backups (sudo -u postgres wal-g backup-list shows them all):
walg-enable: archive_mode=on archive_command=wal-g walg-enable: pg_stat_archiver: archived=9 failed=0 last=000000010000000000000006.00000028.backup walg-enable: storage: WALG_FILE_PREFIX=/mnt/walg/pg walg-enable: walg-backup.timer: enabled / active walg-enable: walg-delete.timer: enabled / active walg-enable: walg-role-credentials.timer: disabled / inactive walg-enable: walg-run check: exit 0 (0 = configured, 78 = not configured, 75 = transient) INFO: 2026/11/09 21:41:51.551264 List backups from storages: [default] backup_name modified wal_file_name storage_name base_000000010000000000000006 2026-11-09T21:41:50Z 000000010000000000000006 default NEXT LEFT LAST PASSED UNIT ACTIVATES Tue 2026-11-10 01:09:29 UTC 3h 27min Mon 2026-11-09 21:38:00 UTC - walg-backup.timer walg-backup.service Sun 2026-11-15 03:11:17 UTC 5 days Mon 2026-11-09 21:38:00 UTC - walg-delete.timer walg-delete.service 2 timers listed.To run a base backup at any time, run sudo systemctl start walg-backup.service. Its log is in journalctl -u walg-backup.service.
The services behave as follows:
- Exit 78, with no retry, while storage is not configured
- Retry after two minutes (exit 75) if PostgreSQL or the instance-role credentials are briefly unavailable
- Exit 1, with no retry, when WAL-G itself fails. The WAL-G exit code is in the journal
Storage inside your VPC (S3 interface endpoint or MinIO)
Public S3 endpoints, including an S3 gateway endpoint, need no change. If WAL-G must reach a private address in your VPC, such as an S3 interface endpoint or a MinIO server, add that address to the egress allow-list. Then reload the guard. PostgreSQL keeps running:
echo '10.0.12.34/32 # S3 interface endpoint' | sudo tee -a /etc/sac/wal-g-egress-allow.conf
sudo systemctl reload walg-egress-guardAlso set the endpoint in /etc/wal-g/wal-g.env, for example AWS_ENDPOINT=https://bucket.vpce-0123-abcd.s3.eu-west-2.vpce.amazonaws.com or your MinIO URL. For MinIO, add AWS_S3_FORCE_PATH_STYLE=true.
Restore
A restore needs the backups in your storage and the libsodium key of the instance that made them. Restore onto a fresh instance of this image, or onto any PostgreSQL 18 host with WAL-G 3.0.9.
- Put the original key in place and point
wal-g.envat the same storage, for read-only use only. Do not runwalg-enableon the restore host while it shares the prefix of the original. In the editor, replace the single line of the file with theWALG_LIBSODIUM_KEYof the original instance. Pasting it keeps it off the command line and out of your shell history, and the file keeps its 0640 root:postgres mode:
bash sudo nano /etc/wal-g/libsodium.key sudo nano /etc/wal-g/wal-g.env # the same WALG_S3_PREFIX / WALG_FILE_PREFIX and credentials
- Stop PostgreSQL, move the empty data directory aside and fetch the latest base backup:
bash sudo systemctl stop postgresql@18-main.service sudo mv /var/lib/postgresql/18/main /var/lib/postgresql/18/main.empty sudo -u postgres install -d -m 0700 /var/lib/postgresql/18/main sudo -u postgres wal-g backup-fetch /var/lib/postgresql/18/main LATEST
- Tell PostgreSQL to replay the archived WAL, then start it. To stop at a point in time instead of the end of the archive, add
recovery_target_time = '2026-11-09 12:00:00+00':
bash printf "restore_command = '/usr/local/bin/wal-g wal-fetch %%f %%p'\nrecovery_target_action = 'promote'\n" \ | sudo tee /etc/postgresql/18/main/conf.d/30-restore.conf >/dev/null sudo -u postgres touch /var/lib/postgresql/18/main/recovery.signal sudo systemctl start postgresql@18-main.service
- When
sudo -u postgres psql -tAc 'SELECT pg_is_in_recovery()'returnsf, the restore is complete. Remove30-restore.conf. Then point the restored server at a new storage prefix before you runsudo walg-enable. The restored cluster keeps thepostgrespassword of the original.
Check the security controls
Confirm the password, listening ports, metadata restrictions and egress rules:
sudo -u postgres psql -tAc "SELECT rolname FROM pg_authid WHERE rolpassword IS NOT NULL"
sudo ss -Hltn systemctl show -p IPAddressDeny postgresql@18-main.service walg-backup.service
sudo nft list table inet sac_walg_egress | grep -c rejectpostgres LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* 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 200 127.0.0.1:5432 0.0.0.0:* LISTEN 0 4096 [::]:22 [::]:* IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 5The output confirms four things:
- Only
postgreshas a password, the one set on first boot - Off the box, only SSH listens
- PostgreSQL and the backup service are blocked from the metadata service
- The refusal rules of the egress backstop are loaded
Switching archiving off
To stop archiving, run:
sudo walg-enable --disableThis switches archiving off, disables the timers and removes the instance-role credentials file. It leaves wal-g.env and the backups in your storage as they are.
Maintenance
- Keep the key safe. Copy
WALG_LIBSODIUM_KEYfrom/root/wal-g-credentials.txtto your secrets manager. Do not change/etc/wal-g/libsodium.keywhile backups made with it are still needed. - Change the postgres password. Run
sudo -u postgres psql, then\password postgres. It prompts for the password and never puts it on a command line. Update/root/wal-g-credentials.txtand your secrets manager. - Retention.
walg-delete.timerrunswal-g delete retain FULL 7 --confirmweekly. To keep a different number, runsudo systemctl edit walg-delete.serviceand overrideExecStartwith your ownwal-g deletecommand. - WAL-G settings. Add any WAL-G setting to
/etc/wal-g/wal-g.env, for exampleWALG_DELTA_MAX_STEPS,WALG_UPLOAD_CONCURRENCYorWALG_S3_STORAGE_CLASS. Every WAL-G command andarchive_commandreads the file. - PostgreSQL configuration. The settings from SecureAnyCloud are in /etc/postgresql/18/main/conf.d/10-sac.conf, and walg-enable adds 20-sac-walg-archive.conf. Root owns the configuration directories, and PostgreSQL only reads them. To reach PostgreSQL from other hosts, change listen_addresses, add a hostssl ... scram-sha-256 rule to pg_hba.conf and configure TLS. Then open 5432 in the security group to those hosts only.
- Updates. Running sudo apt update && sudo apt upgrade installs PostgreSQL 18 minor releases from PGDG and Ubuntu security updates. WAL-G is built into the image. SecureAnyCloud publishes a new version of this listing when WAL-G or one of its libraries needs an update.
- Logs. Read
journalctl -u walg-backup.service -u walg-delete.service -u walg-role-credentials.service,/var/log/postgresql/postgresql-18-main.log(archive_command failures appear there) andjournalctl -u wal-g-firstboot.servicefor first boot.
What SecureAnyCloud changed from the upstream defaults
- WAL-G is built from the v3.0.9 tag with Brotli and libsodium compiled in and without LZO, and six Go dependencies are raised to patched releases within their major versions. The module list of the build and the
govulncheckreports ship in/usr/local/share/doc/wal-g/. - PostgreSQL comes from PGDG, with the repository's signing key pinned to the published PostgreSQL fingerprint. It listens on 127.0.0.1 only and is ready for archiving with
wal_level = replica. - Archiving, the backup and retention timers and the instance-role credential refresh are installed but off.
walg-enableturns them on after it has checked your storage. - The
postgrespassword and the libsodium encryption key are generated on the first boot of your instance. The image contains neither. - The
postgressystem user is confined by an nftables egress backstop with an allow-list, and PostgreSQL, the backup services and first boot cannot reach the instance metadata service.
Support
SecureAnyCloud supports this image by email at sales@secureanycloud.com. Include the instance ID, the version (WAL-G 3.0.9 on Ubuntu 24.04) and what you were doing when the problem occurred.
WAL-G is developed by the WAL-G contributors and is licensed under the Apache License 2.0. PostgreSQL is a trademark of the PostgreSQL Community Association of Canada. Amazon S3 and AWS are trademarks of Amazon.com, Inc. or its affiliates. Ubuntu is a trademark of Canonical Ltd. SecureAnyCloud is not affiliated with or endorsed by these organisations.
Deploy on AWS
Launch WAL-G 3.0.9 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