Backup Configuration
The pgEdge Ansible Collection uses PgBackRest for backup management. The
following parameters control how backup functionality behaves. The
install_backrest, setup_backrest, finalize_backrest and recover_postgres
roles use these variables.
backup_repo_type
- Type: String
- Default:
ssh - Options:
ssh,s3 - Description: This parameter specifies the PgBackRest repository type.
Using
sshrequires a dedicated backup server in thebackuphost group. Usings3stores backups in AWS S3 or compatible object storage, and needs no backup server: a zone that usess3must not have a host in thebackupgroup, andinit_serverrefuses one that does.
In the following example, the inventory specifies SSH-based backups:
backup_repo_type: ssh
backup_host
- Type: String
- Default: (none - first node in the
backupgroup in the same zone) - Description: This parameter specifies the hostname of the backup server when
using SSH mode. When empty, the collection selects the first node in the
backuphost group that shares the same zone as the Postgres node.
In the following example, the inventory specifies the backup server hostname:
backup_host: backup1.example.com
backup_repo_user
- Type: String
- Default: the account Ansible logs in as —
ansible_userwhere the inventory orremote_usernames one, falling back to theansible_user_idfact where neither does - Description: This parameter specifies the OS user that owns the PgBackRest repository on the backup server in SSH mode.
In the following example, the inventory specifies the repository owner:
backup_repo_user: backrest
backup_repo_path
- Type: String
- Default: the home directory of the account Ansible logs in as, such as
/home/ansible - Description: This parameter specifies the full path to the PgBackRest
repository storage location. For S3 repositories, use a simple path such
as
/backrest.
Setting backup_repo_user does not move the path
The two default independently: backup_repo_user names the account that
owns the repository, and this names where the repository lives. Giving the
repository a dedicated owner without also setting this parameter leaves it
in the login user's home directory. Set both together.
In the following example, the inventory specifies a custom repository path:
backup_repo_path: /backup/pgbackrest
backup_user
- Type: String
- Default:
backrest - Description: This parameter specifies the PostgreSQL username for backup
operations. The collection creates this user with the
REPLICATIONattribute, and grants itpg_checkpointmembership on Postgres 15 and later.
backup_password
- Type: String
- Default:
secret - Description: This parameter specifies the password for the backup database user.
In the following example, the inventory retrieves the password from Ansible Vault:
backup_password: "{{ vault_backup_password }}"
backup_repo_cipher_type
- Type: String
- Default:
aes-256-cbc - Options:
aes-256-cbc,none - Description: This parameter specifies the encryption algorithm PgBackRest
applies to the repository. Set it to
nonewhere the storage layer encrypts instead.
backup_repo_cipher
- Type: String
- Default: (none — you must set it)
- Description: This parameter specifies the password PgBackRest encrypts the
repository with. It is required whenever
backup_repo_cipher_typeis notnone, and rejected when it is.
backup_repo_cipher: "{{ vault_backup_cipher }}"
Store it, and do not change it
A lost cipher is an unreadable repository, and PgBackRest cannot rotate it. Changing this on a cluster with existing backups makes those backups unreadable.
Why there is no default
This parameter cannot be defaulted into existence. A generated value would have to satisfy two requirements at once that nothing can meet together:
- Identical on every run. A repository whose cipher changed between runs is a repository that can no longer be read, so the value cannot be random.
- Not guessable. Anyone who can read the repository and knows the password can read the backups.
Earlier releases resolved that by deriving the value from cluster_name and
zone, which met the first requirement by abandoning the second: both are
public, so the password was reproducible by anyone who knew the cluster's name.
Only a secret you keep can be both, so init_server now requires one.
Relying on storage-layer encryption instead
Encrypting at the storage layer is a legitimate arrangement, and the only one that permits key rotation — PgBackRest cannot rotate its own cipher, whereas S3 bucket default encryption and SSE-KMS can rotate underneath an unchanged repository. To use it, turn PgBackRest's own encryption off and supply no password:
backup_repo_type: s3
backup_repo_cipher_type: none
The repository configuration then carries repo1-cipher-type=none and no
password line at all, which is what PgBackRest requires — it rejects a
configuration that names a password while encrypting nothing.
Upgrading a cluster deployed before this was required
Its repository is encrypted with the old derived password, and that derivation
was public, so the value can be recovered and then set explicitly. Substitute
the cluster's own cluster_name and zone:
ansible localhost -m debug \
-a "msg={{ lookup('password', '/dev/null', length=20, seed='pgedgedemo-1') }}"
The seed is the literal string pgedge, the cluster name, a hyphen, and the
zone — so pgedge + demo + - + 1 for a cluster named demo in zone 1.
Each zone has its own repository and therefore its own password. Put the
results in Ansible Vault and set backup_repo_cipher from there. A run that
gives a zone the wrong password stops in setup_backrest, before it replaces a
pgbackrest.conf that can still read that zone's repository.
Treat those values as compromised
A password anyone could derive is not a secret. Recovering it is how you keep reading the existing repository, not a state to stay in. Plan to re-encrypt: take a fresh full backup under a new password into a new repository, or move to storage-layer encryption, and retire the old repository once its retention has passed.
full_backup_count
- Type: Integer
- Default:
1 - Description: This parameter specifies the number of full backups to retain in the repository.
diff_backup_count
- Type: Integer
- Default:
6 - Description: This parameter specifies the number of differential backups to retain in the repository.
full_backup_schedule
- Type: String (cron format)
- Default:
10 0 * * 0(Sundays at 00:10, host time) - Description: This parameter specifies the cron schedule for automated full backups.
In the following example, the inventory schedules full backups for Sunday at 2:00 AM:
full_backup_schedule: "0 2 * * 0"
diff_backup_schedule
- Type: String (cron format)
- Default:
10 0 * * 1-6(Monday through Saturday at 00:10, host time) - Description: This parameter specifies the cron schedule for automated differential backups.
In the following example, the inventory schedules differential backups for Monday through Saturday at 2:00 AM:
diff_backup_schedule: "0 2 * * 1-6"
Turning off scheduled backups
To leave the scheduling to something else, set either schedule to an empty string. The collection installs a cron entry only for a schedule with exactly five fields, so an empty value installs no entry:
full_backup_schedule: ""
diff_backup_schedule: ""
This has no effect on the first full backup the deployment takes; only the recurring jobs are left out.
An existing entry is left in place
An empty schedule tells the collection not to install an entry; it does
not remove one. A host deployed earlier with a schedule keeps its
Full PgBackRest schedule and Differential PgBackRest schedule entries
in the crontab of the user that runs PgBackRest, so remove them yourself
with crontab -e.
backup_repo_params
- Type: Dictionary
- Default: See below.
- Description: This parameter provides configuration for S3 backup
repositories. You must specify this parameter when you set
backup_repo_typetos3. The dictionary is merged over the defaults below, so an inventory names only the keys it changes.init_serverrefuses an S3 configuration with an emptyaccess_key,secret_key,bucket,regionorendpoint.
The backup_repo_params dictionary accepts the following keys with the
defaults shown:
backup_repo_params:
region: us-east-1
endpoint: s3.amazonaws.com
bucket: pgbackrest
access_key: ''
secret_key: ''
uri_style: ''
storage_ca_file: ''
storage_port: ''
storage_verify_tls: ''
uri_stylesets PgBackRest'srepo1-s3-uri-style:hostaddresses the bucket asbucket.endpoint, andpathasendpoint/bucket. Left empty, the setting is omitted and PgBackRest useshost, which is what AWS S3 expects. S3-compatible stores such as MinIO usually needpath.storage_ca_filesets PgBackRest'srepo1-storage-ca-file, the certificate authority that verifies the endpoint's TLS certificate. Left empty, the setting is omitted and PgBackRest uses the system CA bundle. Set it when the endpoint's certificate comes from a private authority. It is a path on the pgEdge nodes, and the collection does not copy the file there.storage_portsets PgBackRest'srepo1-storage-port. Left empty, the setting is omitted and PgBackRest connects on 443. MinIO listens on 9000 by default.storage_verify_tlssets PgBackRest'srepo1-storage-verify-tls, asyfortrueandnforfalse. Left empty, the setting is omitted and PgBackRest verifies the endpoint's certificate. Setting it tofalseaccepts any certificate at all, so reserve it for test environments and preferstorage_ca_filefor a privately signed certificate.
In the following example, the inventory configures S3 backup storage with credentials from Ansible Vault:
backup_repo_params:
region: us-west-2
endpoint: s3.amazonaws.com
bucket: my-pg-backups
access_key: "{{ vault_aws_access_key }}"
secret_key: "{{ vault_aws_secret_key }}"
In the following example, the inventory configures a MinIO server listening on its default port, whose certificate is signed by a private authority:
backup_repo_params:
endpoint: minio.example.com
bucket: pg-backups
access_key: "{{ vault_minio_access_key }}"
secret_key: "{{ vault_minio_secret_key }}"
uri_style: path
storage_port: 9000
storage_ca_file: /etc/pki/tls/certs/minio-ca.crt
patroni_replica_from_backup
- Type: Boolean
- Default:
false - Description: This parameter controls whether Patroni rebuilds a replica from
the backup repository instead of streaming a fresh
pg_basebackupfrom its zone's leader. When enabled, Patroni tries a PgBackRest delta restore first and falls back topg_basebackupif it fails. It takes effect only where a backup repository is configured; without one, replicas are built from the leader as before.
A delta restore moves only the blocks that changed and reads from the repository rather than from the leader, which is worth it for a large database. It also makes replica creation depend on the repository being healthy, so it is off by default; a cluster that leaves it unset builds replicas exactly as it did before.
The restore runs through /usr/local/bin/patroni_pgbackrest_replica, a script
setup_patroni installs. Before restoring, it asks the zone's leader for its
system identifier and timeline history and checks that the newest backup is of
the leader's cluster and ends on the leader's history. It fails when the backup
does not, and Patroni moves on to pg_basebackup. That happens after a
point-in-time recovery that stopped before the newest backup, whose
replicas would otherwise restore a backup they could never follow; once the
recovery is committed, the full backup it takes is restored again. It also
happens in a zone a recovery rebuilt, whose stanza holds only backups of the
cluster it replaced.
In the following example, the inventory rebuilds replicas from the repository:
patroni_replica_from_backup: true
Debian
Debian keeps postgresql.conf in /etc/postgresql/<version>/<cluster>
rather than in the data directory, and Patroni cannot start a replica
without it. A restore writes only the data directory, so on Debian
/usr/local/bin/patroni_pgbackrest_replica also creates the configuration
directory with pg_createcluster after the restore if it is empty. When
the restore fails or is refused, Debian falls back to pg_clonecluster,
which creates the directory and then runs pg_basebackup, rather than to a
bare pg_basebackup.
Recovery Parameters
The following parameters apply only to the
recover_postgres role, which the recovery
playbook described in Recovering a Cluster from Backup uses.
The first five are empty by default, and an ordinary deployment never reads
them.
Pass them on the ansible-playbook command line rather than writing them into
an inventory, where they would sit waiting for the next unrelated run.
| Parameter | Default | Description |
|---|---|---|
recovery_node |
(none) | The pgEdge node to restore from its repository, spelled as the inventory spells it. Must be the first node of its zone. |
recovery_target_type |
(none) | PgBackRest --type: time, xid, lsn, name or immediate. Unset restores everything the repository holds. |
recovery_target |
(none) | The value the target type stops at. Required for time, xid, lsn and name; must be unset for immediate or no type. |
recovery_target_timeline |
(none) | PgBackRest --target-timeline. Unset follows the newest timeline. |
recovery_backup_set |
(none) | A specific backup to restore, labelled as pgbackrest info labels it. Unset takes the newest backup, or leaves the choice to PgBackRest for a time, xid, lsn or name target. |
recovery_stall_minutes |
15 |
Give up only after the restored node has made no progress for this long. |
recovery_poll_seconds |
30 |
How often to look. |
recovery_max_hours |
24 |
Hard ceiling on the restore and the replay. |
Each zone's Spock copy of the restored zone has limits of its own,
pgedge_seed_stall_minutes and pgedge_seed_max_hours, which
Recovery Parameters and
Spock Configuration describe.
In the following example, the command restores a wiped cluster to a point in time:
ansible-playbook -i inventory.yaml playbook.yaml \
-e recovery_node=192.168.6.10 \
-e recovery_target_type=time \
-e "recovery_target='2026-09-15 14:30:00+00'"
Recovery never removes a backup
The recovery takes no backup and leaves the schedule off, so it can be
repeated until the result is right. Committing it with
commit-restore.yaml takes the backups it did not, and under the default
retention expires the ones it came from.