Immutable Linux Backups in 2026: Restic vs Borg vs Kopia with S3 Object Lock

Restic, Borg, and Kopia compared for ransomware-resistant Linux backups in 2026, with S3 Object Lock, append-only SSH, and the 3-2-1-1-0 rule wired end-to-end.

Immutable Linux Backups: Restic vs Borg 2026

Updated: August 4, 2026

Immutable Linux backups are backups that no user, root or otherwise, can modify or delete before a retention period expires, enforced by the storage layer rather than a hopeful shell script. In 2026, the reason to care is pretty unromantic: ransomware crews target your backup repo before they touch production, because a wiped repo turns a bad Tuesday into a company-ending Tuesday. This guide is my honest, threat-model-first comparison of Restic 0.19, Borg 1.4/2.0, and Kopia 0.22 (the three tools I actually reach for), plus the S3 Object Lock, MinIO, and network-segmentation glue that makes them ransomware-resistant on Linux.

  • Immutability lives in the storage layer, not the backup client. Restic, Borg, and Kopia can all be made immutable, but only if the destination refuses deletes.
  • S3 Object Lock in Compliance mode (not Governance) is the only setting that survives a compromised admin account.
  • Borg --append-only plus an SSH forced-command is the cleanest way to get true append-only over a plain Linux host.
  • Restic 0.19.1 (July 2026) still isn't lock-free, so Object Lock works best via rest-server --append-only or a bucket with a separate pruning identity.
  • Kopia's repository server with server-side ACLs is the friendliest option for mixed fleets that want a UI and per-user policies.
  • The 3-2-1-1-0 rule adds two digits to the classic strategy: one immutable/offline copy, and zero errors verified by regular restore drills.

What are immutable Linux backups?

An immutable backup is a backup object that, once written, cannot be modified, overwritten, or deleted by any principal on the system for the duration of a retention window, enforced by the storage layer itself. The classical Unix version of this idea is chattr +i on ext4/XFS, which sets an immutable bit that even root cannot clear without first running chattr -i. The modern, ransomware-era version is Write-Once-Read-Many (WORM) object storage: S3 Object Lock, MinIO retention, Azure Blob immutable containers, and rest-server's append-only mode.

The distinction that matters for your threat model is where enforcement lives. If the backup client can call DELETE on the repo, then any credential leak from that client destroys your recovery path. So the flip side is obvious: if the storage layer refuses DELETE for retained objects regardless of caller identity, you have a real defense.

Honestly, immutability isn't a checkbox on your backup software. It's a property of the destination bucket, the SSH forced-command, or the append-only rest-server sitting in front of the disks. Everything else in this article is scaffolding to make that one property true and provable.

The tools I benchmark here (Restic 0.19.1, Borg 1.4.1 with 2.0 still in beta, and Kopia 0.22.x) all support this pattern on Linux, but each with a different set of gotchas that most tutorials paper over.

The 3-2-1-1-0 backup rule: what the new digits mean

The classical 3-2-1 rule (three copies of your data, on two different media, one offsite) was written for a world where the failure modes were disk death, fire, and typos. It assumed failures were accidental. Modern ransomware crews are adversarial: they enumerate your NAS, delete your shadow copies, dump your backup admin credentials from LSASS or a leaked .env, and then start encrypting. The 3-2-1-1-0 update adds two digits that address that shift.

The extra 1 is an immutable or air-gapped copy. Immutable means the storage layer refuses delete operations during retention. Air-gapped means the media is physically disconnected (a USB drive in a fire safe, an LTO tape in a vault). In practice, most Linux shops meet the "1" with S3 Object Lock in Compliance mode; belt-and-braces environments add tape or rotating USB on top.

The 0 is zero errors, which in operational terms means monitored backup jobs plus scheduled restore drills. A backup you've never restored is a hypothesis, not a backup.

I run a monthly cron on a scratch host that pulls a random snapshot from each repo, mounts it, checksums a known file, and pages me if the check fails. That is the "0." If you're running the first 60 minutes runbook after a Linux server compromise and reach for backups only to find they were never verified, well, you've discovered the rule the hard way.

