Back Up a Linux Server to a Remote Borg Repository over SSH

Build an encrypted, deduplicated backup path to another Linux host you control so one VPS failure does not turn into total data loss.

BorgSSH backupsOpen-source first
Illustrated guide cover for Back Up a Linux Server to a Remote Borg Repository over SSH
Borg • SSH backups • Open-source first
What you learn

How to initialize a remote Borg repository, create archives over SSH, list and inspect snapshots, verify restores, and keep the SSH side conservative.

Best for

Operators who want off-host backups they can host and audit themselves instead of depending only on provider snapshots or sync-copy habits.

Risk to watch

Backing up files that are actively changing, especially databases and container volumes, can give you a backup that exists but restores badly.

Before you begin

  • One Linux source host and one separate Linux backup host.
  • SSH access between them.
  • Borg installed on both sides.
  • A clear plan for application-consistent data such as database dumps or paused container data.

Borg is strong here because it combines encryption, deduplication, and SSH transport without forcing you into a proprietary backup appliance. Borg's quick start and remote repository docs are enough to build a conservative one-host-to-one-host workflow that stays understandable.

Warning: Borg does not magically make live-changing app data consistent. Borg's own docs explicitly warn that changing files can produce inconsistent backups. Dump databases or pause writes when the data demands it.

Step 1: Plan the backup and repository layout

Keep the first design simple:

  • Source host: the VPS you want to protect.
  • Backup host: a second Linux system with enough storage.
  • Repository path: one dedicated directory such as /srv/borg/repos/app1.
  • Remote user: a dedicated account such as borg.

Borg repositories are not just zip files. They are structured stores of archives and deduplicated data chunks. That means one repository can hold many snapshots, but it also means you should keep ownership and access patterns simple and consistent.

Step 2: Prepare the remote Borg host

On the backup host, create a dedicated user and repository path:

sudo adduser --disabled-password --gecos "" borg
sudo mkdir -p /srv/borg/repos/app1
sudo chown -R borg:borg /srv/borg

Install Borg if it is not already present on both machines. Then verify the source host can reach the backup host over SSH:

ssh borg@backup.example.com
hostname
exit

Borg's docs recommend using the same remote user account consistently for the repository so you do not create permission confusion later.

Step 3: Initialize the repository

From the source host, initialize the repository over SSH. Borg's quick start shows the basic pattern with borg init:

export BORG_RSH="ssh"
export BORG_REPO="borg@backup.example.com:/srv/borg/repos/app1"

borg init --encryption=repokey "$BORG_REPO"

Choose a strong passphrase and store it somewhere protected. If you lose the repository passphrase, you may lose the backup set.

After initialization, check that the repository responds:

borg list "$BORG_REPO"

An empty result is fine at this stage. It means the repository exists and is reachable.

Step 4: Create the first backup archive

Pick a small but meaningful first archive. For example, application config, web content, and a database dump directory:

sudo mkdir -p /var/backups/app1
sudo pg_dump -Fc myappdb > /var/backups/app1/myappdb.dump

borg create --stats \
  "$BORG_REPO"::"{hostname}-{now:%Y-%m-%d-%H%M}" \
  /etc \
  /srv/myapp \
  /var/backups/app1

Borg's docs stress that changed files can be inconsistent. That is why this example uses a logical database dump instead of pointing Borg at a live PostgreSQL data directory. Apply the same thinking to MariaDB, Redis persistence, or container volume data that cannot tolerate dirty snapshots.

List the created archives:

borg list "$BORG_REPO"

Step 5: Verify archives and test a restore

A backup is not trustworthy just because it completed. Inspect the archive and restore a sample into a temporary directory:

borg list "$BORG_REPO"::"{hostname}-{now:%Y-%m-%d-%H%M}"
mkdir -p ~/borg-restore-test
cd ~/borg-restore-test
borg extract "$BORG_REPO"::"{hostname}-{now:%Y-%m-%d-%H%M}" etc srv/myapp var/backups/app1

Then inspect the restored files. For a database dump, confirm the dump file exists and is not empty. For application config, open the restored files and make sure permissions and contents look sane.

If you later prune old archives, remember Borg's quick start note: deleting an archive does not immediately free disk space until you compact the repository.

Step 6: Tighten SSH access conservatively

Borg's serve docs support a forced-command pattern in authorized_keys. A conservative example on the backup host is:

command="borg serve --restrict-to-path /srv/borg/repos",restrict ssh-ed25519 AAAA... backup-key

This pattern limits the key to Borg service access under the allowed path and avoids turning the backup account into a general remote shell. Keep the first restriction simple. Do not get clever with a maze of custom wrappers until the basic flow is working reliably.

Rollback and recovery notes

If the first backup run fails, keep the repo and fix the basics one layer at a time:

  • Confirm plain SSH first.
  • Confirm the backup user owns the repository path.
  • Confirm Borg exists on both machines.
  • Confirm the source data is readable by the user running Borg or pre-stage the data with dumps.

If a stale lock is involved after an interrupted run, pause before forcing lock-break actions. Investigate whether another backup is still active before you consider manual lock cleanup.

Troubleshooting common Borg mistakes

The archive completed, but restore contents are not trustworthy.
You probably backed up live-changing data directly. Fix the consistency plan first, then back up again.

SSH works, but Borg repo access fails.
Check the remote path, ownership, and any forced-command restrictions in authorized_keys.

You are running out of space on the backup host.
Borg's docs explicitly call out free-space planning. Monitor repository disk usage and do not wait until the filesystem is full.

You deleted an archive but disk space did not drop.
That is expected. Borg requires a compaction step before deleted archive data stops occupying repository storage.

What to do next: Once the remote Borg path works, the next improvement is scheduling and monitoring it so failures do not stay silent. Pair this guide with How to Schedule Backups and Maintenance with systemd Timers.