VPS Backup and Restore — The 3-2-1 Pattern on a Root Server
What to back up on a VPS, where copies should live, how snapshots fit in, and the restore test that decides whether your scheme works.

What to back up on a VPS, where copies should live, how snapshots fit in, and the restore test that decides whether your scheme works.
Decide what is state
On a typical server the irreplaceable state is small: databases, uploaded files, certificates, configs. The OS is reproducible; restore speed comes from automating its setup (a short Ansible/Cloud-init script beats a golden image you forget to update).
Business $24/mo (6 vCPU / 16 GB DDR5) is the production size. Crypto invoice, no KYC.
Launch nowThe pattern
- 3 copies: live data + on-box snapshot + off-box backup.
- 2 media: local NVMe snapshot for fast rollback; off-box copy on a different account/provider or cold storage.
- 1 tested restore: a calendar entry that actually restores into a scratch directory and diffs.
On BitCloudVPS
- Snapshots are an add-on at checkout on VPS/Storage — good for pre-change safety, not a backup strategy by themselves.
- For the off-box copy, a Storage VPS ($24/mo for 1 TB NVMe) as a Borg/Restic target keeps backups inside the same no-KYC rails.
- Encrypt at the source (restic/Borg do this natively) — the backup target never needs your plaintext.
Short answer: the 3-2-1 layout that fits a one-provider budget
Short answer: keep three copies of anything you would hate to lose - the live disk, one machine-readable backup on a second server, one offsite copy - and rehearse the restore before you need it. The classic 3-2-1 rule asks for two different media and one copy offsite; on a VPS budget the honest translation is two distinct KVM instances plus one object or storage endpoint, with encryption applied before anything leaves the source machine. You do not need three providers to satisfy the intent: you need three failure domains, and two servers in different racks plus a storage tier already are three failure domains as long as you do not snapshot-copy the live disk and call it a backup.
The pages below this one already sort state from rebuildable data and show the scheduling pattern. This guide goes one level more concrete: what to have in place before the first job, the exact restic commands for a first encrypted backup, how to automate it with a systemd timer, how to prove the restore works, the failure modes you will actually hit, and how to push the second copy to cheap bulk storage with rclone. Every command shown runs on a plain Debian or Ubuntu image with root SSH - no control-panel snapshots involved.
Prerequisites: what you need before the first backup job
Backup projects fail at the prerequisites far more often than at the software. Confirm every row of this table before you install anything; each gap becomes a 2 a.m. incident otherwise.
| You need | Why | Minimum on this network |
|---|---|---|
| A source VPS with room to breathe | restic and borg need scratch space for the index and cache, roughly 5-10% of the data set | Starter, $8.50/mo - 2 vCPU EPYC 7443P, 4 GB ECC, 60 GB NVMe |
| A second machine as the backup target | Same-provider is acceptable only on a different rack; same host is not a backup | A second Starter, or Nano at $5.49/mo ($36/yr flat) for small state |
| Bulk capacity for archives and long retention | Weekly fulls of media or mailboxes grow past any boot disk quickly | Storage plans, 1-5 TB NVMe from $24/mo |
| SSH key access to both machines | The backup job must run without a password prompt; passwords in scripts leak into process lists | Any plan; keys are yours to install at first boot |
| An encryption passphrase stored off every server involved | An encrypted backup whose key died with the server is shredded data | A password manager or printed copy - not a file on either box |
Two of these rows are budget lines, and they are small ones: a Nano target at the flat $36/yr plus a $24/mo storage tier keeps a serious 3-2-1 setup under $35 a month even with the source box on Starter. Pay for the year on both at -20% and the same stack drops by a fifth.
Installing restic and making the first encrypted backup
restic is the pragmatic default for a VPS-to-VPS job: content-defined chunking, client-side AES-256 encryption, deduplication across snapshots, and a single binary. Debian and Ubuntu ship it in the base repositories.
- Install on the source server:
apt update && apt install -y restic, then confirm the version withrestic version. - Give restic SSH access to the target. As root on the source:
ssh-keygen -t ed25519 -f /root/.ssh/backup_ed25519 -N '', then append the public key to/root/.ssh/authorized_keyson the target, restricted to the restic command if you want to be strict. - Initialize the repository once, from the source:
export RESTIC_REPOSITORY='sftp:backup@target-host:/srv/backup/repo'andexport RESTIC_PASSWORD_FILE=/root/.restic-pass(a file containing only the passphrase, chmod 600). Thenrestic init- expectcreated restic repositorywith a repository ID. - Run the first backup of real state:
restic backup /etc /home /srv/www /var/lib/psql-dumps --tag base. The first pass uploads everything; on a 10 GB data set over a 1-10 Gbps unmetered port this is minutes, not hours. - Check what you made:
restic snapshotsshould list one snapshot with thebasetag, andrestic stats latestprints the total size after deduplication - usually well under the raw data size once later snapshots share chunks.
Two habits worth forming on day one. First, back up logical dumps (database exports) rather than live database file trees - pg_dump output restores cleanly, a copied /var/lib/postgresql under load often does not. Second, keep the include list in a script under /root, not in your head, because the restore you run in a year will be performed by a calmer version of you or by someone else entirely.
Automating the schedule with systemd timers
cron works, but a systemd timer gives you journal logs, dependency ordering and a persistent marker, and it survives reboots without a crontab surprise. Write a service unit and a timer unit, enable the timer, and the schedule is inspectable with one command.
- Create
/root/restic-backup.sh: it exportsRESTIC_REPOSITORYandRESTIC_PASSWORD_FILE, dumps the databases to a staging directory, runsrestic backupwith your paths, thenrestic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 6 --prune. Exit non-zero on any step failing so the timer records a failure. - Create
/etc/systemd/system/restic-backup.serviceof type oneshot calling that script, withAfter=network-online.target. - Create
/etc/systemd/system/restic-backup.timerwithOnCalendar=*-*-* 03:15:00andPersistent=trueso a powered-off window does not skip the run. - Enable it:
systemctl daemon-reload && systemctl enable --now restic-backup.timer, thensystemctl list-timers restic*- you should see the next run scheduled. - Trigger one manual run with
systemctl start restic-backup.serviceand read the output withjournalctl -u restic-backup.service -n 50. Expect the restic summary line: files added, bytes uploaded, snapshot ID.
Set the window off your busiest hour; the run itself is network-light after the first pass because only changed chunks travel. If you keep the target in a different rack from the source - Reykjavik source, Amsterdam target, for example, at ~18 ms apart - the nightly delta finishes in seconds and the two machines do not share a maintenance window, which is the property you are actually paying for.
How to verify it worked: restore drills, not green dashboards
A backup that has never been restored is a hypothesis. The verification step that matters is a timed restore of a representative slice onto scratch space, run monthly, with the output written down.
- Mount or extract without touching production:
restic restore latest --target /tmp/restore-test --include /etc. Expect the directory tree to appear under/tmp/restore-test/etc. - Diff against the live system:
diff -rq /tmp/restore-test/etc /etc | head. A handful of changed runtime files is normal; a tree that is empty or missing files you know exist is a failed job, not a passed one. - Restore a database dump into a throwaway instance and run a row count against the source. This is the test people skip and the one that catches silent corruption.
- Time it.
time restic restore latest --target /tmp/full-teston the full snapshot tells you your real recovery point objective in minutes. Write that number next to the passphrase - it is the only SLA that is genuinely yours. - Once a quarter, restore from the third copy - the storage tier - not just the near target, and from a second machine entirely, to prove the repository is not quietly tied to one host's cache.
Automate the smoke test: append the restic restore --include /etc step to the backup script and have it diff, so a backup that completes but restores empty pages your journal immediately. Green checkmarks from a job that never ran a restore are how people discover, during an incident, that they backed up an empty directory for eight months.
Troubleshooting: symptom, cause, fix
Four failure patterns cover most of what goes wrong with a restic-over-SSH setup. Each has a signature you can recognize in the journal within a minute.
| Symptom | Likely cause | Fix |
|---|---|---|
restic backup hangs at 'load index' then times out | SSH to the target dropped - firewall change, wrong host key after a redeploy, or the target ran out of disk | ssh backup@target-host df -h first; if disk, prune (restic forget --prune) or grow the plan; if host keys, update known_hosts |
Snapshot runs but restic check reports damaged packs | Interrupted run left partial data, or disk errors on the target | Run restic check --read-data once; rebuild affected packs from a good snapshot; if the target disk repeats it, stop trusting that machine |
| Repository grows without new snapshots | Prune step failing silently or cache on the source bloated | Run restic forget --prune manually and read its output; clear /root/.cache/restic if the cache exceeds a few GB |
| Timer shows 'failed' with no restic output | Script exits before restic - usually the database dump step | journalctl -u restic-backup.service -n 100; fix the dump credential or staging path, rerun manually before re-enabling |
The meta-fix for all four is alerting on the timer result, not the absence of errors. A dead timer produces silence, and silence looks identical to 'no news' unless something watches systemctl list-timers or the journal for the unit.
Offsite copy with rclone: pushing the second copy to Storage
The target VPS and the source VPS are two failure domains, but both are machines you administer, which means both can be lost to the same mistake - a bad script, a compromised key, a deleted repository. The third copy should be a service that only accepts writes, which is where rclone against a Storage box earns its keep.
- Install rclone on the source:
apt install -y rclone. - Configure the remote for SFTP against your Storage plan (1-5 TB NVMe from $24/mo):
rclone config, choose sftp, point it at the storage host and a dedicated user key. - Sync the repository nightly, after the restic job:
rclone sync /srv/backup/repo storage:repo-remote --backup-dir storage:repo-attic/$(date +\%Y-\%m-\%d). The backup-dir keeps overwritten objects instead of deleting them, which bounds the damage of syncing a corrupted repository - you can roll back a day. - Verify with checksums:
rclone check /srv/backup/repo storage:repo-remoteshould report no differences. Add it to the monthly drill, not to every run.
Keep the storage credentials different from the target credentials - separate SSH key, ideally a separate machine - so one leaked key cannot destroy all three copies. That separation, plus the passphrase stored off both servers, is the difference between 3-2-1 on paper and 3-2-1 in fact.
Sizing the pair on this network, and where to start
Putting the whole guide on the price sheet: a Starter source at $8.50/mo, a Nano or second-Starter target in another rack, and the 1 TB Storage tier at $24/mo for the offsite copy. Annual terms take -20% off the two VPS lines (quarter is -10%), and the fund-account bonus tiers - +10% at $1,000, +40% at $2,000 - effectively pre-discount the balance you pay all of it from. Payments run in any of 19 assets: BTC, XMR, USDT on seven networks including TRC-20, and sixteen others; signup is token-only with optional email, so the backup infrastructure does not need to know who you are any more than your keys do.
BitCloudVPS Starter · Amsterdam · $6.80/mo on the annual term
Deploy the source nodeBitCloudVPS Storage · 1 TB NVMe · from $24/mo
Add the offsite copyPruning, retention and disk math: keeping the repository alive for years
The direct answer: a restic repository grows for exactly two reasons — new snapshots and trees you keep referencing — so your retention policy, not your backup job, decides what the target disk costs. Run restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune after every backup run and the repository stabilises at roughly three to five times the size of one full snapshot for a typical server with slow-changing data. Skip the prune for six months and the same repository can eat ten times that, because every snapshot re-points at the blocks that never changed.
Do the math before you pick the box. A 40 GB server snapshotting daily for a week plus weekly and monthly keeps lands comfortably under 200 GB with pruning on — which fits the 60 GB NVMe of a Starter ($8.50/mo) only if the source is half that size, so most people run the repository on the Storage line instead: 1–5 TB of NVMe from $24/mo, with up to 8 vCPU and 32 GB RAM at the top tier if you also want to run verification and housekeeping on the same box. Restic is single-threaded around packing, so a busy repository benefits more from fast NVMe than from cores. Add a monthly restic check --read-data to a systemd timer and let the prune timer run at 04:00 when nobody is pulling from the box. One caveat restic cannot fix for you: prune and backup must never run concurrently on the same repository — gate them in one script with flock so a slow prune postpones the next snapshot instead of corrupting the index.
Ransomware and the append-only copy: making the backup something the attacker cannot delete
The direct answer: assume the source server's root key ends up in someone else's hands, and design so that alone buys them nothing. Restic supports append-only repositories: the server-side credentials can write new data but cannot remove or overwrite existing packs, so a compromised production box can pollute the repository but not erase history. You set it up by serving the repository over rest-server --append-only (or rclone's restic backend in append-only mode) and giving the production host only that restricted endpoint; the prune job then runs from a third place — your workstation or the storage box itself — using full credentials that the source server never sees.
Layer the rest of the discipline around that split. Keep the source server's backup credentials in /root/.restic with mode 600 and nothing in shell history; run the job under a dedicated user, not root, if the paths allow it. Test the blast radius once on purpose: from the production box, try restic forget --prune against the append-only endpoint and confirm it fails. That failure is the whole point of the design. Finally, keep a second copy outside this pair entirely — a quarterly restic copy or rclone sync to another location is enough — because one provider, two boxes, is 2-2-1, not 3-2-1, and no host feature changes that. Both boxes take the same 19-asset crypto checkout and the same -20% annual term, so the immutable-pair setup costs one more plan line and zero additional identity: signup is token-only, no KYC at any tier.
Where the limits are (October 2026)
Keep the threat model precise. Anonymous-adjacent hosting removes the host-side identity trail: token-only signup, optional email, no KYC review, crypto invoices that record a txid rather than a name. It does not blind the host to traffic it must carry, it does not make a logged-in panel session anonymous, and it does not protect workloads that identify themselves — an admin login from your home IP defeats the whole design. Exact limits of no-KYC anonymity. OPSEC guide. Full checklist. Payment-side note: chain analysis happens on public blockchains, not at the host. Coin choice and network choice are your OPSEC knobs, and the checkout keeps 19 of them open.
Are snapshots backups?
They are fast rollbacks stored on the same platform. For provider-level or ransomware events you want an off-box, encrypted copy too.
How often should I back up?
Databases: continuous archiving or daily at minimum. Files: daily. Test the restore at least quarterly.
Can I back up to BitCloudVPS Storage from another provider?
Yes — any server can push to your Storage VPS over SSH/SFTP with Borg or Restic.
Does restic encrypt backups before they leave the server?
Yes. restic encrypts every chunk with AES-256 and authenticates with Poly1305 before writing, so data crossing to the target or the storage tier is already ciphertext. The passphrase never travels; whoever holds it can read the repository, whoever does not, cannot.
How do I test a restore without touching production?
Restore into a scratch directory with --target /tmp/restore-test, or into a second VPS entirely, and diff against the live tree. For databases, load the dump into a throwaway instance and compare row counts. Never restore over the live path to 'check' it.
What is the fastest recovery path after ransomware hits the VPS?
Redeploy a fresh instance from the catalog image, install restic, and restore the newest snapshot that predates the incident - verify its timestamps, since attackers often encrypt after the backup window mounts state. Then rotate every SSH key and passphrase before reconnecting the backup repository.
Do backups count against my disk quota on the target?
Yes - a backup repository is ordinary files on the target VPS or storage plan, so it consumes that plan's NVMe. Budget the repository size plus prune headroom, and let restic forget --keep-daily/--keep-weekly/--keep-monthly bound growth automatically.
Should I ever back up to the same disk the VPS runs on?
Only as a transient staging area. A snapshot written to the same NVMe the server runs on dies with the server, survives neither a ransomware event nor a host failure, and competes with production for the same disk. Use it for a first nightly pass if you must, then rclone or restic-copy it off the box within hours. Everything older than a day should live on a different machine at minimum — a Storage box on a separate plan is the cheap way to do that on this network.
How do I back up a PostgreSQL or MySQL database without corrupting the snapshot?
Never tarball a live data directory mid-write. For PostgreSQL use <code>pg_dump</code> or <code>pg_basebackup</code>; for MySQL use <code>mysqldump --single-transaction</code> or a snapshot with the binary log position recorded. Pipe dumps straight into restic via stdin (<code>pg_dump mydb | restic backup --stdin --stdin-filename mydb.sql</code>) so no unencrypted dump ever touches disk. Verify quarterly that the dump actually restores into a scratch container — a dump that has never been restored is a hope, not a backup.
What actually belongs in a backup versus what can be rebuilt?
The irreplaceable state is small: databases, uploaded files, certificates and configs. The OS is reproducible — restore speed comes from automating its setup with a short Ansible or Cloud-init script rather than a golden image you forget to update. Follow the 3-2-1 rule with one tested restore.
What does the host actually know about me?
Whatever you hand over: a signup token, an optional email address, the invoice you paid and the service traffic. No ID documents, no phone number, no KYC step exists to hand anything else over.
What won't an anonymous VPS protect against?
Self-inflicted links: personal logins from a home IP, leaked cookies, services that publish your address, and on-chain patterns that tie wallets together. The server removes one identifier; it does not fix operational mistakes.
Ready to launch?
Configure a VPS or dedicated server, set a password, then pay the invoice.
Network
Edge RTT
Uptime · 36h
Complaints · 36h