hetshield

A disaster recovery plan for Hetzner Cloud: RTO, RPO and a one-page runbook

A Hetzner Cloud disaster recovery plan for a bad deploy, a lost location and a lost account: inventory commands, RTO/RPO worksheet and a restore drill.

10 minute read by Dominik Geimer

Most small teams on Hetzner Cloud have a backup. Few have a plan, meaning a document that says what breaks, what you restore from, who does it, and how long it takes. The difference shows up at 3 a.m., when the person who set everything up is asleep and the only record of how the server was built is the server.

This article is a template you can finish in an afternoon. It covers three failures of increasing size, a worksheet for recovery time and recovery point, the commands to inventory everything that is not on the disk, and a restore drill. Hetzner's built-in tools handle the first failure well; the point of the plan is to be honest about the other two.

Three disasters, three different copies

A recovery plan is not one thing. The copy that fixes a bad deploy is useless when the account is gone, and the copy that survives the account is too slow for a bad deploy. Write the table first.

Disaster Typical cause What fixes it Where that copy lives
A: the server is wrong Bad deploy, deleted files, ransomware, disk corruption Hetzner Backups or a snapshot taken before the change Same account
B: the location or the machine is unavailable Datacenter incident, host failure, resource shortage in one location A snapshot or backup used to create a server elsewhere; your own provisioning scripts Same account
C: the account is unavailable Payment lock, abuse lock, termination, compromised login A copy you hold outside the account, plus provisioning that does not need the old project Outside Hetzner

Hetzner Backups are automatic and kept in seven slots per server, priced at a flat 20 percent of the server price (billing FAQ). They are "bound to a single server and will automatically be deleted if you delete the server" (backups and snapshots FAQ). Snapshots are manual, are kept until you delete them, and the default limit is 30 across the whole account, not per server. Both are the right answer to disaster A, and you should keep using them.

Neither helps with disaster C, because both are resources of the account that is gone. The reasoning, with dated public cases, is in Hetzner account locked: what actually happens to your data. For the plan, treat C as a real row, not a footnote.

Set an RTO and an RPO per server

Two numbers make a plan testable.

  • RPO (recovery point objective): how much data you can afford to lose, measured in time. A nightly backup means an RPO of up to 24 hours.
  • RTO (recovery time objective): how long you can be down before it hurts, measured from the decision to restore until the service answers requests.

Fill in this worksheet for every server, even if the answer is "we don't care".

Server What it runs RPO we accept RTO we accept Copy for A Copy for B Copy for C Measured restore time
web-1
db-1

Two rules keep the worksheet honest. First, the last column is measured, never estimated; the drill below produces it. Second, if the RPO for a database is shorter than your backup interval, the plan is wrong, and the fix is usually a database-level dump or replication, not a more frequent disk copy. A disk image of a running database is a crash-consistent copy: it usually recovers, but the recovery is the same as after a power cut, and you should know that before you rely on it.

Inventory what is not on the disk

A disk image holds the disk. A running Hetzner setup has more than that, and the missing pieces are what stretch the RTO. The Hetzner Cloud API has separate resources for each of them (OpenAPI spec): servers, volumes, firewalls, networks, floating IPs, primary IPs, load balancers, certificates and SSH keys.

With the hcloud CLI and a read-only token, dump all of it to a file you keep outside the account:

export HCLOUD_TOKEN=...   # read-only token

mkdir -p hetzner-inventory && cd hetzner-inventory
for kind in server volume firewall network floating-ip primary-ip load-balancer certificate ssh-key; do
  hcloud "$kind" list -o json > "$kind.json"
done
hcloud image list --type snapshot -o json > snapshot.json

Then ask the questions the dump answers.

# Which servers have a volume attached? Those servers are not fully covered by any image.
jq -r '.[] | select((.volumes // []) | length > 0) | .name' server.json

# Which firewall rules does each server depend on?
jq -r '.[] | "\(.name): \(.applied_to | length) resources, \(.rules | length) rules"' firewall.json

# Which floating IPs does DNS point at?
jq -r '.[] | "\(.ip) -> \(.server)"' floating-ip.json

Commit the JSON to a private repository on a different provider, or keep it in your password manager's attachments. The goal is that someone with a new Hetzner account, or no Hetzner account, can read exactly what existed.

Three items deserve a line each in the runbook:

  • Volumes. Hetzner states "we do not provide Backups or Snapshots for Volumes" (volumes overview), and no agentless image of a server can contain one. Every volume needs its own backup path, usually file-level with restic or Borg from inside the server. The long version is in Hetzner snapshots do not include volumes.
  • IP addresses. A restored server in another location or another provider will not keep its address. The runbook step is a DNS change, so the TTL matters: lower it before a planned move, and know where the zone is hosted. If your DNS zones live in the account that is locked, you have lost the lever you need.
  • Secrets. The restore should not depend on a credential that lives only on the server being restored. Keep the Hetzner API token, the restic or Borg passphrase, registrar login and 2FA recovery codes in a password manager that a second person can open.

Write the runbook for disaster A, B and C

One page per disaster, numbered steps, no prose. A usable shape:

Disaster A: roll back inside the account.

  1. Decide the restore point: the newest backup, or the named snapshot from before the change.
  2. Rebuild the server from it with hcloud server rebuild web-1 --image <backup-or-snapshot-id>. Rebuild replaces the disk, so confirm that the server has nothing newer than the restore point that you still need. If in doubt, take a snapshot of the broken state first.
  3. Check the application with the same request that monitoring uses.
  4. Write down the data window lost, since the plan promises an RPO.

Disaster B: a different location.

  1. Create a server from the snapshot or backup in another location, with the same or larger disk: hcloud server create --name web-1-b --type <type> --image <snapshot-id> --location <loc> --ssh-key <key>. Pick the type that is in stock at that moment; availability changes and a type that was free yesterday may not be today, so have two candidates written down.
  2. Reattach firewalls and networks from the inventory. Volumes cannot follow a server to another location; restore their contents from the volume's own backup.
  3. Move DNS or the floating IP, then check certificates.

Disaster C: no account.

  1. Assume nothing in the old project is reachable, including the API token.
  2. Create a new account or use an existing second provider. Restore from the copy held outside Hetzner, following the steps for your target: Proxmox, VMDK, VHDX or VDI, or a new Hetzner server by writing the raw image from rescue mode.
  3. Rebuild networking, firewalls and DNS from the inventory file, which is why it lives outside the account.
  4. Restore volume data from its separate backup.

Keep the runbook itself outside the account too. If the only copy is a wiki page on the Hetzner server, disaster C takes the plan with it.

What the copy for disaster C can be

You need at least one copy that is reachable with credentials unrelated to the Hetzner login. There are two layers, and a small setup benefits from both.

File-level and database-level. Run restic or Borg from the server to an S3-compatible bucket or a server at another provider. It is small, frequent and cheap, and it is the only sensible home for volume data and database dumps. Restoring it needs a machine that already has the operating system, packages and configuration, so on its own it can mean an RTO of hours of rebuild work. Nothing in this layer needs a paid tool; the trade-offs of Borg, restic and Proxmox Backup Server are in Hetzner offsite backup: a Storage Box is not outside Hetzner.

Whole-server image. A raw disk image of the server, stored outside Hetzner, restores the operating system, packages, configuration and data on the disk in one step. It is the fast path for disaster C and the reason the RTO is measured in a drill rather than guessed. Hetzner offers no download for snapshots (Can you download a Hetzner Cloud snapshot? explains why and lists what works), so this layer needs either a manual process or a service. Whichever you choose, the same caveat holds: volumes are never part of it.

The restore drill

A plan you have not run is a draft. Do this once per server class, and then on a calendar; quarterly is realistic for a small team. The point is to produce the last column of the worksheet.

For a whole-server image, you do not need a cloud account to test that it boots. On any Linux workstation with QEMU and OVMF installed (on Debian or Ubuntu, apt install qemu-system-x86 ovmf zstd):

zstd -d server.img.zst -o server.raw

qemu-system-x86_64 -enable-kvm -m 2048 -smp 2 \
  -drive file=server.raw,format=raw,if=virtio \
  -bios /usr/share/ovmf/OVMF.fd \
  -nic user,hostfwd=tcp::2222-:22

Use OVMF for a server that booted through UEFI and leave the -bios option out for a legacy BIOS one; the way to tell is a partition of type EFI System in the image, as described in the Proxmox guide. Expect the first boot to come up without network, because the image still carries Hetzner's static address configuration. With a working network, ssh -p 2222 root@localhost should work.

Then time the whole sequence and record each stage:

  1. Start the clock when you decide to restore.
  2. Get the credentials: password manager, second factor, token.
  3. Download and decompress the image, or restore the file-level backup onto a fresh machine.
  4. Boot, fix the network, and apply the first-boot fixes.
  5. Make the request that proves the service works, and check the newest row in the database.
  6. Stop the clock. That is your measured restore time. Write down every step where you had to look something up on the old server; each is a gap in the runbook.

Also check the part people forget: a second person can follow the page without you. If they cannot, the plan depends on one individual being reachable.

Keep the plan alive

  • Review triggers. Revisit the worksheet when you add a server, attach a volume, change DNS provider or someone with credentials leaves.
  • Watch the snapshot count. With 30 snapshots across the account, a retention script that never prunes will eventually fail to create the one you needed before a risky change. Count them in the same inventory run.
  • Check the account itself. Two-factor authentication on, a recovery email at a domain that is not hosted at Hetzner, billing and abuse email going to more than one person, and invoices paid with a method that does not expire.
  • Nothing here assumes Hetzner is unreliable. Backups and snapshots work. The plan assumes only that a single account, at any provider, is one failure domain.

Where hetshield fits

If you want the whole-server layer for disaster C without writing the process yourself, hetshield is one option. It needs only a Hetzner API token, runs without downtime, and only snapshots the source server: it is never rebooted, and the cloned copy is never booted as your system. You download a compressed raw image. In our measurement a 40 GB disk was read in 1:52, and a full export takes about nine minutes end to end. It costs $29 flat plus VAT and is refunded if the export does not finish. Volumes are excluded, as with every image-level method, so they stay on your list. The documentation describes what is and is not in the image and how to restore it on each hypervisor.

You can also make the image yourself with the manual process or use none of this and rely on tested file-level backups. Any of those is a plan. Leaving disaster C as an empty row in the table is not.

Keep reading