Cryptographic Deduplicated Backups: BorgBackup, Borgmatic, Append-Only SSH & 3-2-1-1-0 Recovery
Executive Summary: Relying on RAID arrays or unencrypted
rsyncscripts leaves infrastructure vulnerable to ransomware encryption, silent filesystem bit-rot, and accidentalrm -rfpropagation. This architectural guide details the deployment of BorgBackup orchestrated via Borgmatic, implementing content-defined Buzhash chunk deduplication, authenticated AEAD AES-256-OCB / BLAKE2b client-side encryption, atomic pre-backup PostgreSQL/MariaDB database dumps, and Append-Only (--append-only) SSHauthorized_keysisolation to guarantee immutable ransomware-proof off-site recovery.
-
Buzhash Variable-Size Deduplication
Split files into content-defined rolling-hash chunks so moving, renaming, or appending to multi-gigabyte VM images only transmits changed byte blocks.
-
repokey-blake2 AEAD Encryption
Encrypt all data chunks, filenames, and metadata client-side before a single byte leaves the host, leaving the remote storage server zero-knowledge.
-
Immutable Append-Only SSH Target
Restrict the backup client's SSH key via
command="borg serve --append-only"so even a root-compromised server cannot delete or overwrite historical archives. -
Declarative Borgmatic Pipelines
Automate streaming
pg_dump/mariadb-dumphooks, GFS (keep_daily/keep_monthly) retention pruning, and cryptographicborg checkverification in one YAML file.
1. Architecture: Buzhash Chunking, Client-Side AEAD & Append-Only Storage
When BorgBackup processes a directory tree, it never trusts timestamps alone for new files: it streams data through a Buzhash rolling window, splitting streams into variable-length chunks (between $64\text{ KiB}$ and $4\text{ MiB}$). Each chunk is hashed using a keyed HMAC/BLAKE2b MAC, compressed (zstd), and encrypted client-side before being transmitted over SSH to the remote repository:
%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
subgraph SourceHost["Production Host (Borgmatic + Borg Client)"]
DBHook["Borgmatic Pre-Hook<br/>Streaming pg_dump / MariaDB"]
Buzhash["Buzhash Rolling Chunker<br/>Deduplication Index"]
Crypto["zstd-6 Compression +<br/>repokey-blake2 Encryption"]
end
subgraph UntrustedSSH["SSH Transport (Port 22)"]
SSHStream["Encrypted SSH Pipe<br/>Zero Server-Side Plaintext"]
end
subgraph BackupVault["Off-Site Storage Vault (Borg Server)"]
AuthKeys["SSH authorized_keys<br/>borg serve --append-only"]
Repo[("Immutable Borg Repository<br/>Transaction Log Segments")]
end
DBHook -->|"Stdout Pipe"| Buzhash
Buzhash -->|"Unique Chunks Only"| Crypto
Crypto --> SSHStream
SSHStream --> AuthKeys
AuthKeys -->|"Write New Segment Only"| Repo
Why Append-Only Mode Defeats Ransomware
If an attacker gains root access on your production server and inspects /root/.ssh and /etc/borgmatic/config.yaml, they obtain the SSH private key and repository passphrase. On a standard SSH backup server, the attacker can immediately run borg delete ssh://backup-vault/./repo or rm -rf, destroying all backups before deploying ransomware.
When the remote backup server enforces command="borg serve --append-only --restrict-to-path /srv/borg/prod-node" in ~/.ssh/authorized_keys, the remote borg serve process ignores or converts every delete and prune RPC into a non-destructive transaction log entry. All historical segments remain intact on disk.
2. Hardening the Remote Backup Server (--append-only)
On your off-site backup server (or Hetzner Storage Box / ZFS NAS), create a dedicated borg user and restrict the production server's Ed25519 SSH public key inside /srv/borg/.ssh/authorized_keys:
command="borg serve --append-only --restrict-to-path /srv/borg/repos/prod-node-01",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... root@prod-node-01
Initialize the repository from the production host using repokey-blake2 (optimal on 64-bit CPUs without AES-NI bottlenecks, or repokey for AES-256-CTR + HMAC-SHA256) and immediately export an offline paper/password-manager copy of the key:
export BORG_PASSPHRASE="Your-High-Entropy-64-Char-Passphrase"
# 1. Initialize encrypted repository over SSH
borg init --encryption=repokey-blake2 ssh://borg@vault.nejat-hakan.de:22/srv/borg/repos/prod-node-01
# 2. MANDATORY: Export the encrypted key material and store it inside Vaultwarden / printed paper!
borg key export ssh://borg@vault.nejat-hakan.de:22/srv/borg/repos/prod-node-01 ~/borg-prod-node-01-key-backup.txt
borg key export --paper ssh://borg@vault.nejat-hakan.de:22/srv/borg/repos/prod-node-01 ~/borg-prod-node-01-paper.txt
Without BOTH the Key File and the Passphrase, Recovery is Impossible
In repokey and repokey-blake2 modes, the encrypted chunk-encryption key is stored inside the repository config file. However, if the repository header is corrupted, or if you use keyfile-blake2, losing the exported key means permanent cryptographic loss of all backups—even if you know the passphrase. Always store borg key export output in a separate physical location (3-2-1-1-0 rule).
3. Declarative Production Pipeline with Borgmatic (/etc/borgmatic/config.yaml)
Install borgbackup and borgmatic (sudo apt install borgbackup borgmatic) and define your complete backup, database-dump, retention, consistency check, and Uptime Kuma monitoring pipeline in /etc/borgmatic/config.yaml (chmod 0600):
source_directories:
- /etc
- /opt
- /var/vmail
- /home
repositories:
- path: ssh://borg@vault.nejat-hakan.de:22/srv/borg/repos/prod-node-01
label: offsite_vault
- path: /mnt/local_zfs_backup/borg/prod-node-01
label: local_zfs
exclude_patterns:
- "*.pyc"
- "*/.cache/*"
- "/var/lib/docker/overlay2/*"
- "/opt/*/data/logs/*"
exclude_caches: true
exclude_if_present:
- .nobackup
# --- Compression & Encryption ---
compression: auto,zstd,6
encryption_passcommand: "cat /etc/borgmatic/.borg_passphrase"
archive_name_format: "{hostname}-{now:%Y-%m-%dT%H:%M:%S}"
ssh_command: "ssh -i /root/.ssh/id_ed25519_borg -o StrictHostKeyChecking=accept-new"
# --- Grandfather-Father-Son (GFS) Retention Policy ---
keep_hourly: 24
keep_daily: 14
keep_weekly: 8
keep_monthly: 12
keep_yearly: 3
# --- Cryptographic Repository & Archive Verification ---
checks:
- name: repository
frequency: 1 week
- name: archives
frequency: 2 weeks
- name: data
frequency: 1 month
max_duration: 3600
# --- Atomic Streaming Database Dumps (Zero Temporary Disk Overhead) ---
postgresql_databases:
- name: all
hostname: 127.0.0.1
port: 5432
username: postgres
format: custom
mariadb_databases:
- name: all
hostname: 127.0.0.1
username: root
password: "${MARIADB_ROOT_PASSWORD}"
options: "--single-transaction --quick"
# --- Dead-Man's-Switch Uptime Kuma Heartbeat Integration ---
uptime_kuma:
push_url: "https://status.nejat-hakan.de/api/push/x9Km2pL8qR"
states:
- start
- finish
- fail
4. Verification, Live FUSE Mounting & Bare-Metal Restore Drills
A backup is only valid once a restore drill has been verified. Borg allows mounting any historical archive as a read-only userspace FUSE filesystem (borg mount) to inspect or copy individual files, or extracting specific database dumps:
# 1. Validate Borgmatic configuration syntax
sudo borgmatic config validate
# 2. Execute full backup + database dump + prune + check cycle with verbose telemetry
sudo borgmatic --verbosity 1 --stats
# 3. List all historical archives and inspect deduplicated size
sudo borgmatic list
sudo borgmatic info --archive latest
# 4. Mount the latest archive as a read-only FUSE filesystem for granular file recovery
sudo mkdir -p /mnt/borg-restore
sudo borgmatic mount --archive latest --mount-point /mnt/borg-restore
ls -la /mnt/borg-restore/etc
sudo borgmatic umount --mount-point /mnt/borg-restore