Threat model: what breaks first when ransomware lands

Before comparing tools, it helps to sketch the blast radius in prose, because "immutable" means different things depending on which node the attacker owns. Picture three concentric zones.

In the innermost zone sits the production Linux host running your database or web app; it holds the backup client credentials because it needs them to push. In the middle zone sits the backup server (a MinIO instance, a Borg host, a rest-server) on a segmented subnet. In the outer zone sits the storage substrate: S3 in a different AWS account, an LTO library, a rotated USB shelf.

Ransomware almost always starts in the innermost zone. What breaks first is whatever the compromised client has permission to do. If your Restic credentials can call s3:DeleteObject, expect deletes within seconds. If your Borg client can SSH into the repo host and run arbitrary commands, expect borg delete. If your Kopia client has a repository password that also permits maintenance, expect a maintenance run that garbage-collects your snapshots.

So the design goal is a client that can only add. Every architecture in this article is a variation on that theme: append-only key on rsync.net, SSH forced-command on Borg, server-side ACL on Kopia, deny-s3:DeleteObject IAM on Restic. Anything you push into the middle zone can be poisoned. The outer zone (Object Lock, tape, USB) is the only tier that survives a determined attacker who also owns your admin laptop.

Restic vs Borg vs Kopia: 2026 comparison

All three tools are mature, open-source, actively maintained, and do the boring things (chunking, client-side encryption, dedup, snapshots) well enough that any of them will beat rolling your own rsync. The interesting differences show up when you push on immutability, multi-writer semantics, and how they handle a compromised client.

DimensionRestic 0.19.1Borg 1.4.1 (2.0-beta)Kopia 0.22.x
Language / distributionGo, single binaryPython + C, packaged binaryGo, single binary + web UI
Client-side encryptionAES-256-CTR + Poly1305AES-256-CTR + HMAC-SHA256AES-256-GCM or ChaCha20-Poly1305
Compressionzstd (default since 0.16)lz4/zstd/zlibzstd/s2/pgzip (per-policy)
Multi-writer to one repoYes (locking)No (exclusive access)Yes (server mode)
BackendsS3, B2, Azure, GCS, SFTP, REST, rclone (any)SSH, local diskS3, B2, Azure, GCS, WebDAV, SFTP, rclone
True append-only mechanismrest-server flag, or S3 Object Lock + separate prune identitySSH forced-command --append-onlyRepository server with server-side ACLs
Object Lock friendlinessPartial (not lock-free as of 0.19)N/A (not an S3-native tool)Good with S3 backend
Web UIThird-party (Backrest, Autorestic)None (borgmatic wraps CLI)Built-in

My honest read after wiring all three into production fleets: Borg is the tool I trust most for a small number of Linux hosts backing up over SSH, because the forced-command trick gives real append-only without any cloud-provider trust. Restic is what I reach for whenever the destination is object storage, because the backend list is wider than anyone else's, and 0.19's zstd default plus improved copy support makes cross-provider migrations painless. Kopia wins when I need a UI, per-user policies, or multi-writer semantics against a shared repo, and it's the fastest of the three on initial ingest thanks to parallel hashing.

How does S3 Object Lock work?

S3 Object Lock is the WORM primitive that AWS, Backblaze B2, Cloudflare R2, Wasabi, and MinIO all speak. It works by stamping a Retain Until date into each object's metadata at write time; the storage layer then rejects any DELETE or PUT that would remove or overwrite the object before that date. Object Lock requires S3 Versioning to be enabled. Without versioning, there is no concept of an immutable object identity to protect. On new buckets you can enable Object Lock at creation; on existing buckets, AWS now supports enabling it retroactively, but MinIO still requires the flag at bucket creation.

Two modes matter. Governance mode is a soft lock: any principal holding s3:BypassGovernanceRetention can delete a "locked" object. It exists for compliance workflows where a lawyer might need to override retention. It's useless against an attacker who has already compromised an admin account, because that attacker will happily grant themselves the bypass permission on their way out.

