Guide

Immutable backups: what actually stops ransomware

Modern ransomware deletes your backups first, using your own credentials. What immutability really means, which controls survive a compromised admin account, and how to build one.

8 min readBackup Data

Ransomware stopped being a file-encryption problem years ago. The encryption is the last step, and by the time it runs the interesting work is already done.

The modern sequence is: get credentials, live in the network quietly for days or weeks, find and destroy the backups, exfiltrate data for a second extortion lever, and only then encrypt. Attackers know that a victim with working backups does not pay, so backups are a primary target rather than collateral damage.

Which means the question to ask about your own setup is not "do I have backups." It is:

If an attacker had my admin credentials for an hour, what backups would still exist afterwards?

For most teams the honest answer is "none," and the reason is that every copy is reachable with the same credentials that manage everything else. Nightly snapshots in the same cloud account. A NAS mounted on the server it backs up. An S3 bucket the application's IAM role can write to — and therefore delete from.

What immutability actually means

Immutable means an object cannot be modified or deleted for a defined retention period — not by an application, not by an administrator, in the strongest configurations not even by the account root. The underlying idea is WORM: write once, read many.

The important word is period. Immutability is always time-bounded; nothing is immutable forever, or storage would grow without limit. A backup with a 30-day immutable window means an attacker who gets in today cannot destroy anything written in the last 30 days, which is what buys you a recovery.

Real-world implementations:

ControlBlocksDoes not block
S3 Object Lock, compliance modeDeletion by anyone including root, until expiryAccount closure, billing termination
S3 Object Lock, governance modeDeletion by normal usersA user with BypassGovernanceRetention
Append-only repository (Borg, restic)Overwrite and delete over the wireSomeone with shell on the repo host
Versioning + lifecycle rulesCasual overwriteVersion deletes, and the lifecycle rule itself
Offline / air-gapped mediaEverything onlineBeing forgotten in a drawer, unverified
Separate-provider snapshots with no delete scopeDeletion using the source account's credentialsCompromise of the backup provider's credentials too

Versioning appears in that table and it is worth being blunt: versioning is not immutability. A delete marker hides the current version, the old versions remain — and then a lifecycle rule expires non-current versions on a schedule someone configured for cost reasons, or an attacker with s3:DeleteObjectVersion removes them outright. It raises the effort slightly. It does not create a floor.

The property that matters more than any product

Strip away the vocabulary and every effective control does one thing: it separates the ability to write backups from the ability to destroy them.

That is the whole idea. A backup process needs exactly one permission — append a new snapshot. It does not need to delete, overwrite, or modify anything that already exists. When those permissions are split, a compromised application server can write junk into tomorrow's backup, and yesterday's is still there.

When they are not split — when the same key that uploads can also delete — you have a backup system that hands its own kill switch to whatever holds the key.

Two practical consequences:

Retention must be enforced somewhere the client cannot reach. If pruning old snapshots happens because a cron job on the same server runs a delete command, then the attacker on that server can run it too, with different arguments. Retention enforced by the storage side, on a policy set separately, cannot be turned into a weapon by compromising the client.

The credential doing backups should be minted for that and nothing else. Not the deploy key. Not the admin token. A key scoped to writing backups, with no permission that removes anything.

Building it

The pattern below assumes Backup Data, but the shape transfers to any system that supports scoped credentials and server-side retention.

1. A key that can write and read, but not destroy.

Mint an API key scoped to backup:write, backup:read, snapshots:read. Leave out anything that grants deletion. This key goes on the production server. If that server is compromised, the attacker can create new snapshots — noisy and useless to them — and cannot remove old ones.

export LH_API_KEY="lh_xxxxxxxxxxxxxxxxxxxxxxxx"   # write + read only
export LH_WORKSPACE_ID="your-workspace-uuid"

2. Snapshots are immutable by construction.

import { BackupClient } from "@lighthouse-web3/baas-js-sdk";

const client = new BackupClient({
  apiKey: process.env.LH_API_KEY,
  workspaceId: process.env.LH_WORKSPACE_ID,
});

const snapshot = await client.backup(["./db-dumps"], {
  description: "nightly",
  tags: { type: "db-backup", env: "prod" },
});

