Home / Guides / VPS Backup and Restore — The 3-2-1 Pattern on a Root Server
How-to

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.

BitCloudVPS Tor relay abstract: layered light rings around a node
Short answer

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 now

The pattern

On BitCloudVPS

  1. Snapshots are an add-on at checkout on VPS/Storage — good for pre-change safety, not a backup strategy by themselves.
  2. 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.
  3. 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 needWhyMinimum on this network
A source VPS with room to breatherestic and borg need scratch space for the index and cache, roughly 5-10% of the data setStarter, $8.50/mo - 2 vCPU EPYC 7443P, 4 GB ECC, 60 GB NVMe
A second machine as the backup targetSame-provider is acceptable only on a different rack; same host is not a backupA second Starter, or Nano at $5.49/mo ($36/yr flat) for small state
Bulk capacity for archives and long retentionWeekly fulls of media or mailboxes grow past any boot disk quicklyStorage plans, 1-5 TB NVMe from $24/mo
SSH key access to both machinesThe backup job must run without a password prompt; passwords in scripts leak into process listsAny plan; keys are yours to install at first boot
An encryption passphrase stored off every server involvedAn encrypted backup whose key died with the server is shredded dataA 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.

  1. Install on the source server: apt update && apt install -y restic, then confirm the version with restic version.
  2. 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_keys on the target, restricted to the restic command if you want to be strict.
  3. Initialize the repository once, from the source: export RESTIC_REPOSITORY='sftp:backup@target-host:/srv/backup/repo' and export RESTIC_PASSWORD_FILE=/root/.restic-pass (a file containing only the passphrase, chmod 600). Then restic init - expect created restic repository with a repository ID.
  4. 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.
  5. Check what you made: restic snapshots should list one snapshot with the base tag, and restic stats latest prints 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.

  1. Create /root/restic-backup.sh: it exports RESTIC_REPOSITORY and RESTIC_PASSWORD_FILE, dumps the databases to a staging directory, runs restic backup with your paths, then restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 6 --prune. Exit non-zero on any step failing so the timer records a failure.
  2. Create /etc/systemd/system/restic-backup.service of type oneshot calling that script, with After=network-online.target.
  3. Create /etc/systemd/system/restic-backup.timer with OnCalendar=*-*-* 03:15:00 and Persistent=true so a powered-off window does not skip the run.
  4. Enable it: systemctl daemon-reload && systemctl enable --now restic-backup.timer, then systemctl list-timers restic* - you should see the next run scheduled.
  5. Trigger one manual run with systemctl start restic-backup.service and read the output with journalctl -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.

  1. 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.
  2. 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.
  3. 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.
  4. Time it. time restic restore latest --target /tmp/full-test on 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.
  5. 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.

SymptomLikely causeFix
restic backup hangs at 'load index' then times outSSH to the target dropped - firewall change, wrong host key after a redeploy, or the target ran out of diskssh 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 packsInterrupted run left partial data, or disk errors on the targetRun 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 snapshotsPrune step failing silently or cache on the source bloatedRun 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 outputScript exits before restic - usually the database dump stepjournalctl -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.

  1. Install rclone on the source: apt install -y rclone.
  2. 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.
  3. 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.
  4. Verify with checksums: rclone check /srv/backup/repo storage:repo-remote should 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 node

BitCloudVPS Storage · 1 TB NVMe · from $24/mo

Add the offsite copy

Pruning, 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

Pick a city