Compliance mode is the hard lock: no principal, not even the AWS account root user, can shorten retention or delete a retained object until the clock runs out. This is the setting I insist on for anything I call "ransomware-proof." See the AWS S3 Object Lock documentation for the full policy reference.

You can also apply a legal hold, which locks an object with no expiry until an authorized principal removes the hold. I use legal holds for one-off snapshots taken during active incident response.

How do you make Restic backups immutable?

Restic supports S3-compatible backends natively, so the mechanical path is: create a versioned bucket with Object Lock enabled in Compliance mode, set a default retention period, point Restic at it, and hand the backup client an IAM identity that is allowed to PutObject and GetObject but denied DeleteObject. The wrinkle (and it's a real wrinkle) is that Restic isn't yet lock-free as of 0.19.1. It writes and removes short-lived lock files in the repo during normal operation, so a bucket that refuses all deletes will eventually leave stale locks behind that block backups from running.

The two clean workarounds are: (a) run restic unlock and restic prune from a separate, hardened maintenance identity that is allowed to bypass or expire the locks, or (b) put a rest-server in front of the storage and give backup clients an append-only key. My preferred layout, borrowed from the Restic community's ransomware-protection thread, uses both.

Here's a minimal Restic backup client systemd timer against an S3 bucket with Object Lock. The credentials it uses can only add data:

# /etc/restic/env
export RESTIC_REPOSITORY="s3:https://s3.eu-central-1.amazonaws.com/acme-immutable-backups"
export RESTIC_PASSWORD_FILE="/etc/restic/password"
export AWS_ACCESS_KEY_ID="AKIA...append-only-key..."
export AWS_SECRET_ACCESS_KEY="..."

# /etc/systemd/system/restic-backup.service
[Unit]
Description=Nightly restic backup
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/local/bin/restic backup /srv /etc /home \
    --exclude-caches \
    --tag nightly \
    --host $(hostname -f)
# Notice: no `forget` or `prune` here. Retention is enforced by S3.
Nice=10
IOSchedulingClass=idle

The matching S3 bucket policy denies delete outright for the backup identity:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyDeletesForBackupClient",
      "Effect": "Deny",
      "Principal": { "AWS": "arn:aws:iam::111122223333:user/restic-nightly" },
      "Action": [
        "s3:DeleteObject",
        "s3:DeleteObjectVersion",
        "s3:PutBucketPolicy",
        "s3:PutBucketVersioning",
        "s3:PutObjectRetention"
      ],
      "Resource": [
        "arn:aws:s3:::acme-immutable-backups",
        "arn:aws:s3:::acme-immutable-backups/*"
      ]
    }
  ]
}

Because I still need to prune eventually (nobody wants an infinite-growth bucket), I run a separate weekly restic forget --prune from a hardened jumphost using a different IAM identity that is not reachable from any production node. That host stores its password in a Vault, not a plain file. If you haven't read the Linux secrets management guide covering Vault, SOPS, and systemd-creds, that's the pattern I use to keep the maintenance credential out of every attacker's reach.

Borg append-only with SSH forced-command

Borg's append-only story is arguably the cleanest in the entire ecosystem, because it doesn't depend on cloud APIs. You give the backup client an SSH key that is pinned to a specific borg serve --append-only command in the repo host's authorized_keys. The forced-command means that even if the client tries to run rm -rf, sshd rejects it. The only shell it can spawn is borg serve with the append-only flag. Combine that with a chrooted borg user on a network-segmented backup server, and you have a genuinely hard-to-poison target.

On the repo host:

# /home/borg/.ssh/authorized_keys
command="borg serve --append-only --restrict-to-repository /srv/borg/db01",\
restrict,from="10.20.30.11" \
ssh-ed25519 AAAAC3Nz... db01-backup-key

command="borg serve --append-only --restrict-to-repository /srv/borg/db02",\
restrict,from="10.20.30.12" \
ssh-ed25519 AAAAC3Nz... db02-backup-key