Each run creates a new point-in-time snapshot. Existing snapshots are never modified — a later backup adds chunks, it does not rewrite earlier ones. There is no API that edits a snapshot in place, which means there is no call an attacker can make to poison last Tuesday.

3. Pruning happens from somewhere else, deliberately.

// Run this from an admin context, on a schedule you control —
// not from the production server, and not with the backup key.
const { sources } = await client.listSources();

for (const source of sources) {
  await client.pruneSnapshots({
    sourceId: source.sourceId,
    keepLatest: 30,
    before: new Date(Date.now() - 30 * 86400_000).toISOString(),
    dryRun: true,                      // drop to false once the preview looks right
  });
}

Note that retention is currently applied across the whole workspace rather than per source, so keepLatest counts every snapshot in the workspace. Keep workloads with different retention needs in separate workspaces.

Keep this out of the same script as the backup. The machine that writes backups should have no path to removing them, and putting both in one cron job with one credential undoes the separation you just built.

4. Encrypt client-side, and understand what that does and does not buy.

import { generateKeyfile } from "@lighthouse-web3/baas-js-sdk";

generateKeyfile("/secure/lh.keyfile", process.env.LH_KEYFILE_PASSPHRASE);

await client.backup(["./db-dumps"], {
  encryption: {
    keyfilePath: "/secure/lh.keyfile",
    passphrase: process.env.LH_KEYFILE_PASSPHRASE,
  },
});

This is a confidentiality control, not an availability control. It defeats the second extortion lever — exfiltrated backups are ciphertext, so "pay us or we publish your customer database" loses its force. It does nothing to stop deletion. Immutability and encryption solve different halves of the same attack, and you want both.

You hold the keys. Lose the keyfile and passphrase and the data is unrecoverable, with no server-side reset. Store it outside the environment it protects, and treat losing it as its own disaster scenario, because it is one.

3-2-1-1-0

The classic 3-2-1 rule predates ransomware. The updated form adds the two things this article is about:

  • 3 copies of the data
  • 2 different media or failure domains
  • 1 off-site
  • 1 immutable or offline
  • 0 errors — every backup verified by an actual restore

That trailing zero is not decoration. An immutable backup that has never been restored is an immutable file of unknown value, and ransomware recovery is exactly the wrong moment to find out that the dump has been empty since a schema change in March. See how to test your backups for a drill protocol.

What to actually check this week

Run these against your own setup. The answers are usually uncomfortable.

  • Can the credential that writes backups also delete them? If yes, everything else here is theatre.
  • Are your backups in the same cloud account as production? If yes, one compromised root account loses both.
  • Is your NAS mounted on the machine it backs up? A mounted share is just another directory to encrypt.
  • Who can change the retention policy, and from where? If it is the same admin console the attacker would already be in, it is not a floor.
  • When did a backup last get restored? Not verified, not checksummed — restored, into something you then used.
  • Where is the encryption keyfile? If the answer is "on the server," a ransomware event takes the key too.
  • How far back do immutable copies go? Attackers often sit quiet for weeks specifically to outlast short retention.

That last one is the subtle one. A 7-day immutable window sounds reasonable and is comfortably shorter than typical dwell time, which means the attacker simply waits for the clean copies to age out before triggering. Thirty days is a more defensible floor for the copy you would actually rebuild from.

The honest limits

Immutability is not a security programme. It is a floor under the worst case.

It does not stop the intrusion, detect the intrusion, prevent exfiltration, or help with data that was already corrupted before the backup ran. If an attacker sat in your database quietly altering records for six weeks, six weeks of immutable backups faithfully preserve the altered records.

What it does is guarantee that destroying your recovery option is not one of the things a compromised credential can do. That single property is the difference between an incident that costs you a weekend and one that ends the company — which is why it is worth the hour it takes to split those permissions properly.

Start with the free tier: backupdata.io has 5 GB free, no card, and the 10-minute quickstart gets you to a first verified snapshot today.

Start with the free tier

5 GB free, no card required. Point your existing dump at Backup Data and get to a first verified snapshot in about ten minutes.

Read the quickstart

Keep reading

Guide · 7 minAutomating backups with GitHub Actions (and when not to)Guide · 6 minBack up model checkpoints before your spot instance disappearsGuide · 7 minBacking up a Linux server without backing up the whole disk