$osscostcalc.comSelf-hosting cost intelligence

Backup Strategies That Won't Eat Your Hosting Margin

9 min read·1,720 words·Operations

TL;DR

Provider snapshots are convenient but priced like a tax — 5 to 6 cents per GB-month on top of your droplet. Restic or borg pushed to Backblaze B2, Hetzner Storage Box, or Cloudflare R2 cuts that bill by 5–10x while giving you real off-provider copies. This guide puts hard numbers on a 50 GB and 500 GB workload, lays out a 3-2-1 setup, and tells you how often to actually test a restore.

TL;DR

Provider snapshots cost about 5–6 cents per GB-month and live on the same account as the server they protect — convenient, but not a real backup and not cheap at scale. Restic or borgbackup pushed to Backblaze B2, Cloudflare R2, or a Hetzner Storage Box costs roughly $0.005–$0.006 per GB-month and gives you the off-provider copy the 3-2-1 rule actually requires. The numbers below show why the snapshot-only approach quietly eats 10–20% of a small VPS bill, and how to build a sane alternative in an afternoon.

Why snapshots feel free but aren't

Every major VPS provider sells you snapshots as a checkbox at install time. On a €4.51/month Hetzner CX22 (2 vCPU, 4 GB RAM, 40 GB disk), enabling weekly snapshots that retain 40 GB will add roughly €0.012/GB × 40 = €0.48/month. That sounds like nothing — until you realise the snapshot bill scales with disk size, not with the data you actually changed. A CPX41 with a 240 GB disk that's 80% full quietly adds €2.40/month for a single snapshot slot. DigitalOcean charges $0.06/GB-month for snapshots and $0.05/GB-month for their managed backup service. Linode charges a flat per-plan fee ($2/month on a Nanode, $5/month on a 4 GB plan) which is competitive at small sizes but inflexible: you get four slots (daily, 2x weekly, biweekly) and that's it.

Three things wrong with snapshot-only backups:

  • They live on the same provider. If your account gets locked, your billing card declines, or a region goes down hard, the snapshots go with the droplet.
  • Granularity is poor. Most providers cap retention at a handful of slots. You cannot keep "daily for 14 days, weekly for 8 weeks, monthly for a year" without scripting and paying per slot.
  • Restore is whole-disk. You can't pull a single accidentally-dropped Postgres table out of a snapshot without spinning up a whole new VM and mounting the image. That's a 20-minute operation when the outage clock is ticking.

Snapshots are useful as a pre-upgrade safety net ("snapshot now, run apt full-upgrade, delete the snapshot in 24 hours"). They are not a backup strategy.

The 3-2-1 rule, translated for a one-person ops team

The classic rule is three copies of your data, on two different media, with one copy off-site. For a self-hoster running Nextcloud, Gitea, or a Postgres-backed SaaS on a single VPS, that maps to:

  1. Primary — the live data on your VPS disk.
  2. Local/near — a hot copy that survives "I deleted the wrong thing." A provider snapshot or a restic repo on a Volume attached to the same VPS works.
  3. Off-provider — an encrypted restic/borg repo on cheap object storage at a different vendor. This is the copy that survives account suspension, ransomware on the host, or a regional fire.

"Two different media" gets a modern interpretation: object storage and block storage count as different enough, because the failure modes don't overlap. What matters is that one copy is administratively separated from your primary — different login, different billing card if you can swing it.

Tool choice: restic vs borg vs rsync

Tool Dedup Encryption Object storage native Best for
restic Yes (content-addressed) Yes (AES-256) Yes (S3, B2, Azure, GCS, SFTP) Most self-hosters
borgbackup Yes (chunked) Yes (AES-CTR + HMAC) Via rclone/sshfs only SSH targets, Storage Boxes
rsync + hardlinks No No (use SSH) No Simple file trees, no DB
rclone No Optional Yes One-way sync, not versioned

Restic is the default recommendation in 2026. Single static Go binary, no server-side daemon, native support for every object store that matters, deduplication that handles a daily Postgres dump well, and a restic mount command that exposes any snapshot as a FUSE filesystem so you can cp a single file back. Borg is faster on incremental backups for very large filesystems (think 2 TB+) and has the best CLI ergonomics, but it really wants an SSH target — pairing it with a Hetzner Storage Box is the canonical setup.

Real numbers: 50 GB workload

Assume a small Nextcloud + Postgres setup: 50 GB of user data, 2 GB Postgres dump, daily churn around 500 MB. Retention: 14 daily, 8 weekly, 12 monthly. After dedup and compression, restic typically stores about 1.3–1.6x the live dataset across that retention window. Call it 75 GB on the wire.

Strategy Monthly cost Notes
DO snapshots (4 slots, 50 GB each) $12.00 $0.06/GB × 200 GB
DO managed backups (20% of droplet) ~$1.20 on a $6 droplet Limited retention
Hetzner snapshots (4 slots × 40 GB) €1.92 Provider-local only
Linode backup add-on (4 GB plan) $5.00 Flat fee, 4 slots
restic to Backblaze B2 (75 GB) $0.45 $0.006/GB-month
restic to Cloudflare R2 (75 GB) $1.13 $0.015/GB, no egress fees
borg to Hetzner Storage Box 100 GB €3.20 Flat, EU-located
restic to Wasabi (1 TB min) $6.99 Only worth it at scale