On the client, the backup command is unchanged from a normal Borg workflow:

#!/usr/bin/env bash
set -euo pipefail

export BORG_REPO="ssh://[email protected]/srv/borg/db01"
export BORG_PASSCOMMAND="cat /etc/borg/passphrase"

borg create --stats --compression zstd,6 \
  ::'{hostname}-{now:%Y-%m-%dT%H:%M:%S}' \
  /var/lib/postgresql /etc /srv

# Pruning MUST run from the repo host, not the client,
# because --append-only forbids it from the client side.

Pruning runs on the backup server itself via a systemd timer, using a separate key without the append-only restriction. Because the repo host lives on a management VLAN unreachable from the production subnet (the pattern I sketched in the Zero Trust networking guide for Linux), an attacker who owns db01 cannot pivot to the prune identity. This is the setup I recommend when you have fewer than ~50 Linux hosts and no strict cloud-first policy.

Kopia repository server and server-side ACLs

Kopia's answer to append-only is architectural: rather than push credentials down to the client, you run a kopia server daemon that owns the repository password, and let clients authenticate to it with per-user credentials scoped by server-side ACLs. That's a fundamentally different trust model. The client never touches the repo password, so a compromised client cannot decrypt other clients' snapshots or issue destructive maintenance commands. It also gives you a real web UI, real per-user quotas, and mixed Windows/Linux/macOS fleets on one repo.

# On the repo server (Ubuntu 24.04, inside the mgmt VLAN)
kopia repository create s3 \
  --bucket=acme-kopia \
  --endpoint=s3.eu-central-1.amazonaws.com \
  --access-key="$AWS_ACCESS_KEY_ID" \
  --secret-access-key="$AWS_SECRET_ACCESS_KEY" \
  --password-file=/etc/kopia/repo.pass

kopia server start \
  --address=0.0.0.0:51515 \
  --tls-cert-file=/etc/kopia/tls.crt \
  --tls-key-file=/etc/kopia/tls.key \
  --server-username=admin \
  --server-password-file=/etc/kopia/admin.pass

# Add a client user that can only write its own path
kopia server users add db01@production \
  --user-password-file=/etc/kopia/db01.pass

On the client, the connection targets the server, not the raw S3 bucket:

kopia repository connect server \
  --url https://kopia.mgmt.internal:51515 \
  --server-cert-fingerprint=$(cat /etc/kopia/tls.fp) \
  --override-username=db01 \
  --override-hostname=production

kopia snapshot create /var/lib/postgresql

The server ACLs let you deny content.delete and manifest.delete to client identities entirely, so even a rogue client can only add. Combine that with S3 Object Lock on the underlying bucket, and you have belt-and-braces immutability: server-side ACL and WORM storage.

IAM policy hardening and network segmentation for backup servers

The best append-only client is worthless if the storage or backup host is reachable from a compromised production node. My rule of thumb: the backup path is one-way, and it terminates in a segment with no path back to production. Concretely, that means (a) the backup server sits on a dedicated management VLAN or overlay, (b) production hosts can reach it only on the exact ports needed (9000 for MinIO, 22 for Borg SSH, 51515 for Kopia server), and (c) the backup server has zero SSH, HTTP, or database access into production. The nftables firewall hardening guide covers the exact rulesets for this pattern.

On the IAM side, the backup client identity should hold the minimum viable policy: PutObject, GetObject, ListBucket, and nothing else. Deny DeleteObject, DeleteObjectVersion, and any policy-mutating action. The prune identity lives in a separate AWS account or IAM user that isn't reachable from any production credential store. I keep it in a Vault namespace that only the maintenance jumphost can auth against. If a production node's Restic credential leaks, the attacker can pollute the repo with new snapshots (annoying, easy to trim) but cannot delete anything (fatal, recoverable only from tape).

