User Tools

Site Tools


linux:borg

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

linux:borg [2026/08/20 07:56] – created 127.0.0.1linux:borg [2026/08/24 14:52] (current) – [Inspecting] odefta
Line 1: Line 1:
 +====== Borg Backup - essential commands ======
 +
 +Deduplicating, compressing, optionally encrypted backups. Verified against **borg 1.2.9**.
 +
 +Two things that trip everyone up on day one:
 +
 +  * Archive paths are stored **without the leading slash** — you extract ''%%etc/fstab%%'', not ''%%/etc/fstab%%''.
 +  * ''%%borg mount%%'' holds a **lock** on the repository. While mounted, ''%%borg create%%'' cannot run.
 +
 +===== Setup =====
 +
 +Point ''%%BORG_REPO%%'' at the repository once and every command gets shorter — ''%%::name%%'' is enough afterwards.
 +
 +<code bash>
 +export BORG_REPO=/mnt/backup/borg          # local
 +export BORG_REPO=ssh://user@host/./borg    # remote (./ = relative to home)
 +</code>
 +
 +Create the repository. The encryption mode is fixed **at creation** and cannot be changed later:
 +
 +<code bash>
 +borg init --encryption=repokey-blake2 ::   # key in repo, unlocked by passphrase
 +borg init --encryption=keyfile-blake2 ::   # key stays on this machine only
 +borg init --encryption=none ::             # no key, no passphrase
 +</code>
 +
 +<code bash>
 +borg key export :: borg-key.txt            # BACK THIS UP, off-machine
 +borg key export --paper ::                 # printable version
 +</code>
 +
 +> Losing the key or passphrase of an encrypted repository means losing the backup. There is no recovery.
 +
 +===== Creating archives =====
 +
 +<code bash>
 +borg create --stats --progress ::'{hostname}-{now}' /etc /home /srv
 +</code>
 +
 +Placeholders usable in archive names: ''%%{hostname}%%'', ''%%{user}%%'', ''%%{now}%%'', ''%%{utcnow}%%'', ''%%{pid}%%''.
 +
 +Realistic invocation:
 +
 +<code bash>
 +borg create                       \
 +    --one-file-system             \
 +    --compression zstd,         \
 +    --exclude-from /etc/borg.excl \
 +    --exclude-caches              \
 +    --stats                       \
 +    ::'{hostname}-{now}'          \
 +    / /boot /home /data
 +</code>
 +
 +Preview what would be archived, without writing anything:
 +
 +<code bash>
 +borg create --dry-run --list ::test / | head -50
 +</code>
 +
 +Back up the output of a command instead of a file:
 +
 +<code bash>
 +mysqldump --all-databases | borg create ::db-'{now}' -
 +</code>
 +
 +^ Flag ^ Why you want it ^
 +| ''%%--one-file-system%%'' | Do not cross mount points. Keeps the backup target itself out of the archive. |
 +| ''%%--exclude-caches%%'' | Skips directories tagged ''%%CACHEDIR.TAG%%''. |
 +| ''%%--exclude-if-present .nobackup%%'' | Opt-out marker you can drop anywhere. |
 +| ''%%--compression zstd,3%%'' | Good default. ''%%lz4%%'' = fastest, ''%%zstd,10%%'' = smaller/slower, ''%%none%%''. |
 +| ''%%--stats%%'' | Prints original / compressed / deduplicated sizes. |
 +| ''%%--checkpoint-interval 900%%'' | Resume point every 15 min on long runs. |
 +
 +===== Inspecting =====
 +
 +<code bash>
 +borg list                              # archives in the repository
 +borg list /path/to/repo::root-2016-02-15
 +borg list ::archive-name               # files inside one archive
 +borg list ::archive-name 'var/log/**'  # only matching paths
 +borg info ::archive-name               # size, duration, dedup stats
 +borg info                              # totals for the whole repository
 +</code>
 +
 +Custom output, useful for scripting:
 +
 +<code bash>
 +borg list --format '{archive}{TAB}{time}{NL}'
 +borg list ::arch --format '{size:8d} {path}{NL}'
 +borg list --json | jq -r '.archives[].name'
 +</code>
 +
 +What changed between two archives — the second one takes **no** repository prefix:
 +
 +<code bash>
 +borg diff ::monday tuesday
 +borg diff ::monday tuesday etc/
 +</code>
 +
 +===== Restoring =====
 +
 +Paths are relative and extraction writes into the **current directory**, so ''%%cd%%'' first.
 +
 +<code bash>
 +cd /tmp/restore
 +borg extract ::archive-name                        # everything
 +borg extract ::archive-name etc/nginx              # one subtree
 +borg extract ::archive-name 'home/*/.ssh'          # pattern
 +borg extract --dry-run --list ::archive-name etc/  # preview only
 +borg extract --strip-components 2 ::arch var/www/site
 +</code>
 +
 +Single file to stdout:
 +
 +<code bash>
 +borg extract --stdout ::archive-name etc/fstab
 +</code>
 +
 +Browse the archive like a normal folder — needs ''%%llfuse%%'', mounted read-only:
 +
 +<code bash>
 +borg mount ::archive-name /mnt/restore
 +borg mount :: /mnt/restore          # ALL archives, one subdirectory each
 +ls /mnt/restore
 +borg umount /mnt/restore            # do not forget - it holds a lock
 +</code>
 +
 +Export straight to tar, no intermediate extraction:
 +
 +<code bash>
 +borg export-tar ::archive-name backup.tar.gz --tar-filter="gzip"
 +borg export-tar ::archive-name - | tar tvf - | less
 +</code>
 +
 +===== Retention =====
 +
 +''%%prune%%'' deletes archives; ''%%compact%%'' is what actually frees disk space.
 +
 +<code bash>
 +borg prune --list --dry-run --keep-daily 7 --keep-weekly 4 --keep-monthly 12
 +borg prune --list           --keep-daily 7 --keep-weekly 4 --keep-monthly 12
 +borg compact
 +</code>
 +
 +Always dry-run first. Restrict to one host if several share the repository:
 +
 +<code bash>
 +borg prune --glob-archives 'web01-*' --keep-daily 7 --keep-monthly 6
 +</code>
 +
 +^ Rule ^ Meaning ^
 +| ''%%--keep-last N%%'' | Last N archives, whatever their age. |
 +| ''%%--keep-within 10d%%'' | Everything from the last 10 days. |
 +| ''%%--keep-daily / -weekly / -monthly / -yearly N%%'' | Newest archive of each period. |
 +
 +> An archive matched by **no** rule is deleted. Running prune with no keep rule at all deletes everything.
 +
 +===== Integrity =====
 +
 +<code bash>
 +borg check                    # structural: repository + archive metadata
 +borg check --verify-data      # re-reads and re-hashes every chunk (slow, thorough)
 +borg check --repository-only  # quick, repository layer only
 +</code>
 +
 +Rough guidance: structural weekly, ''%%--verify-data%%'' monthly. The second one is the only check that catches silent bit rot on disks with no SMART.
 +
 +<code bash>
 +borg check --repair           # last resort, can discard data
 +</code>
 +
 +===== Housekeeping and troubleshooting =====
 +
 +<code bash>
 +borg delete ::archive-name         # one archive
 +borg delete --glob-archives 'tmp-*' --dry-run --list ::
 +borg delete ::                     # the whole repository (asks for confirmation)
 +</code>
 +
 +<code bash>
 +borg break-lock ::                 # stale lock after a crash - check nothing runs first
 +borg delete --cache-only ::        # rebuild the local cache
 +borg with-lock :: cp -a repo /elsewhere
 +</code>
 +
 +Retroactively drop files from existing archives (rewrites them):
 +
 +<code bash>
 +borg recreate --list --dry-run --exclude '*/node_modules/*' ::
 +borg recreate --recompress --compression zstd,10 ::
 +</code>
 +
 +^ Message ^ Cause ^
 +| ''%%Failed to create/acquire the lock (timeout)%%'' | Another borg is running, or the repository is still mounted. |
 +| ''%%Cache is newer than repository%%'' | Repository was rolled back or restored. Run ''%%borg delete --cache-only%%''. |
 +| ''%%Repository ... does not exist%%'' | Wrong ''%%BORG_REPO%%'', or the ''%%::%%'' is missing. |
 +| ''%%repository version not supported%%'' | Borg 2.x cannot read a 1.x repository. Use borg 1.x or ''%%borg transfer%%''. |
 +
 +===== Environment variables =====
 +
 +^ Variable ^ Use ^
 +| ''%%BORG_REPO%%'' | Default repository, lets you write ''%%::archive%%''. |
 +| ''%%BORG_PASSPHRASE%%'' | Non-interactive runs. Prefer the next one. |
 +| ''%%BORG_PASSCOMMAND%%'' | e.g. ''%%cat /root/.borg-pass%%'' — keeps the secret out of the environment. |
 +| ''%%BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes%%'' | Skip the prompt for unencrypted repositories. |
 +| ''%%BORG_RELOCATED_REPO_ACCESS_IS_OK%%'' | Repository moved path. Leave it ''%%no%%'' unless you moved it yourself. |
 +| ''%%BORG_CACHE_DIR%%'' / ''%%BORG_CONFIG_DIR%%'' | Override ''%%~/.cache/borg%%'' and ''%%~/.config/borg%%''. |
 +
 +> Under **systemd**, ''%%HOME%%'' is not set unless you set it. Borg then builds a second files cache and re-reads everything from scratch on every run. Add ''%%Environment=HOME=/root%%'' to the unit.
 +
 +===== Cron / systemd skeleton =====
 +
 +<code bash>
 +#!/bin/bash
 +set -uo pipefail
 +export BORG_REPO=/mnt/backup/borg
 +export BORG_PASSCOMMAND='cat /root/.borg-pass'
 +
 +borg create --one-file-system --compression zstd,3 \
 +    --exclude-from /etc/borg.excl --stats \
 +    ::'{hostname}-{now}' / /home /data
 +rc=$?
 +
 +borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 12
 +borg compact
 +
 +# 0 = ok, 1 = warnings (archive is still valid), 2 = error
 +[ $rc -ge 2 ] && exit 1
 +exit 0
 +</code>
 +
 +Exit codes: **0** success, **1** warning (archive written and usable), **2** error.
 +
 +===== See also =====
 +
 +  * [[https://borgbackup.readthedocs.io/|Official documentation]]
 +  * [[linux:check-hdd-health|Check HDD health]]
  
linux/borg.txt · Last modified: by odefta