Linux Server Backups with restic and BorgBackup: A Practical Guide
restic and BorgBackup give Linux servers encrypted, deduplicated backups. This guide covers repositories, database dumps, append-only protection against ransomware, retention, scheduling, restore tests and monitoring.
For file-level backups of Linux servers, restic and BorgBackup are the two proven open-source choices. Both encrypt data on the server before it leaves, deduplicate so daily backups stay small, and keep many restore points. A good setup has five parts: an encrypted repository on a different machine or provider, database dumps taken right before each backup, a retention policy applied with forget/prune, a systemd timer that runs it unattended, and monitoring plus regular restore tests. Ideally, the server being backed up cannot delete its own backups.
This guide applies to current Debian 12/13, Ubuntu 24.04 and RHEL/AlmaLinux 9/10 servers. If you run virtual machines on Proxmox, image-level backups are covered in our Proxmox Backup Server guide; file-level backups complement them.
restic or Borg?
Both are mature and well documented. The practical differences:
| restic | BorgBackup | |
|---|---|---|
| Storage targets | Local, SFTP, REST server, S3-compatible, Azure, Google Cloud Storage, rclone | Local or SSH (Borg must be installed on the remote side) |
| Encryption | Always on (AES-256 with Poly1305 authentication) | Chosen at borg init; repokey with a strong passphrase is the documented default choice |
| Protection from a compromised client | rest-server --append-only |
borg serve --append-only via SSH forced command |
| Packaging | Single Go binary; in Debian, Ubuntu and EPEL | In Debian, Ubuntu and EPEL; this guide uses the stable 1.4 series |
Pick restic when you want object storage (S3-compatible buckets) or one binary everywhere. Pick Borg when you back up to your own SSH server and like its flexible compression options. Do not run both on the same data; pick one and do it well.
Whichever you choose, store the repository password or key somewhere other than the server, such as your password manager. Both projects are explicit: lose the password (and for Borg, the key) and the backups are unreadable. For Borg, export the key with borg key export and store it safely.
Step 1: Dump databases first
Copying the live files of a running PostgreSQL or MySQL/MariaDB database does not give you a consistent backup. Dump them first, then back up the dump.
## PostgreSQL: custom format, restorable with pg_restore
sudo -u postgres pg_dump -Fc appdb > /var/backups/db/appdb.dump
## MySQL / MariaDB (InnoDB): consistent dump without locking tables
mysqldump --single-transaction --routines --triggers appdb > /var/backups/db/appdb.sql
## On MariaDB the same tool is also called mariadb-dump
restic can also stream a dump directly into the repository, without a temporary file:
restic backup --stdin-filename appdb.sql \
--stdin-from-command -- mysqldump --single-transaction appdb
restic checks the command's exit code: if the dump fails, no snapshot is created and restic exits with an error, which is exactly what you want.
Step 2: Create the repository
Keep the repository off the server: another server in a different location, a backup appliance, or an S3-compatible bucket. A repository on the same disk protects against nothing but mistakes.
restic
Put the settings in a root-only environment file, /etc/restic/env (mode 600):
RESTIC_REPOSITORY=sftp:[email protected]:/srv/restic/web01
RESTIC_PASSWORD_FILE=/etc/restic/password
For S3-compatible storage, use these lines instead:
RESTIC_REPOSITORY=s3:https://s3.example-provider.com/company-backups/web01
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
sudo bash -c 'set -a; . /etc/restic/env; restic init'
SFTP requires key-based SSH login to the backup host, because automated backups cannot answer a password prompt.
Borg
export BORG_PASSCOMMAND="cat /etc/borg/passphrase"
borg init --encryption=repokey [email protected]:/srv/borg/web01
borg key export [email protected]:/srv/borg/web01 /root/web01-borg-key.txt
## Move that key file to your password manager, then delete it from the server
The Borg documentation recommends repokey with a strong passphrase as the general choice, and repokey-blake2 if the CPU lacks hardware-accelerated SHA-256.
Step 3: Make backups append-only
Ransomware and intruders increasingly go after backups. If the server can delete its own repository, an attacker who owns the server can too. Both tools support an append-only mode where the client can add backups but not delete or modify existing ones:
- restic: run the repository behind rest-server with
--append-only(and--private-reposso each server sees only its own repository). - Borg: on the backup server, restrict the client's SSH key in
~/.ssh/authorized_keys:
command="borg serve --append-only --restrict-to-path /srv/borg/web01",restrict ssh-ed25519 AAAA... web01-backup
The trade-off: pruning old backups must then happen from a separate, well-protected admin machine with full access, not from the client. Borg's documentation also warns that compaction does nothing in append-only mode and that a later prune in normal mode makes deletions permanent, so check the repository (borg check --verify-data) before pruning. Storage-side snapshots or versioning on the backup host, which the client cannot change, add another layer. This is the "immutable copy" idea behind the 3-2-1 backup strategy.

