diff --git a/postgres/defaults/main.yml b/postgres/defaults/main.yml
index 1f70df5df2a9ee6163768a95c4350a4e71bfe20a..22c6be75d123128054a0929958348d8bb6114f8a 100644
--- a/postgres/defaults/main.yml
+++ b/postgres/defaults/main.yml
@@ -3,6 +3,12 @@
 postgres_pgdg_repo: false
 postgres_rsnapshot: false
 postgres_commvault_compat: false
+postgres_pgbackrest: false
+postgres_wal_archive: "{{ postgres_commvault_compat or postgres_pgbackrest }}"
+
+pgbackrest_retention_diff: 7
+pgbackrest_retention_full: 2
+pgbackrest_frequently_minute: "*/15"
 
 postgres_versions_debian:
   buster: "11"
diff --git a/postgres/files/wal-archive-commvault.sh b/postgres/files/wal-archive-commvault.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3aba082ec3bd6e209278c09c9633bdf0e7e63ae9
--- /dev/null
+++ b/postgres/files/wal-archive-commvault.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -e
+[ $# -lt 2 ] && exit 1
+cp --reflink=auto "$1" "/var/backups/pg_wal/$2"
diff --git a/postgres/files/wal-archive-pgbackrest.sh b/postgres/files/wal-archive-pgbackrest.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5f68615359a889a87168cd10459926cfaaebdea0
--- /dev/null
+++ b/postgres/files/wal-archive-pgbackrest.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -e
+[ $# -lt 1 ] && exit 1
+pgbackrest --stanza=main archive-push "$1"
diff --git a/postgres/tasks/main.yml b/postgres/tasks/main.yml
index 6ac01b7622612bc72a17b029a349dc65120d5973..4bc8c14f7ded1c81ce2b0fbf08eb3557413a402a 100644
--- a/postgres/tasks/main.yml
+++ b/postgres/tasks/main.yml
@@ -60,26 +60,52 @@
         path: /etc/cron.d/postgres-snapshot
         state: absent
 
-- name: Configure Commvault backup compatibility
-  when: postgres_commvault_compat
+- name: Configure Postgres WAL archive framework
+  when: postgres_wal_archive
   block:
-    - name: Create WAL backup directory
+    - name: Create WAL archiver drop-in directory
       file:
-        path: /var/backups/pg_wal
+        path: /etc/postgresql/wal-archive
         state: directory
-        owner: postgres
+        owner: root
         group: postgres
-        mode: '0750'
+        mode: "0750"
 
     - name: Configure Postgres WAL archive
       postgresql_set:
         name: "{{ item.name }}"
         value: "{{ item.value }}"
+      become: true
+      become_user: postgres
       loop:
         - name: archive_mode
           value: "on"
         - name: archive_command
-          value: 'cp %p /var/backups/pg_wal/%f'
+          # pgBackRest checks the Postgres configuration, archive_command
+          # _needs_ to contain the string "pgbackrest", so add it as (unused)
+          # argument
+          value: >-
+            run-parts --report --arg=%p --arg=%f --arg=pgbackrest
+            /etc/postgresql/wal-archive
+
+- name: Configure Commvault backup compatibility
+  when: postgres_commvault_compat
+  block:
+    - name: Create WAL backup directory
+      file:
+        path: /var/backups/pg_wal
+        state: directory
+        owner: postgres
+        group: postgres
+        mode: '0750'
+
+    - name: Configure Commvault Postgres WAL archive
+      copy:
+        src: wal-archive-commvault.sh
+        dest: /etc/postgresql/wal-archive/commvault
+        owner: root
+        group: postgres
+        mode: "0755"
 
     - name: Configure Postgres ident mappings
       blockinfile:
@@ -106,3 +132,7 @@
         options: map=postgres
       notify:
         - Restart Postgres
+
+- name: Configure pgBackRest
+  when: postgres_pgbackrest
+  import_tasks: pgbackrest.yml
diff --git a/postgres/tasks/pgbackrest.yml b/postgres/tasks/pgbackrest.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fb06c97a58b9452562c521e09c54fb7c908ac570
--- /dev/null
+++ b/postgres/tasks/pgbackrest.yml
@@ -0,0 +1,76 @@
+---
+
+- name: Install pgBackRest
+  apt:
+    name: pgbackrest
+    state: present
+
+- name: Create pgBackRest repository
+  file:
+    path: /var/backups/pgbackrest
+    state: directory
+    owner: postgres
+    group: postgres
+    mode: "0750"
+
+- name: Configure pgBackRest
+  template:
+    src: pgbackrest.conf
+    dest: /etc/pgbackrest.conf
+    owner: root
+    group: postgres
+    mode: "0640"
+
+- name: Initialise pgBackRest
+  command:
+    cmd: pgbackrest stanza-create --stanza=main
+    creates: /var/backups/pgbackrest/backup/main
+  become: true
+  become_user: postgres
+
+- name: Configure pgBackRest Postgres WAL archive
+  copy:
+    src: wal-archive-pgbackrest.sh
+    dest: /etc/postgresql/wal-archive/pgbackrest
+    owner: root
+    group: postgres
+    mode: "0750"
+
+- name: Configure pgBackRest cron env
+  cron:
+    cron_file: pgbackrest
+    user: postgres
+    env: true
+    name: SHELL
+    value: /bin/bash
+
+- name: Configure pgBackRest cron jobs
+  cron:
+    cron_file: pgbackrest
+    weekday: "{{ item.weekday }}"
+    hour: "{{ item.hour }}"
+    minute: "{{ item.minute }}"
+    name: pgbackrest-{{ item.name }}
+    user: postgres
+    job: >-
+      {% if item.sleep %}sleep $(($RANDOM \% 3600)) &&{% endif %}
+      pgbackrest backup --stanza=main --type={{ item.type }}
+  loop:
+    - name: frequently
+      weekday: "*"
+      hour: "*"
+      minute: "{{ pgbackrest_frequently_minute }}"
+      sleep: false
+      type: incr
+    - name: daily
+      weekday: 1-6
+      hour: 3
+      minute: 5
+      sleep: true
+      type: diff
+    - name: weekly
+      weekday: 0
+      hour: 3
+      minute: 5
+      sleep: true
+      type: full
diff --git a/postgres/templates/pgbackrest.conf b/postgres/templates/pgbackrest.conf
new file mode 100644
index 0000000000000000000000000000000000000000..a84affa4a2b862c0792127d4b7f4c63163fc5857
--- /dev/null
+++ b/postgres/templates/pgbackrest.conf
@@ -0,0 +1,7 @@
+[global]
+repo1-path=/var/backups/pgbackrest
+repo1-retention-diff={{ pgbackrest_retention_diff }}
+repo1-retention-full={{ pgbackrest_retention_full }}
+
+[main]
+db-path=/var/lib/postgresql/{{ postgres_version }}/main