VPS Backup: a 3-2-1 Design on a Single Box
A single VPS can carry a complete 3-2-1 design: three copies of the data, in two places that do not fail together, with one outside the provider. The first layer is the live data and consistent dumps on the box itself. The second is the provider’s automatic backup: on Hetzner, 20% of the plan’s monthly price for 7 rotating daily copies (~€1.20/month on a €5.99 server). The third is a copy pulled outside the provider, the only one that survives losing the account. The most common mistake is not doing too little backup. It is counting the provider’s backup as the off-site copy. It lives in the same account, behind the same credential and the same invoice as the server: whatever takes down one takes down both.
This post describes the copy design a real infrastructure uses: one VPS serving everything behind Cloudflare Tunnel. On it run the services whose cost components have appeared here before. We go layer by layer, with what each one costs and what each one protects against. The alarm system for this routine has its own post: the dead-man switch, which turns silence into an alert, and the restore rehearsal that knows how to fail. Here the subject is the copy design and the bill.
What is the 3-2-1 backup rule?
The classic formulation, popularized by photographer Peter Krogh: 3 copies of the data (production counts), on 2 different media, with 1 off-site. It was born in the world of disks and tapes. For a cloud VPS, the honest translation changes the words without changing the idea:
- “2 different media” becomes 2 different failure domains. That is, two copies that do not die for the same reason. The server’s disk and the provider’s backup qualify: losing the host takes one and not the other.
- “1 off-site” becomes 1 outside the administrative domain: outside the provider, the account, the credential. It is the number almost everyone gets wrong, and it is the subject of the most important section of this post.
How does 3-2-1 fit on a single VPS?
The complete map of the design, from live data to the most distant copy:
| Copy | Where it lives | What it protects against | Max lag (RPO) |
|---|---|---|---|
| Live data | the VPS disk | — | — |
| Dumps every 6h | the same disk | logical errors: DROP TABLE, a bad migration, an application bug | 6h |
| Provider backup (daily, 7 rotating) | same provider, off the host | losing the disk or the host | 24h |
| Nightly pull to the outside | another machine, another roof, another account | losing the provider, the account, a compromised box | ~30h |
Counting honestly: the dumps on the box itself add no durability, because they live on the same disk they are meant to save. The three copies of the “3” are the live data, the provider’s backup, and the outside copy. But the dumps are not decoration. They are the artifact the other layers copy, and the reason becomes clear with the first question.
Why make dumps if the provider already takes snapshots?
Because a disk snapshot of a running server is, at best, crash-consistent: it is a photo of the disk as it would look after yanking the power cable. Picture a Postgres mid-checkpoint, or a SQLite with a full WAL. The snapshot catches them mid-motion, and restoring becomes crash recovery, not a clean return.
The design fixes this by inverting the dependency: consistency is guaranteed by the application, not by the snapshot. A cron job on the box generates, every 6 hours, dumps that each engine knows how to produce consistently while the database is in use. Those are compressed pg_dump for the Postgres databases, SQLite’s .backup() API (consistent even with the database in WAL mode), and tarballs for file volumes and configuration. The provider’s snapshot and the outside copy do not copy the live database files. They copy the dumps, which, unlike the live files, restore cleanly.
The cost of this layer: disk space the plan already includes. On a server with 40 GB of NVMe, a few GB of rotated dumps never show up on the invoice.
Backups or snapshots on Hetzner?
Hetzner offers both, and the difference matters for the bill (hetzner.com/cloud and docs.hetzner.com, checked on 2026-08-25):
- Cloud Backups: automatic, daily, 7 rotating slots per server. They are enabled with one click, for 20% of the plan’s monthly price. On a €5.99/month CX23, about €1.20/month. This is layer 2 of the design: cheap, no manual operation, stored off the server’s host.
- Snapshots: manual, billed per GB of actual usage per month, alive until deleted. They exist for the “freeze everything before this big migration” moment. They are not for routine, because nobody presses a button every day (the per-GB price is on the same pricing page).
For the routine, the automatic backup is the choice. And the obvious thing the invoice hides is worth saying out loud: 20% of the plan is one of the cheapest insurance policies in infrastructure. The box behind this blog keeps it on.
What this layer covers: the disk dies, the host catches fire, hardware fails. You restore yesterday’s backup from the panel in minutes. What it does not cover is the next section.
Why doesn’t the provider’s backup count as the off-site copy?
Because the “1” in 3-2-1 does not measure geographic distance. It measures administrative independence. The provider’s backup lives:
- behind the same credential: the same API token that destroys the server destroys its backups. One leaked credential, or an attacker with panel access, takes both copies in the same afternoon.
- in the same account and on the same invoice: it takes one billing dispute, one card declined in the wrong month, one suspension after an abuse report. A locked account locks the server and the backups together.
- at the same provider: an incident that affects the whole provider, whether technical, commercial, or legal, affects both copies at once.
None of this is criticism of Hetzner. It is structural, and it holds for any provider. A provider snapshot protects against hardware failure. An outside copy protects against relationship failure: account, credential, contract. They are different questions, and 3-2-1 demands an answer to both.
The practical consequence: as long as the only copy off the host lives in the same panel as the server, the design has two layers, not three.
How to build the outside copy — and why it pulls instead of pushing
The third layer of this infrastructure is the simplest of all: a machine outside the provider, a home computer will do, that pulls the dumps from the VPS every night. The copy uses rsync over SSH, the command that syncs files between machines. The direction is not a detail:
- The VPS holds no credential that reaches the backup machine. An attacker with root on the box sees the local dumps and can destroy them. The outside copies are beyond reach, because no path exists from there to here. In the inverted design, with the VPS pushing to some storage, the write credential lives exactly on the machine the attacker controls.
- What has arrived is never overwritten. rsync runs with
--ignore-existingand never with--delete: from the origin’s point of view, a snapshot that has arrived is immutable. Pruning is local, by age (60 days), done by the side that pulls. The origin has no way to shrink the retention. - Integrity on arrival: every freshly arrived compressed file goes through
gunzip -t. A.gztruncated by a dropped connection is deleted on the spot and re-pulled the next night, instead of spending 60 days pretending to be a backup. - Age by mtime, not by pull success.
rsync -apreserves the mtime, the file’s modification date. It marks the time the VPS generated the dump, not the time the copy arrived. That distinction pays for itself. If the dump cron dies on the VPS, the nightly pull keeps “succeeding” forever, copying nothing new with exit code zero. What you watch is the age of the newest file (here, alarm above 30 hours), not the success of the copy.
A purist will note that the outside machine is not a datacenter, and that is true. But the layer’s criterion is administrative independence: another roof, another account, another failure domain. If you prefer, swap the home machine for object storage at another provider, and the design does not change. What cannot happen is the “off-site” copy living in the same panel as the server.
What does the complete design cost?
Hetzner Cloud public prices, checked on 2026-08-25 (before tax):
| Layer | Item | Monthly cost |
|---|---|---|
| — | CX23 VPS (2 vCPU / 4 GB / 40 GB NVMe) + IPv4 | €5.99 + €0.50 |
| 1 | Dumps every 6h on the box itself | €0 — space already included in the plan |
| 2 | Hetzner Cloud Backups (7 rotating dailies) | ~€1.20 — 20% of the plan |
| 3 | Nightly pull to your own machine outside the provider | €0 on the invoice — the cost is operating it |
The complete 3-2-1 for a €6.49 box costs ~€1.20/month in invoices. The rest of the cost appears on no invoice at all. That is exactly what makes it dangerous.
Where does this design break first?
In layers 1 and 2, a professional is watching: the provider operates its own backups, and the box’s cron job is one docker logs away. Layer 3 is homemade, precisely the one that protects against the worst scenarios: a household scheduler, a machine that sleeps, an SSH credential that expires.
It happened here, and the lesson is worth more than the embarrassment: the nightly job went 14 days without running. It had been unloaded from the system scheduler, with not one line of error anywhere, because a job that does not run does not fail. Two weeks in which layers 1 and 2 stayed perfect while the only copy outside the provider aged in silence. The fix was not “pay more attention”. It was inverting the signal: the job now reports when it succeeds, and silence became an alarm with a deadline. That design is detailed in the post on the dead-man switch and the restore rehearsal, together with the rehearsal proving the files not only arrive but come back. I consider that the mandatory second half of this post.
The boundary between the two posts is the boundary of the problem itself: this one describes where the copies live; the other, how to know they still exist. A copy design with no watchdog rots starting with its most important layer.
The summary you take home
- 3-2-1 on a single VPS: live data + provider backup + copy outside the provider. The local dumps are the fourth element: they add no durability, but they are the consistent artifact the other layers copy.
- A snapshot of a running server is crash-consistent. Consistency comes from the application:
pg_dump, SQLite’s.backup(), at whatever cadence your RPO demands. - Turn on the provider’s backup. On Hetzner, 20% of the plan for 7 daily copies (price as of 2026-08-25) is cheap insurance against hardware loss.
- But it is not the off-site copy. Same account, same credential, same invoice = same failure domain. The “1” in 3-2-1 measures administrative independence, not distance.
- The outside copy pulls, never pushes: the VPS should have no path to the backup machine. No
--delete, age-based pruning on the pulling side, integrity checked on arrival. - Watch the age of what arrived, not the success of the copy. The mtime rsync preserves records when the dump was generated, and it is what exposes a dead cron at the origin.
- The whole design’s invoice is ~20% of the plan. The real cost is watching the homemade layer: dead-man switch and restore rehearsal, in the neighboring post.
Infrastructure that scales without breaking the bank
Cloud bill out of control? I run my own on a single VPS with no open ports, automatic deploys and healthcheck-gated rollback. The whole design is published here.
Read the infrastructure posts →Related Posts
A Backup With a Dead-Man Switch — and the restore rehearsal that must know how to fail
11 min
One VPS, Zero Open Service Ports: Anatomy of a Tunnel That Serves Everything
11 min
A Self-Hosted GitHub Actions Runner with Docker: The Setup That Survives Billing
11 min