Skip to content

BorgBase: managed offsite target for BorgBackup repositories

How BorgBase fits into a BorgBackup strategy as the immutable offsite tier — SSH key provisioning, repo quotas, append-only enforcement, and the operational delta versus self-hosting.

BorgBase is a hosted, append-only target for BorgBackup repositories. It exists to solve one specific problem: most operators run a primary Borg repo on a local server, and they need a second copy somewhere they don’t run themselves so that the disaster scenarios (hardware loss, ransomware, insider threat) don’t take both copies at once. This article covers a working BorgBase wire-up, the differences from a self-hosted Borg server, and where it does and does not fit our managed engagements.

How to verify

After provisioning the repo and the SSH key on BorgBase, smoke-test from a client:

ssh-add -L | head
ssh -T -o StrictHostKeyChecking=accept-new <user>@<repo-id>.repo.borgbase.com
borg info ssh://<user>@<repo-id>.repo.borgbase.com/./repo
borg list ssh://<user>@<repo-id>.repo.borgbase.com/./repo | head

The ssh -T connection should print a Borg-specific greeting and immediately close — the BorgBase shell only runs borg serve. If you get a normal interactive shell, your key landed in the wrong account.

What’s happening

Conceptually, a BorgBase repo is the same thing as a self-hosted Borg server: an SSH endpoint that runs borg serve under a restricted user with a forced command. The difference is operational. BorgBase handles:

  • Per-repo storage (with quotas you set in the dashboard).
  • Per-repo append-only enforcement, with a separate “alert mode” that emails when delete operations are attempted.
  • SSH host-key publication, IPv6, geographic redundancy across their PoPs, and the basic service availability work you would otherwise own.
  • A two-factor-protected dashboard for adding/removing keys and rotating repository tokens.

What it does not do: it does not see your data (encryption keys stay on the client), it does not orchestrate backups (you still run borg create from cron or systemd-timer), and it does not replace your primary repo (it is meant as a second tier, not a single tier).

The architecture pattern we run: primary repo on the client’s own infrastructure (fast restores, local network), BorgBase as the offsite immutable copy, and the prune host configured to prune the local repo but not BorgBase — BorgBase keeps everything until its retention policy says otherwise. Two structurally different copies, two adversaries.

The procedure

  1. Provision a repository in the BorgBase dashboard. Pick a region close to your primary site; choose an alert quota that fires before the storage quota does.

  2. Generate a per-client SSH keypair on the client. The same convention as a self-hosted Borg target: dedicated key, no passphrase, restricted use.

    ssh-keygen -t ed25519 -N "" -f /root/.ssh/borgbase-acme -C "acme-prod-borgbase"
  3. Upload the public key in the BorgBase dashboard and attach it to the repository with append-only permission. The dashboard distinguishes between full-access keys (used by your prune host) and append-only keys (used by the actual clients). Most keys should be append-only.

  4. Initialize the repository from the client. Use the SSH URL the dashboard prints; the path component is always ./repo.

    borg init --encryption repokey-blake2 -e repokey-blake2 \
      ssh://[email protected]/./repo

    Export the repokey and store it offline. The whole point of BorgBase is that they cannot read your data; that property is gone the moment they could.

  5. Configure the daily backup as a systemd timer with the BorgBase URL as the target.

    # /etc/systemd/system/borg-backup.service
    [Service]
    Type=oneshot
    Environment=BORG_PASSPHRASE=<from-credential>
    Environment=BORG_RSH=ssh -i /root/.ssh/borgbase-acme
    ExecStart=/usr/bin/borg create --stats --compression zstd,6 \
      ssh://[email protected]/./repo::"{hostname}-{utcnow:%%Y%%m%%dT%%H%%M%%S}" \
      /etc /home /var/www /var/lib/postgresql
  6. Configure the prune host. This is a separate trusted machine (not the backed-up client) that holds a full-access BorgBase key. Its weekly cron runs:

    borg prune --list \
      --keep-daily 14 --keep-weekly 8 --keep-monthly 12 \
      ssh://[email protected]/./repo
    borg compact ssh://[email protected]/./repo
  7. Wire the BorgBase webhook into your alerting. The dashboard can POST when storage usage crosses a threshold or when a delete is rejected; those events should land in the same place as the rest of your platform alerts, not in a forgotten inbox.

Operational notes

  • BorgBase rate-limits aggressively under bandwidth abuse. The first full upload of a multi-TB dataset should be paced via borg create --upload-ratelimit to avoid throttle.
  • The repokey is your data. Storing it inside the same backup it protects creates a chicken-and-egg restore. Keep it in a separate vault and document the recovery procedure.
  • BorgBase is one company. Treat it like any other vendor — assume it could disappear and the second offsite copy should be reproducible against a different hosted Borg provider or your own server within a working day.
  • The “alert mode” only fires when a connected client attempts a destructive operation. It does not fire on missed backups; that monitoring you still own.
  • Network egress on restore is non-trivial — the entire deduplicated repo has to come back to the restore host. Plan a test restore over real bandwidth, not just a verify.

In the engagements we run, BorgBase is one of two common offsite patterns alongside an S3 bucket with object-lock. We pick it when the client’s policy demands a second backend vendor and we want SSH-only access semantics rather than IAM. The primary Borg repo, the BorgBase target, the prune host, and the monthly restore drill are all configured as code in the same repo as everything else we run. The broader operating model is at /en/services/managed-operations/.