Step 4: Run backups with a systemd timer
Systemd timers are easier to monitor than cron jobs: every run is logged in the journal, and Persistent=true catches up on a run missed while the server was off.
/etc/systemd/system/restic-backup.service:
[Unit]
Description=restic backup
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStartPre=/usr/local/sbin/dump-databases.sh
ExecStart=/usr/bin/restic backup --exclude-file=/etc/restic/excludes /etc /home /srv /var/backups/db
Nice=10
IOSchedulingClass=idle
/etc/systemd/system/restic-backup.timer:
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 01:30:00
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer
The Borg equivalent calls borg create with an archive name such as '{hostname}-{now}' and --compression lz4, followed by borg prune and borg compact (when the repository is not append-only).
Step 5: Retention with forget and prune
Decide how far back you need to go, then let the tool enforce it. A common policy is 7 daily, 4 weekly and 12 monthly restore points.
## restic: preview first, then apply
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --dry-run
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
## Borg
borg prune --glob-archives '{hostname}-*' --keep-daily 7 --keep-weekly 4 --keep-monthly 12
borg compact
Two details from the official documentation matter here. In restic, forget only removes snapshot references; data is actually deleted by prune (or forget --prune), and pruning locks the repository, so schedule it away from backups. In Borg, the --glob-archives '{hostname}-*' filter keeps one server's prune from touching another server's archives in a shared repository.
Step 6: Check integrity and test restores
A backup you have never restored is a hope, not a backup.
## restic: verify structure, plus read a sample of the actual data
restic check --read-data-subset=5%
## restore one directory to a scratch location
restic restore latest --target /tmp/restore-test --include /etc
## Borg
borg check [email protected]:/srv/borg/web01
borg extract --dry-run [email protected]:/srv/borg/web01::web01-2026-10-27T01:30:00
Once a quarter, do a real restore drill: restore a database dump into a test server, start the application and confirm the data is current. Write down how long it took; that is your realistic recovery time.
Step 7: Monitor failures
Most backup failures are silent: an expired cloud credential, a full disk, a changed password. Watch for them:
- Exit codes. restic exits 0 on success, 1 on a fatal error (no snapshot) and 3 when some files could not be read (incomplete snapshot). Treat 3 as a warning to investigate, not as success.
- systemd status.
systemctl status restic-backup.serviceandjournalctl -u restic-backup.serviceshow the last run. AnOnFailure=unit can send an alert. - A "dead man's switch". Have the job report success to your monitoring system and alert when that report does not arrive, which also catches a timer that never ran.
- Snapshot age.
restic snapshots --latest 1orborg list --last 1should always show last night's date.
Backup checklist
- Repository on another machine or provider, encrypted, password stored off the server.
- Database dumps before every file backup.
- Append-only access for the client; pruning from a separate admin machine.
- Systemd timer with
Persistent=trueand failure alerts. - Retention enforced with
forget --pruneorprune+compact. - Monthly integrity checks and quarterly restore drills.
Want your server backups verified?
Core Florida IT designs and monitors backups for business servers across Miami-Dade and South Florida, with remote support nationwide, on cloud and dedicated servers and managed IT support plans. If you are not sure your last backup would actually restore, request a free IT assessment and we will test it with you.
Sources
- restic documentation: Backing up
- restic documentation: Preparing a new repository
- restic documentation: Removing backup snapshots
- restic rest-server (append-only mode)
- BorgBackup documentation: Quick Start
- BorgBackup documentation: Additional notes (append-only mode)
- BorgBackup documentation: borg init
- systemd.timer manual