One more layer: separate accounts. If your S3 bucket lives in the same AWS account as the workload it backs up, a compromised console user can delete the bucket even with Object Lock enabled. Not immediately, because Compliance mode blocks that, but they can revoke your access, corrupt your billing, or delete the KMS key that decrypts the bucket. Put the backup bucket in a separate account with its own SSO, its own KMS key, and its own break-glass credentials stored offline.

Zero errors: verifying restores before you need them

The 0 in 3-2-1-1-0 is where most Linux shops fail their audit. A backup you have never restored is a claim, not a fact. The fix is a scheduled, automated restore drill that fails loudly. For each repo I run, a small scratch VM in the management VLAN wakes up nightly, picks a random snapshot, mounts it read-only, checksums a known canary file, and compares against a signed reference stored in a separate location. If the hash differs or the mount fails, PagerDuty screams.

#!/usr/bin/env bash
# /usr/local/bin/restic-drill.sh
set -euo pipefail
source /etc/restic/env

MOUNT=$(mktemp -d)
trap 'fusermount3 -u "$MOUNT" && rmdir "$MOUNT"' EXIT

SNAP=$(restic snapshots --json | jq -r '.[-1].short_id')
restic mount --snapshot-template "$SNAP" "$MOUNT" &
MOUNT_PID=$!
sleep 3

EXPECTED=$(cat /etc/restic/canary.sha256)
ACTUAL=$(sha256sum "$MOUNT/hosts/$(hostname)/etc/machine-id" | awk '{print $1}')

if [[ "$EXPECTED" != "$ACTUAL" ]]; then
  logger -t restic-drill "FAIL snapshot=$SNAP host=$(hostname)"
  curl -sSf -X POST -d "restic drill failed for $SNAP" \
    https://alerts.internal/pagerduty
  exit 1
fi

logger -t restic-drill "PASS snapshot=$SNAP"
kill $MOUNT_PID

You should also run restic check --read-data-subset=5% weekly, borg check --verify-data monthly on Borg repos, and kopia repository validate-provider on Kopia. These are the checks that catch bit-rot, misconfigured providers, and silent object-lock policy drift before an incident forces you to discover them.

Frequently Asked Questions

Is Restic ransomware-proof?

Restic on its own isn't ransomware-proof, because a compromised client with delete permissions can wipe the repo. Restic plus S3 Object Lock in Compliance mode, plus an IAM policy denying s3:DeleteObject, plus a separate hardened identity for pruning, is effectively ransomware-resistant. The tool is fine; the destination and the credential model do the work.

What is the difference between Governance and Compliance mode in S3 Object Lock?

Governance mode is a soft lock: any user with s3:BypassGovernanceRetention can delete a retained object. Compliance mode is a hard lock: no user, not even the AWS root account, can shorten retention or delete a retained object until the clock expires. For ransomware protection, use Compliance mode, because Governance is designed for override workflows and an attacker with admin will simply grant themselves the bypass.

Can I use Restic and Borg together in one backup strategy?

Yes, and I recommend it for anything past a homelab. A common 3-2-1-1-0 layout uses Borg with SSH append-only for the on-prem tier (fast local restores) and Restic to S3 Object Lock for the immutable off-site tier. The tools don't share a format, so restores are independent. A Borg regression cannot corrupt your Restic copy, and vice versa.

Does Kopia support S3 Object Lock?

Kopia works with S3 Object Lock, but the recommended pattern is to combine Object Lock on the underlying bucket with Kopia's repository-server ACLs. That gives you two independent enforcement layers: the storage refuses deletes, and the Kopia server refuses to issue them. Retention policies are configured at the bucket level, not from the Kopia client.

How often should I test restores from immutable backups?

Automated drills nightly for a canary file, and a full end-to-end restore of at least one production dataset monthly. The 0 in 3-2-1-1-0 isn't a suggestion. Backups you have never restored have an unknown recovery time and an unknown integrity state. Every organization I've worked with that got hit by ransomware discovered restore problems during the incident, not before.

Aisha Okonkwo
About the Author Aisha Okonkwo

Infrastructure security architect at a hyperscaler. Spends her days on Zero Trust, secrets management, and yelling at unencrypted backups.