How to Set Up Restic Encrypted Backups on Ubuntu 24.04 VPS
A VPS without backups is a VPS waiting to lose data. Disks fail, rm -rf happens, ransomware encrypts what you did not encrypt first, and "I will set up backups tomorrow" is a phrase every sysadmin regrets at least once. This guide walks you through installing Restic on Ubuntu 24.04, initialising an encrypted repository on S3-compatible object storage, running your first backup, automating nightly runs with a systemd timer, and rehearsing the restore before you actually need it.
Need a VPS to back up from or to? The CloudCore Starter plan gives you 100 GB of NVMe for your workload plus room to spin up a second box as your MinIO backup target. Deploy in 60 seconds.
Table of Contents
forget and prunerestic checkWhy Restic?
Restic is an open-source backup tool written in Go that has quietly become the default choice for Linux admins who want encryption, deduplication, and cross-cloud portability without wrestling with a dozen config files. It ships as a single static binary with no runtime dependencies, which means installing it on a fresh Ubuntu 24.04 server is essentially a file copy.
The feature list is short, opinionated, and directly aligned with what modern infrastructure needs:
- Encrypted by default -- Every byte written to the repository is encrypted client-side with AES-256 in counter mode, authenticated with Poly1305-AES. You cannot accidentally create an unencrypted repo. Whoever holds your storage (AWS, Backblaze, a compromised VPS) cannot read your data.
- Content-defined deduplication -- Restic splits files into variable-sized chunks using a rolling hash (CDC), and identical chunks are stored only once. Back up 50 identical WordPress sites and you pay the storage cost of roughly one.
- Incremental forever -- After the first snapshot, every subsequent backup only uploads new or changed chunks. A nightly backup of a 200 GB server typically transfers a few hundred megabytes.
- Works with everything -- Native backends for Amazon S3, Backblaze B2, Google Cloud Storage, Azure Blob, MinIO, Wasabi, any S3-compatible service, SFTP, local disks, REST server, and anything
rclonecan reach. - BSD 2-Clause licensed -- Use it commercially, embed it in products, ship it with appliances. No license fees, no "open core" upsell.
- Atomic snapshots -- Each backup is a full, self-contained snapshot from the user's perspective, even though the storage layer is incremental. Restoring a snapshot never requires reassembling a chain of fulls + incrementals like tar-based tools.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- A storage target -- one of: an S3 bucket (AWS, Wasabi, Backblaze B2, MinIO on a second VPS), an SFTP-accessible server, or a mounted external disk
- At least 1 GB of free RAM for Restic to run comfortably on large repos
- Disk headroom equal to 1-2% of your protected data for Restic's local cache
Recommended Plan: CloudCore Starter>
For most single-server backup setups, CloudCore Starter is enough:>
- 4 vCPU cores
- 8 GB RAM
- 100 GB NVMe SSD
- Unmetered bandwidth>
Many users deploy two CloudCore Starter instances in different regions: one as the production server, the second as a MinIO target. That way your backups live on hardware you control, encrypted with a key only you hold.
Connect to your server via SSH:
ssh root@your-server-ipStep 1: Update System Packages
Start by bringing the package index current. Ubuntu 24.04 LTS ships a reasonably recent Restic in the official universe repository, and keeping the system patched avoids surprises during install.
sudo apt update && sudo apt upgrade -yIf the kernel or glibc was updated, reboot before continuing:
sudo rebootReconnect once the server is back.
Step 2: Install Restic
Ubuntu 24.04 includes Restic in the universe component, so apt is the fastest way to get a working binary:
sudo apt install -y resticVerify the install:
restic versionExpected output:
restic 0.16.4 compiled with go1.22.0 on linux/amd64The Ubuntu-packaged version lags a few minor releases behind upstream. That is fine for most users, but if you want the latest features (for example, compression, which became the default in 0.16) or a specific fix, grab the binary directly from the release page:
RESTIC_VERSION=0.17.1
curl -L https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_amd64.bz2 \
| bunzip2 > /usr/local/bin/restic
sudo chmod +x /usr/local/bin/restic
restic versionFrom here on, either restic path works identically.
Enable shell completion so tab-completing subcommands and flags Just Works:
restic generate --bash-completion /etc/bash_completion.d/resticStep 3: Choose and Configure a Repository Backend
A Restic repository is just a directory structure (config, keys/, snapshots/, data/, index/, locks/) that Restic can reach through one of its backends. The choice of backend determines how you address the repo.
The three backends that cover 95% of real deployments:
Option A: S3-Compatible (MinIO, Backblaze B2, Wasabi, AWS S3)
This is the standard choice for offsite backups. Any service that speaks the S3 API works, and the URL syntax is uniform:
export RESTIC_REPOSITORY="s3:https://s3.eu-central-003.backblazeb2.com/my-restic-bucket"
export AWS_ACCESS_KEY_ID="your-key-id"
export AWS_SECRET_ACCESS_KEY="your-application-key"The endpoint changes per provider:
- Backblaze B2 (S3 API):
s3:https://s3.<region>.backblazeb2.com/<bucket> - Wasabi:
s3:https://s3.<region>.wasabisys.com/<bucket> - AWS S3:
s3:s3.amazonaws.com/<bucket>(region inferred from creds) - MinIO on your own VPS:
s3:https://minio.yourdomain.com/<bucket>
Option B: SFTP to Another Server
Simplest and zero-cost if you already have a second box:
export RESTIC_REPOSITORY="sftp:[email protected]:/srv/restic/myserver"Restic will use your SSH keys -- set up ~/.ssh/config so backup-host.example.com resolves to the right user, port, and identity file.
Option C: Local Disk or Mounted Volume
Useful as a staging target before pushing offsite, or when you have attached block storage:
export RESTIC_REPOSITORY="/mnt/backup/restic"Store the Password Securely
Whichever backend you pick, Restic needs a repository password at init and every subsequent operation. If you lose this password, your data is unrecoverable. There is no recovery mechanism. This is the cost of strong encryption.
Create a password file readable only by root:
sudo mkdir -p /etc/restic
openssl rand -base64 48 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/passwordTell Restic where to find it:
export RESTIC_PASSWORD_FILE=/etc/restic/passwordBack this password up separately -- in a password manager, a sealed envelope in a safe, a second admin's keychain. Losing /etc/restic/password without a copy means losing the backup.
Persist Environment Variables
Drop all the variables into an env file so cron, systemd, and interactive sessions share one source of truth:
sudo tee /etc/restic/restic.env > /dev/null <<'EOF'
RESTIC_REPOSITORY=s3:https://s3.eu-central-003.backblazeb2.com/my-restic-bucket
RESTIC_PASSWORD_FILE=/etc/restic/password
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-application-key
EOF
sudo chmod 600 /etc/restic/restic.envFor interactive use:
set -a; source /etc/restic/restic.env; set +aStep 4: Initialise the Encrypted Repository
With environment variables loaded, initialise the repo. This is a one-time operation that writes the encryption keys and empty index structures to the backend.
restic initExpected output:
created restic repository 8e1a7b2cc3 at s3:https://s3.eu-central-003.backblazeb2.com/my-restic-bucket
Please note that knowledge of your password is required to access the repository. Losing your password means that your data is irrecoverably lost.
If you see Fatal: create repository ... failed: repository master key and config already initialized, the repository already exists -- either you are re-running against a populated target, or somebody got here first. Do not re-init; that would destroy existing data.
Step 5: Run Your First Backup
The backup subcommand takes one or more paths and an optional tag. Tags are free-form strings you use later to filter snapshots.
Back up /var/www (typical webserver document root):
restic backup /var/www --tag webroot --tag dailyExpected output:
repository 8e1a7b2c opened (version 2, compression level auto) created new cache in /root/.cache/restic no parent snapshot found, will read all filesFiles: 2743 new, 0 changed, 0 unmodified Dirs: 318 new, 0 changed, 0 unmodified Added to the repository: 612.447 MiB (158.221 MiB stored)
processed 2743 files, 1.204 GiB in 0:47 snapshot 4f2e8a1c saved
A few things to notice:
- "Added to the repository" shows raw bytes and compressed/stored bytes. Restic 0.14+ enables zstd compression by default; expect 50-70% savings on text-heavy workloads.
- "snapshot 4f2e8a1c" is the first 8 hex chars of the snapshot ID. You will reference this in
ls,restore, andforgetcommands. - The first run reads every file. Subsequent runs walk the filesystem, compare modification times and sizes against the parent snapshot, and only re-read files that look changed.
Backing Up Multiple Paths
Pass as many paths as you like:
restic backup /etc /home /var/www /srv --tag nightlyExcluding Files
Large, regenerable, or sensitive paths should be skipped. Create an exclude file:
sudo tee /etc/restic/excludes.txt > /dev/null <<'EOF'
Caches and build artefacts
**/node_modules
**/.cache
**/__pycache__
*.log.gz
Volatile databases (back these up via dump, not raw files)
/var/lib/mysql
/var/lib/postgresql
Mounted shares
/mnt
/media
Swap and tmp
/swap.img
/tmp
EOFPass it to backup:
restic backup / \
--exclude-file=/etc/restic/excludes.txt \
--one-file-system \
--tag system--one-file-system prevents descent into bind mounts and virtual filesystems like /proc, /sys, /dev.
Database Backups
Do not back up live /var/lib/mysql or /var/lib/postgresql as raw files -- you will capture mid-write pages and the restore will be corrupt. Dump first, then let Restic pick up the dump file:
# Postgres
sudo -u postgres pg_dumpall | gzip > /var/backups/pg_all_$(date +%F).sql.gzMySQL / MariaDB
mysqldump --all-databases --single-transaction --quick | gzip > /var/backups/mysql_all_$(date +%F).sql.gzThen back up /var/backups along with the rest
restic backup /var/backups /etc /var/www --tag nightlyStep 6: List and Inspect Snapshots
After a few backups, list what you have:
restic snapshotsExpected output:
ID Time Host Tags Paths
-----------------------------------------------------------------------------
4f2e8a1c 2026-04-15 03:00:12 web-01 webroot,daily /var/www
91c3d7b2 2026-04-16 03:00:08 web-01 webroot,daily /var/www
a7e4f0d9 2026-04-16 03:00:08 web-01 system /etc /home /var/www
-----------------------------------------------------------------------------
3 snapshotsFilter by tag, host, or path:
restic snapshots --tag daily
restic snapshots --host web-01 --path /var/wwwList the contents of a snapshot:
restic ls 91c3d7b2 | head -20Diff two snapshots to see exactly what changed:
restic diff 4f2e8a1c 91c3d7b2Expected output:
comparing snapshot 4f2e8a1c to 91c3d7b2:M /var/www/html/index.php
+ /var/www/html/new-page.html
- /var/www/html/old-banner.jpg
Files: 1 new, 1 removed, 1 changed
Dirs: 0 new, 0 removed
Others: 0 new, 0 removed
Data Blobs: 3 new, 4 removed
Tree Blobs: 2 new, 2 removed
Added: 1.823 MiB
Removed: 984.214 KiBStep 7: Restore from a Snapshot
Restic offers two restore workflows. Pick based on what you need back.
Full Restore
Restore the latest snapshot's contents to a staging directory:
mkdir -p /tmp/restore
restic restore latest --target /tmp/restorelatest resolves to the newest snapshot for the current host and paths. Use an explicit ID for precision:
restic restore 91c3d7b2 --target /tmp/restoreRestore only specific paths from a snapshot:
restic restore 91c3d7b2 --target /tmp/restore --include /var/www/htmlOnce the restore is verified, move files into place with rsync -a. Never restore directly over a running service's live directory -- always stage to /tmp/restore or /var/tmp/restore first.
Mount and Browse (FUSE)
For recovering a single file without pulling down the whole snapshot, mount the repo as a FUSE filesystem:
sudo apt install -y fuse
mkdir -p /mnt/restic
restic mount /mnt/resticIn another terminal:
ls /mnt/restic/snapshots/
cp /mnt/restic/snapshots/2026-04-16T03:00:08/var/www/html/index.php /root/recovered-index.phpPress Ctrl+C in the original terminal to unmount.
This is the single most useful feature Restic offers for day-to-day ops. A user asks "hey, can you pull me last Tuesday's version of that one config file?" and you answer in 30 seconds without downloading the whole snapshot.
Step 8: Apply a Retention Policy with forget and prune
Without a retention policy, snapshots accumulate forever. restic forget removes snapshot pointers according to rules you define; restic prune then reclaims the underlying storage.
A sensible default for production servers:
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 \
--keep-yearly 3 \
--pruneThis keeps:
- The 7 most recent daily snapshots
- One per week for the last 4 weeks (distinct from the dailies)
- One per month for the last 12 months
- One per year for the last 3 years
--prune flag runs repository garbage collection in the same command. On large repos this can take 10-30 minutes; for nightly automation, run forget without --prune and schedule prune weekly instead:# Nightly
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --keep-yearly 3Weekly (Sunday)
restic pruneTo apply retention only to a tag:
restic forget --tag daily --keep-daily 14 --pruneStep 9: Automate with a systemd Service and Timer
Cron works, but a systemd service + timer gives you structured logs, dependency ordering, and proper failure handling. This is the right tool on Ubuntu 24.04.
Create the backup script:
sudo tee /usr/local/sbin/restic-backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefailset -a
source /etc/restic/restic.env
set +a
HOSTNAME="$(hostname -s)"
DUMP_DIR=/var/backups/db
mkdir -p "$DUMP_DIR"
Dump databases first (uncomment what applies)
sudo -u postgres pg_dumpall | gzip > "$DUMP_DIR/pg_all.sql.gz"
mysqldump --all-databases --single-transaction --quick | gzip > "$DUMP_DIR/mysql_all.sql.gz"
Main backup
restic backup \
/etc /home /root /srv /var/www "$DUMP_DIR" \
--exclude-file=/etc/restic/excludes.txt \
--one-file-system \
--tag nightly \
--tag "$HOSTNAME"Retention (forget only; prune runs weekly from a separate timer)
restic forget \
--tag nightly \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 \
--keep-yearly 3
EOF
sudo chmod 700 /usr/local/sbin/restic-backup.shCreate the systemd service unit:
sudo tee /etc/systemd/system/restic-backup.service > /dev/null <<'EOF'
[Unit]
Description=Restic nightly backup
Wants=network-online.target
After=network-online.target[Service]
Type=oneshot
Nice=19
IOSchedulingClass=idle
ExecStart=/usr/local/sbin/restic-backup.sh
Lock the repo automatically if a previous run left a stale lock (rare)
ExecStartPre=/usr/bin/restic unlock || true
EOFCreate the timer unit:
sudo tee /etc/systemd/system/restic-backup.timer > /dev/null <<'EOF' [Unit] Description=Run Restic backup nightly[Timer] OnCalendar=--* 03:00:00 RandomizedDelaySec=30m Persistent=true
[Install] WantedBy=timers.target EOF
Persistent=true ensures that if the VPS was off at 03:00, the backup runs on next boot. RandomizedDelaySec=30m staggers runs so you are not hammering your S3 endpoint from a fleet of servers at the same second.
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timerCheck scheduling:
systemctl list-timers restic-backup.timerRun the backup immediately to validate end-to-end:
sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service -fFor prune, create a parallel restic-prune.service + restic-prune.timer running OnCalendar=Sun 04:00:00 weekly.
If you want a systemd primer beyond this, see our guide on writing robust systemd services.
Step 10: Verify Integrity with restic check
A backup you have not tested is a wish. restic check walks the repository and verifies that every pack file is well-formed and that indexes match reality:
restic checkExpected output:
using temporary cache in /tmp/restic-check-cache-2637485913
created new cache in /tmp/restic-check-cache-2637485913
create exclusive lock for repository
load indexes
check all packs
check snapshots, trees and blobs
no errors were foundFor deeper verification, sample-read actual data blobs:
restic check --read-data-subset=5%This downloads and cryptographically verifies 5% of data packs. Run it monthly. For the paranoid, --read-data reads 100% (expensive -- the whole repo gets re-downloaded).
Cold-Restore Rehearsal
Checking the repo proves the repo is intact. It does not prove you can actually recover. Once a quarter, rehearse a true cold restore:
/etc/restic/password on the source server).restic restore latest --target /recovered.If this fails, you find out on a Tuesday afternoon rather than during a disaster at 3 a.m.
Offsite Strategy
The oft-quoted 3-2-1 rule (three copies, two media, one offsite) applies. A few patterns that work well with Restic:
Pattern A: Primary Server -> Backblaze B2
Cheapest and simplest. B2 storage is ~$0.006/GB/month, egress to Cloudflare is free via their Bandwidth Alliance partnership. For a 100 GB backup retained with the policy above (deduped to ~40 GB typical), you pay under 30 cents per month.
Pattern B: Primary Server -> Self-Hosted MinIO on a Second VPS
If your compliance posture rules out third-party object storage, run MinIO on a second VPS in a different region. Restic treats MinIO exactly like AWS S3. A CloudCore Starter in a different region gives you fully-controlled, encrypted-at-rest-and-in-transit backups for a flat monthly price with no egress charges.
See our guide on deploying MinIO on Ubuntu 24.04 for the receiving side.
Pattern C: Primary Server -> Local Mount -> rclone -> Cloud
If your VPS has attached block storage, back up to local first (fast), then replicate the repo directory to cloud overnight using rclone sync. This optimises for restore speed (local is fast) while keeping the offsite copy.
Whichever pattern you pick, ensure the offsite credentials are append-only or write-only where the backend supports it (B2 application keys, AWS IAM). Ransomware that reaches the source server should not be able to delete backups.
Alerting and Monitoring
A silent backup that silently stopped working six weeks ago is worse than no backup -- you are planning around data you no longer have. Wire up failure alerts.
Healthchecks.io (Free Tier)
Sign up at healthchecks.io, create a check with grace period 90 minutes, copy the ping URL, and append to your backup script:
# At the very end of restic-backup.sh
curl -fsS --retry 3 https://hc-ping.com/your-check-uuid > /dev/nullTo distinguish failures from silent non-runs, use the start/failure endpoints:
curl -fsS https://hc-ping.com/your-check-uuid/start > /dev/null
restic backup ... || { curl -fsS https://hc-ping.com/your-check-uuid/fail > /dev/null; exit 1; }
curl -fsS https://hc-ping.com/your-check-uuid > /dev/nullHealthchecks emails (or Slacks, or PagerDuty-pages) you the moment a run misses its window.
systemd OnFailure
As a belt-and-braces backup, add email-on-failure to the service unit:
[Unit]
OnFailure=status-email@%n.serviceCombined with a sendmail-ready MTA (Postfix + a transactional provider), any non-zero exit from the service lands in your inbox within seconds.
Restic vs. Borg vs. Duplicity vs. Kopia
All four are legitimate choices. Short version of how they compare:
| Feature | Restic | Borg | Duplicity | Kopia |
|---|---|---|---|---|
| Language | Go (single binary) | Python + C | Python | Go (single binary) |
| Encryption | AES-256 + Poly1305, always on | AES-CTR + HMAC, always on | GPG (optional) | AES-256-GCM, always on |
| Deduplication | CDC (variable chunks) | CDC (variable chunks) | None (tar chains) | CDC (variable chunks) |
| Cloud-native backends | Yes (S3, B2, GCS, Azure, rclone) | No (SSH only, needs rclone) | Yes (many) | Yes (S3, B2, GCS, Azure, SFTP) |
| Compression | zstd (default in 0.14+) | lz4/zstd/zlib | gzip/bzip2 | zstd |
| Multi-client to one repo | Yes (concurrent) | Serialised (one at a time) | Serialised | Yes (concurrent) |
| GUI | Third-party | Third-party | No | Built-in (Kopia UI) |
| Mature | Yes | Yes | Yes (older) | Newer (2019+) |
Pick Borg when your targets are always SSH-accessible and you value Borg's slightly better compression ratio on some workloads. Borg's pull model (backup server pulls from clients) has a security edge in some threat models.
Pick Duplicity if you are already running it and it works. New deployments should skip it -- no deduplication means it balloons on large or churny datasets.
Pick Kopia if you want a GUI, policy-per-directory configuration, and are comfortable with a slightly newer project. It is the most feature-rich of the four in pure capability terms.
For a typical Ubuntu VPS shop, Restic is the path of least regret.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Fatal: unable to open config file: ... Is there a repository at the following location? | Repository not initialised at that URL, or credentials wrong | Run restic init. If already initialised elsewhere, double-check RESTIC_REPOSITORY and bucket name. |
Fatal: wrong password or no key found | Wrong password, or wrong repo pointed at | Verify RESTIC_PASSWORD_FILE path. If the password is truly lost, the data is unrecoverable -- there is no backdoor. |
unable to create lock in backend: repository is already locked | Previous run crashed, or a concurrent operation is running | Check for running restic processes: pgrep -a restic. If none, clear stale lock: restic unlock. |
prune runs for hours | Large repo, many small snapshots, many deleted blobs | Expected on first-ever prune after a long retention buildup. Subsequent prunes are fast. Use --max-repack-size 10G to chunk the work. |
Fatal: StorageError: ... 403 Forbidden on S3 | IAM / bucket policy does not grant required actions | Ensure the key has s3:ListBucket, s3:GetObject, s3:PutObject, s3:DeleteObject on the bucket and its objects. |
| Slow first backup over the internet | Normal -- first run uploads everything | Consider seeding: run initial backup to a local repo, then rclone sync the repo to S3. Subsequent runs are incremental and fast. |
backup skips changed files | Filesystem modification times are not updating (some network filesystems) | Pass --ignore-ctime --ignore-inode cautiously, or --force to re-scan all files. |
| Memory usage ballooning on large repos | Index loaded into RAM | Normal. Expect ~1 GB RAM per ~5 TB logical data. If constrained, add swap or run backups from a larger box. |
| Backup succeeded but restore produces empty files | Source files were being written during backup (e.g., live DB) | Always dump databases to files first, then back up the dumps. Never back up running DB data directories raw. |
Viewing Logs
sudo journalctl -u restic-backup.service -n 100 --no-pager
sudo journalctl -u restic-backup.service --since "1 week ago"For interactive debugging, crank verbosity:
restic -v backup /var/www
restic --verbose=3 check # Very chattyFAQ
What happens if I forget the repository password?
Your data is gone. Restic's encryption has no master key, no recovery phrase, no support hotline. This is a feature -- it is what makes the backup genuinely useless to whoever steals the storage -- but it means you must treat the password as irreplaceable. Store it in at least two independent locations: a team password manager (1Password, Bitwarden, Vaultwarden) and a printed copy in a sealed envelope in a physical safe. Test recovery from each location once a year.
Can multiple servers back up to the same repository?
Yes. Restic is designed for concurrent writers to a single repository. Each server identifies itself via hostname, and snapshots are tagged accordingly. Deduplication works across servers -- if ten VPSes all have the same Ubuntu base files, those chunks are stored once. You can target the same bucket from your fleet and restic snapshots --host web-03 to filter. The only constraint is that prune takes an exclusive lock, so schedule it when no backups are running.
How much will Backblaze B2 actually cost me for VPS backups?
For a single Ubuntu VPS with 50 GB of protected data backed up nightly and the 7/4/12/3 retention policy above, real-world storage after dedup + zstd compression typically lands at 20-30 GB. At Backblaze's $0.006/GB/month, that is 12-18 cents per month in storage. Egress (what you pay when restoring) is $0.01/GB but the first 3x of stored data each month is free. For 99% of VPS users, the monthly bill stays under $1.
Is it safe to back up a running database with Restic?
Not if you point Restic at the live data directory. MySQL's /var/lib/mysql and Postgres's /var/lib/postgresql contain files that are being written mid-transaction; a file-level snapshot captures an inconsistent state that will fail to replay on restore. The correct pattern is to dump first: pg_dumpall or mysqldump --single-transaction, write the dump to /var/backups, then let Restic back up the dump files. For high-volume databases where downtime from dumping is unacceptable, consider filesystem-level snapshots (LVM, ZFS) as an intermediate step, or use the database's native streaming backup tool (WAL archiving for Postgres, xtrabackup for MySQL).
Can I use Restic with Windows or macOS?
Yes. Restic is cross-platform and works on Windows, macOS, Linux, and the BSDs. This guide focuses on Ubuntu 24.04 because that is the dominant VPS Linux, but the same repository can be read from any OS with the password and credentials. A common setup: Linux servers back up nightly via systemd timers, developers on macOS run ad-hoc backups of their home directories to the same B2 bucket, and a Windows workstation in the office backs up via Task Scheduler. All three see each other's snapshots and dedupe against each other.
Next Steps
With Restic running, some natural follow-ups:
- Deploy MinIO as your own backup target -- Turn a second VPS into a fully-controlled S3-compatible store. See our guide on installing MinIO on Ubuntu 24.04. No egress fees, no third-party credentials.
- Harden SSH before trusting SFTP backends -- If you are using
sftp:as your backend, the SSH configuration on the receiving server is the security boundary. Work through hardening SSH on Ubuntu -- key-only auth, Fail2ban, restricted chroot for the backup user.
- Document the restore runbook -- Write down, in one page, the exact steps to restore this server onto a fresh VPS. Keep it outside the server (a Git repo, Notion, a printout). Future-you at 3 a.m. will not remember today's environment variables.
- Scale to fleet-wide management with
resticprofile-- resticprofile wraps Restic with YAML-based profiles, making it easy to manage dozens of servers with a shared config pattern.
- Read the community -- forum.restic.net is active, well-moderated, and the Restic maintainers participate directly. Most operational questions already have high-quality answers there.
Pair Restic with a Reliable VPS>
Restic is only as good as the box it runs on. Our CloudCore Starter plan gives you NVMe-backed Ubuntu 24.04 with unmetered bandwidth -- enough for nightly offsite backups without worrying about transfer caps. Deploy two and you have a production server plus a private MinIO target, encrypted end-to-end with a key only you hold.>
Launch Your VPS Now