The cheapest credible 3-2-1 setup here is: keep one Hetzner snapshot as your "oops" copy (€0.48/mo) plus restic to Backblaze B2 for the off-provider copy ($0.45/mo). Total backup spend: about €0.90/month protecting a server that costs €4.51/month. That's roughly 20% of the compute bill — acceptable. The snapshot-only DigitalOcean approach at $12/month on a $6 droplet is 200% of compute, which is the kind of ratio that quietly kills hobby-project margins.

Real numbers: 500 GB workload

Now the same shape, scaled up: a Mastodon instance or a self-hosted Plex with 500 GB of media and database, ~5 GB daily churn. Restic repo after dedup: ~650 GB.

Strategy Monthly cost
DO snapshots (2 slots × 500 GB) $60.00
Hetzner snapshots (2 slots, ~500 GB total billable) €6.00
Linode backup add-on (16 GB plan) $5.00
restic to Backblaze B2 (650 GB) $3.90
restic to Cloudflare R2 (650 GB) $9.75
borg to Hetzner Storage Box 1 TB €11.90
Wasabi (1 TB) $6.99

At this scale, B2 absolutely dominates if you accept the egress charge on restore ($0.01/GB after a free allowance equal to 3x stored data). A full 650 GB disaster restore from B2 costs roughly $0 thanks to the free egress allowance covering it. Cloudflare R2 wins if you expect to restore frequently or run integrity checks that pull a lot of data, because R2 has zero egress fees — useful for restic check --read-data on a monthly cadence.

For comparison, run the same workload through the osscostcalc calculator on this site and you'll see the compute side is often €15–€25/month; backup at $4 of that is reasonable. Backup at $60 (DO snapshots) is not.

The setup, end to end

A minimal restic cron on a Hetzner CX22 looks like this in spirit:

  • Install restic from the distro repo (1.x is fine; 0.16+ has the features that matter).
  • Create a B2 bucket with object-lock or lifecycle rules disabled — restic manages retention itself.
  • Generate a long random repo password. Store it in a password manager, not just on the box. If you lose it, the repo is unrecoverable by design.
  • restic init --repo b2:my-bucket:/hostname
  • A nightly script: dump Postgres with pg_dump -Fc, then restic backup /var/lib/data /var/backups/pg.dump --tag nightly.
  • A weekly restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune.
  • A monthly restic check --read-data-subset=10% to catch bitrot.

The whole thing fits in a 40-line bash script plus a systemd timer. Don't reach for Kubernetes operators or backup SaaS for a single VPS — you're adding a dependency that costs more than the thing it protects.

Encryption and key handling

Restic and borg both encrypt client-side, so the storage provider sees only opaque blobs. This is the right model. The failure mode to worry about is not the provider reading your data — it's you losing the repo key. Two practical rules: print the key on paper and stick it somewhere physical, and store a copy in a second password manager or a sealed env var on an unrelated machine. A backup you cannot decrypt is a write-only log.

Disaster recovery test cadence

A backup that has never been restored is a hypothesis, not a backup. Useful cadence for a small operation:

  • Weekly, automated: restic check (metadata only, ~30 seconds, negligible cost).
  • Monthly, automated: restic check --read-data-subset=10%. Catches silent corruption on the provider side. On 650 GB this pulls ~65 GB; free on R2, $0 on B2 if under the egress allowance.
  • Quarterly, manual: Spin up a fresh €4.51 CX22, restore the latest snapshot, bring up Postgres and the app, log in, click around. Destroy the VM. Total cost: maybe €0.15 of prorated compute plus an hour of your time. Write down how long it took — that's your RTO, and the first time you do it, it will surprise you.
  • Yearly, manual: Restore from the oldest retained backup (the 12-month-old monthly). This catches "we changed the schema and the old dumps don't import anymore" before it matters.

If you can't make time for the quarterly drill, at minimum script the restore-and-verify on a throwaway VM and run it from CI once a month. The point is not to satisfy a checklist — it's to make sure the muscle memory exists when something is actually on fire.

Decision checklist

  • Is your provider-snapshot bill more than 15% of your compute bill? If yes, move to restic/borg.
  • Do you have a copy of your data on a different vendor's storage, paid for with a different card if possible? If no, you don't have a backup, you have redundancy.
  • Can you recite your repo password's recovery path from memory? If no, fix that today — it's the single most likely failure.
  • When did you last do a full restore to a clean machine? If the answer is "never" or "I don't remember," schedule it this week.
  • Have you priced the restore? Egress on a full DR pull from B2, R2, or a Storage Box should be a known number, not a surprise invoice.

Get the off-provider copy in place first, then tune retention, then automate the restore test. In that order. Anything else is theater.

backupsresticborgdisaster-recoveryobject-storagehetzner