Backups & recovery
Capture a whole server to one encrypted .hsi image, then restore, clone, or relocate it anywhere — with automatic IP rewrite and proven recoverability.
Backups & recovery
HostSSH turns a whole server into one portable, encrypted file — and then lets
you put that file back wherever you want. Every backup is a complete .hsi image:
the platform brain, every database, every volume, the system layer, and the manifest
that lets any agent rebuild the box from scratch. From that single image you can do
four things — capture, restore, clone, and relocate — and move images
between machines three different ways.
This guide assumes the agent is already installed. If it isn't, start with Install on any VPS.
The .hsi image
A .hsi (HostSSH Image) is a full server snapshot in one self-describing artifact.
You never have to know what's inside to restore it — the manifest does. Conceptually:
image.hsi
├── manifest.json schema version, source box, public/overlay IPs, versions, layer index
├── control-plane/ the platform brain — the control-plane DB dump + its data dir (incl. the APP_KEY)
├── databases/ per-DB dumps (Postgres/MySQL, detected automatically)
├── volumes/ every named volume + bind dir (content-addressed, dedup-friendly)
├── system/ WireGuard, systemd units, firewall, crontab, packages
├── sidecars/ inspect JSON for every non-platform container
└── checksums + ed25519 signature
A note on the brain layer. HostSSH's control-plane brain is captured into the neutral
control-plane/layer (.hsischema v2). On a box still running the Coolify control plane, that layer holds thecoolify-dbdump +/data/coolify; once a box runs HostSSH's own control plane it holds that instead — the layer name and restore path don't change. Older images written with the legacycoolify/layer still restore: a newer agent reads either.
What that buys you:
| Property | What it means for you |
|---|---|
| Encrypted | Every image is encrypted (age / AES-256-GCM) before it leaves the host. A leaked bucket is not leaked data. |
| Integrity-signed | An ed25519 signature + per-layer checksums are verified before any restore — a tampered or truncated image is rejected, not restored. |
| Self-describing | The manifest records what's inside and where every IP lives, so any agent can restore it and the IP-rewrite is deterministic. |
| Portable | Backed by restic, so an image lives in any backend (R2/S3/MinIO/B2/Wasabi/SFTP/local) and can be downloaded and re-uploaded as a single pack. |
| Versioned | The schema is versioned — a newer agent always restores an older image. |
The image contains the platform
APP_KEYthat decrypts your app secrets. That is exactly why encryption is non-negotiable — and why you choose who holds the key (your own passphrase, an escrowed KMS key, or a host-only key that never reaches the control plane). See Connections (BYOK) and Security.
The four operations
| Operation | CLI verb | What it does |
|---|---|---|
| Capture | hostssh capture | Build a fresh .hsi image of this server and write it to your storage. |
| Restore | hostssh restore | Roll this server back to a prior image, in place. |
| Clone | hostssh clone | Reproduce a server on another machine (e.g. staging from prod) — without touching the original. |
| Relocate / migrate | hostssh migrate --new-ip | Stand up the server on a fresh VPS, rewrite the public IP, and bring the whole stack back up from scratch. |
Capture
Capture builds a complete image and writes it to your storage target. Run it on demand or on a schedule.
hostssh capture # full image of this server, now
hostssh capture --target r2-backups # write to a specific storage connection
hostssh status # confirm: last image, size, integrity
Capture is online-safe — it dumps databases consistently and snapshots volumes without taking the platform down. Because restic dedupes underneath, the second and later images of a box only store what changed.
You need at least one storage connection before you can capture — no storage, no image. See Connections (BYOK).
Restore (in place)
Restore rolls the same server back to a chosen image. It is non-destructive until you confirm the cutover — HostSSH stages the restore and waits for your go-ahead before swapping anything live.
hostssh restore --image latest # roll this box back to its newest image
hostssh restore --image <image-id> # or pick a specific point in time
hostssh restore --image file.hsi # from a downloaded image file (Offline mode)
You don't have to take all or nothing. Browse an image and recover just the piece you need — a single database, one volume, or a file — from the recovery console or the CLI.
Emergency restore is never blocked. Restoring an existing image works even on an expired or revoked license, with quota exceeded, or with a storage connection marked disabled. We never hold your data hostage behind billing. (Capturing new images and starting new migrations do require an active license.)
Clone (to another machine)
Clone reproduces a server somewhere else while leaving the source untouched — the canonical "spin up staging from prod" move.
hostssh clone --image latest --to <target>
The key safety rule: a clone does not join the private overlay network. WireGuard
is left off on a clone unless you explicitly pass --activate-wg (replace mode).
This prevents two boxes from claiming the same overlay IP and breaking your live mesh.
A clone is a copy; relocate is a move.
Relocate / migrate (fresh VPS + automatic IP rewrite)
Relocate is the headline move: take an image, stand it up on a brand-new VPS, and automatically rewrite the public IP everywhere it appears — then bring the entire stack back up, end to end.
hostssh migrate --new-ip # relocate this server to a fresh VPS
hostssh migrate --new-ip --to <target> # relocate onto a specific provisioned target
The IP-rewrite is deterministic, not guesswork. It touches only the public-facing references, because inter-service traffic rides an IP-stable overlay:
| Reference | Rewritten by |
|---|---|
Control-plane server record (the brain DB's servers.ip) | SQL replace |
Preview domains (*.<ip>.sslip.io in app/service FQDNs) | SQL replace |
| DNS A records | your DNS provider's API (one-click flip) |
Overlay peer endpoint (wg0.conf) | rewritten only in replace mode |
| Firewall allow-lists | re-applied |
A managed relocate finishes with a verification report — database row counts match, domains return 200, TLS is valid — so you know the new box is actually serving before you decommission the old one.
This is the literal "3-year VPS renewal" path: provision a fresh box, full-clone the old one onto it, rewrite the IP, verify, flip DNS, decommission. No manual rebuild.
Named-volume data is captured, not just named
A backup that records the names of your volumes but not their contents is a trap you only
discover during a restore. HostSSH captures the data inside every named volume into the .hsi
image — binary-safe, any file type. Uploads, images, video, PDFs, SQLite files, WordPress
wp-content, MinIO object directories, and any other bytes on a declared volume come back on restore.
There is no MIME filter and no “SQL dumps only” path for volume data.
When you migrate a stateful app, confirm it declares its volumes (--volume name:/path) so they're
in scope. Bind mounts are not a first-class product surface; use named volumes.
Rebuilding a whole node
Restoring a single image to a Node is one thing; bringing an entire Node back to serving every app it hosted is the recovery you actually want after a box dies. Today:
- Available now: restore any captured image to a target Node (in place or onto a fresh VPS with the IP rewritten). The image carries the platform brain, databases, and volume data.
- On the roadmap (node-restore orchestration): a one-click "Restore node" that restores the snapshot and re-enqueues every App that was pinned to that Node — because the App rows and their sealed env survive in the control-plane database, the platform can redeploy them all onto the rebuilt box and re-provision the proxy automatically. Until that ships, rebuild by restoring the image, then redeploying each App from the dashboard.
Off-box backup of your platform keys
The .hsi image protects your servers, but the keys that unlock it — the encryption key
(HOSTSSH_ENC_KEY) and your license key — need to live somewhere the box's failure can't take with
it. HostSSH can write an age-encrypted, off-box copy of your platform secrets so a total loss of
the control-plane machine doesn't mean losing the ability to decrypt your own backups. Store that
copy somewhere separate from the fleet (a password manager, a second bucket, cold storage).
The three transfer modes
Capture/restore handle a single box. Transfer modes are how an image gets from one machine to another. Pick the one that fits your trust and connectivity situation.
Mode 1 — Offline / Download
Trust-minimized and fully cross-provider. The image becomes a file you physically control; no third party ever touches plaintext, and the two machines never need to reach each other.
- Capture on the source (
hostssh capture). - Download the image — from the dashboard, or
hostssh export --image <id> --out box.hsi(it may also already be sitting in your own bucket). - Move
box.hsianywhere — USB, laptop, a different cloud, a different provider. - On the destination: install the agent, upload the image, and run
hostssh restore --image box.hsi— choosing same IP or new IP (relocate).
Use it for: air-gapped environments, moving across providers that can't see each other, and any "I want the file in my own hands" workflow.
Mode 2 — Peer transfer (one-time transfer key)
The fastest path when both machines are online and licensed — a near one-click move with no full local download. The image streams directly source → target, already encrypted end to end.
-
On the source, mint a one-time, expiring, scope-locked transfer key:
hostssh transfer --new-key --image <id> --target-host <new-box> -
On the target (or in the SaaS panel), paste the key and pull:
hostssh transfer --receive <KEY> -
The image streams over an authenticated peer channel — or relayed through the control plane if both boxes are behind NAT — with live progress and verification.
The transfer key is single-use, time-limited, and locked to a specific target host and image id. It is not a credential to your storage; it authorizes exactly one pull.
Mode 3 — Managed migration (panel-driven, full setup)
The "do the whole thing for me" mode. Driven from the SaaS control panel, it provisions (or connects) the destination, restores everything, rewrites the IP, brings up sidecars, re-issues TLS, optionally flips DNS, and hands you a verification report — zero manual steps.
- In the panel choose Migrate → pick the source image → choose a target:
- Provision new — HostSSH spins up a fresh VPS via the provider API (Hostinger/Hetzner/DO/Vultr/AWS), or
- Bring your own — enter SSH credentials for any reachable VPS or bare-metal host.
- HostSSH installs the agent, restores brain + databases + volumes, rewrites the IP, starts sidecars, re-issues TLS, and (optionally) cuts DNS over.
- You watch a live progress stream; it ends with a green/red verification card (row counts match · domains 200 · TLS valid).
From the CLI the same managed path is hostssh migrate against a provisioned or
BYO target; the panel adds the wizard, progress stream, and verification report.
| Mode 1 — Offline | Mode 2 — Peer transfer | Mode 3 — Managed migration | |
|---|---|---|---|
| Both boxes online? | No — air-gap friendly | Yes (both licensed) | Yes (panel-driven) |
| You handle the file | Yes (download/upload) | No (direct stream) | No (fully automated) |
| Third party sees plaintext | Never | Never (E2E encrypted) | Never (CP orchestrates, not decrypts) |
| Provisions the target | No | No | Yes (or BYO VPS) |
| Best for | cross-provider, air-gapped, max trust control | fastest move, both reachable | hands-off relocate + full setup + verify |
Peer transfer and managed migration are gated by Licensing; the Offline mode and in-place restore are available on every tier — including emergency restore.
Retention: keep everything, by default
HostSSH's house policy is no silent deletion. Out of the box, retention is
keep_all — every image is kept until you decide otherwise. Nothing is ever
pruned automatically unless you opt in, and every prune is logged and can be
previewed as a dry run first.
Set a policy per (server × storage target):
hostssh retention get --target r2-backups # show the active policy (keep_all by default)
hostssh retention set --target r2-backups \
--keep-last 30 \
--keep-daily 7 --keep-weekly 8 --keep-monthly 12 \
--min-free-space 20%
hostssh retention preview --target r2-backups # dry-run: exactly what WOULD be deleted
hostssh retention clear --target r2-backups # back to keep_all (no auto-delete)
| Setting | Meaning |
|---|---|
keep_all (default) | Never delete anything automatically — the house policy. |
--keep-last N | The number of most-recent images to keep before older ones become deletable. |
--keep-daily / --keep-weekly / --keep-monthly | Grandfather-father-son buckets — keep N dailies, N weeklies, N monthlies. |
--min-free-space | Capacity floor — prune the oldest unpinned images first to stay above this. |
min_keep | A hard floor (default ≥ 1) — retention never prunes below it. |
| Locked / pinned images | Legal hold — never auto-deleted; removing a lock is an explicit, audited action. |
prune_trigger | When pruning runs: after_capture, scheduled, or manual. |
A typical schedule pairs a nightly capture with a GFS retention policy and a daily +
weekly + monthly grid, while keeping a --min-free-space floor so storage never fills.
Because emergency restore bypasses retention entirely, tightening a policy can never
cost you the ability to recover an image that still exists.
Retention is set per storage target, so you can keep-all in cheap cold storage and run a tighter window on a hot bucket. See Connections (BYOK) for adding and scoping storage targets.
Restore-drills: prove it actually restores
A backup you have never restored isn't a backup — it's a hope. HostSSH closes that gap with restore-drills: on a schedule, it spins up a throwaway target (an ephemeral Railway or Hostinger box), restores your latest image onto it, diffs database row counts to confirm the data is intact, and tears the target back down — at scale-to-zero cost.
The result lands in your dashboard as a pass/fail with a timestamp, and (with a notification connection) is delivered as a scheduled report. This is the difference between a backup that "completed" and one that is proven recoverable.
hostssh status # includes last restore-drill result + last backup health
Phase note: scheduled restore-drills and the live managed-migration progress stream are part of the control-plane roadmap; the underlying capture, restore, clone, and
migrate --new-ipengine is the proven core. See Architecture and Provisioning.
Command reference
hostssh capture # build a new .hsi image of this server
hostssh restore --image latest|<id>|file.hsi # roll back in place (non-destructive until confirm)
hostssh clone --image <id> --to <target> # reproduce on another machine (WG off by default)
hostssh migrate --new-ip [--to <target>] # relocate to a fresh VPS + rewrite the IP
hostssh transfer --new-key --image <id> --target-host <host> # mint a one-time peer transfer key
hostssh transfer --receive <KEY> # pull a peer-transferred image
hostssh export --image <id> --out box.hsi # download an image as a single portable file
hostssh provision # stand up a fresh VPS (Mode-3 / managed)
hostssh retention set|get|preview|clear --target <id> # retention policy (keep_all by default)
hostssh status # health, license, last backup, last restore-drill
See Web-SSH & access for getting onto any box (hostssh ssh <server>),
Licensing for which modes each tier unlocks, and
Architecture for how the engine and IP-rewrite work under the hood.