
A good Linux VPS backup strategy is boring by design: it runs unattended, fails loudly, and restores fast. Most “we have backups” stories collapse during the restore—missing databases, partial file copies, or credentials that vanished with the server.
This post lays out a production-friendly approach for 2026 using restic (encrypted, deduplicated, quick) and an S3-compatible object store. You’ll decide what to back up, schedule it, verify it weekly, and restore without guessing. It’s aimed at developers and sysadmins running one (or a handful of) VPS instances who want backups to behave like infrastructure—not a once-in-a-while ritual.
What this Linux VPS backup strategy covers (and what it deliberately skips)
Before tools and commands, define the scope. Backups exist to meet recovery objectives, not to “save files.”
- Covered: app + system configuration, database dumps (or hot backups), user uploads, secrets stored in files, and a restore drill that results in a working service.
- Covered: client-side encryption, a retention policy, repository consistency checks, and clear failure signals.
- Not covered: full image snapshots as your only safety net. Snapshots are useful, but they don’t replace file-level restore or point-in-time DB recovery.
- Not covered: enterprise multi-tenant backup suites. If you need central policy across dozens of nodes, you’ll likely add a controller later.
If you want to combine this with machine snapshots, pair it with VPS snapshot backup automation. Treat snapshots as “fast rollback,” and restic as your “clean restore.”
Why restic + S3 works well on a VPS in 2026
Restic stays popular for one reason: it behaves predictably when you’re under pressure. It encrypts before anything leaves the server, deduplicates across runs, and works with immutability patterns (object lock) if your storage provider supports them.
- Speed: after the first run, backups usually finish in seconds to a few minutes because restic uploads only changed chunks.
- Security: AES-256 encryption with authentication; S3 credentials alone can’t decrypt your repository.
- Portability: you can restore to any Linux host with restic installed—no special appliance required.
The practical win on a VPS is clarity. You know what’s included, and you can test it. That removes most of the surprise during an incident.
Baseline architecture: keep your backups out of your blast radius
Backups stored on the same VPS aren’t backups. They’re a convenience copy.
A sane minimal layout:
- Primary VPS runs your app, database, and the restic client.
- Object storage bucket (S3-compatible) holds the restic repo and enforces lifecycle/immutability as an extra guardrail.
- Off-VPS secrets copy (your password manager / vault) stores: restic repository password, S3 keys, and a one-page restore runbook.
If you rebuild servers often, a HostMyCode VPS gives you clean snapshots, predictable performance, and enough headroom to run backup jobs without punishing user latency.
What to back up (a practical checklist)
The slowest part of an outage is the argument about what mattered. Decide ahead of time.
- System config: /etc (but exclude bulky, regenerable directories).
- App config and deploy artifacts: /srv, /opt, or your chosen app root.
- Uploads/static content: e.g., /var/www/uploads, /srv/app/storage, /data/media.
- Databases: logical dumps (PostgreSQL/MySQL) or hot backup directories if you know what you’re doing.
- Service definitions: systemd unit files, Nginx/Caddy configs, crontabs.
- Certificates/keys: only if you must; ideally regenerate via ACME, but back up private keys if you can’t.
Skip caches, node_modules, build artifacts, and anything you can reproduce from CI. If you’re unsure about a path, include it for now—then trim based on real restore tests.
Implementation details you can copy: folders, users, and file layout
Backups get easier when every server looks the same. Pick a layout once and keep it consistent.
- Backup scripts:
/usr/local/sbin/backup-restic - Backup config:
/etc/restic/backup.env(root-only) - DB dump location:
/var/backups/db - Restic exclude list:
/etc/restic/excludes.txt - Logs: journald (systemd service logs) + optional
/var/log/backup/
If your VPS regularly runs close to full, pair backups with strict log retention. This post complements VPS log rotation best practices in 2026.
Step-by-step: set up restic backups to S3 (Ubuntu 24.04/24.10-style, still valid in 2026)
The goal is repeatability. Keep credentials out of shell history, fail the job if any step fails, and verify the repository on a schedule.
Prerequisites
- A Linux VPS with root access (Ubuntu/Debian shown, but the approach maps to any distro).
- An S3-compatible bucket (AWS S3, Wasabi, Backblaze B2 S3, MinIO, etc.).
- S3 credentials limited to that bucket (read/write/list; delete permission depends on your retention approach).
- A decision on what paths and DBs you’ll back up.
1) Install restic and supporting tools
On Ubuntu/Debian:
sudo apt update
sudo apt install -y restic jq curl
Verify:
restic version
Expected output format:
restic 0.x.y compiled with go1.xx.x on linux/amd64
2) Create a dedicated directory structure
sudo install -d -m 0750 /etc/restic
sudo install -d -m 0750 /var/backups/db
sudo install -d -m 0750 /var/log/backup
3) Create /etc/restic/backup.env (root-only)
This file is sourced by systemd. Keep it 0600.
sudo tee /etc/restic/backup.env >/dev/null <<'EOF'
# S3 endpoint examples:
# AWS: https://s3.amazonaws.com
# MinIO: https://minio.example.net
# Many providers use region-specific endpoints.
export RESTIC_REPOSITORY='s3:https://s3.example.net/hostmycode-prod-restic'
export RESTIC_PASSWORD='REPLACE_WITH_A_LONG_RANDOM_PASSPHRASE'
export AWS_ACCESS_KEY_ID='REPLACE_WITH_S3_ACCESS_KEY'
export AWS_SECRET_ACCESS_KEY='REPLACE_WITH_S3_SECRET_KEY'
# Optional but useful if your provider needs it
# export AWS_DEFAULT_REGION='us-east-1'
# A tag helps you search snapshots during restores
export RESTIC_TAG='vps-prod-1'
EOF
sudo chmod 0600 /etc/restic/backup.env
Operational note: put the RESTIC_PASSWORD and S3 keys in your password manager as well. If the VPS disappears, you still need both.
4) Add an exclude file to keep backups lean
sudo tee /etc/restic/excludes.txt >/dev/null <<'EOF'
# OS noise
/proc
/sys
/dev
/run
/tmp
/var/tmp
# Package caches
/var/cache
# Journals can be large; usually you rely on log shipping instead
/var/log/journal
# Common app noise (tune for your stack)
**/node_modules
**/.cache
**/tmp
EOF
5) Initialize the repository
sudo -E bash -c 'source /etc/restic/backup.env && restic init'
Expected output includes:
created restic repository ...
If you hit endpoint, DNS, or TLS errors here, stop and fix them. An unreliable repository turns backups into a daily mystery.
6) Add database dumps (example: PostgreSQL)
Backing up raw database directories is easy to do incorrectly. For many VPS workloads, a logical dump is slower but dependable.
Create a dump script /usr/local/sbin/pg_dump_all_to_dir:
sudo tee /usr/local/sbin/pg_dump_all_to_dir >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
OUT_DIR=/var/backups/db/postgres
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "$OUT_DIR"
# Dump globals (roles, permissions)
sudo -u postgres pg_dumpall --globals-only > "$OUT_DIR/globals-$STAMP.sql"
# Dump each DB as a custom-format archive
DBS=$(sudo -u postgres psql -Atc "SELECT datname FROM pg_database WHERE datistemplate = false;")
for db in $DBS; do
sudo -u postgres pg_dump -Fc "$db" > "$OUT_DIR/$db-$STAMP.dump"
done
# Keep the directory tidy: remove dumps older than 10 days (restic has retention too)
find "$OUT_DIR" -type f -mtime +10 -delete
EOF
sudo chmod 0750 /usr/local/sbin/pg_dump_all_to_dir
Run it once:
sudo /usr/local/sbin/pg_dump_all_to_dir
ls -lh /var/backups/db/postgres | head
You should see files like globals-20260115T021500Z.sql and appdb-20260115T021500Z.dump.
7) Create the restic backup script
This script runs dumps, backs up selected paths, then confirms a snapshot was created.
sudo tee /usr/local/sbin/backup-restic >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
source /etc/restic/backup.env
HOSTNAME_TAG=$(hostname -s)
# 1) Create DB dumps first (add more dump steps as needed)
if command -v psql >/dev/null 2>&1; then
/usr/local/sbin/pg_dump_all_to_dir
fi
# 2) Run restic backup
# Tune paths for your server.
restic backup \
--tag "$RESTIC_TAG" \
--tag "$HOSTNAME_TAG" \
--exclude-file /etc/restic/excludes.txt \
/etc \
/var/backups/db \
/srv \
/var/www \
--verbose
# 3) Simple verification: list newest snapshot
restic snapshots --last 1
EOF
sudo chmod 0750 /usr/local/sbin/backup-restic
8) Schedule with systemd timer (cleaner than cron for logs and failure handling)
Create /etc/systemd/system/restic-backup.service:
sudo tee /etc/systemd/system/restic-backup.service >/dev/null <<'EOF'
[Unit]
Description=Nightly restic backup to S3
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/backup.env
ExecStart=/usr/local/sbin/backup-restic
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
# Hardening (keep realistic; restic needs network + file access)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/backups /var/log /srv /var/www /etc/restic
# If it fails, you want it obvious
StandardOutput=journal
StandardError=journal
EOF
Create /etc/systemd/system/restic-backup.timer:
sudo tee /etc/systemd/system/restic-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Run restic backup nightly
[Timer]
OnCalendar=*-*-* 02:17:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.target
EOF
Enable:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
systemctl list-timers --all | grep restic-backup
Expected output shows the next run time around 02:17 with a randomized delay.
Verification that actually matters: weekly checks, not blind faith
A job that runs isn’t the same as a backup you can restore. Bake in two checks: repository integrity, and a small restore test.
Repository check (weekly)
Create /etc/systemd/system/restic-check.service:
sudo tee /etc/systemd/system/restic-check.service >/dev/null <<'EOF'
[Unit]
Description=Weekly restic repository integrity check
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/backup.env
ExecStart=/usr/bin/restic check
StandardOutput=journal
StandardError=journal
EOF
Timer /etc/systemd/system/restic-check.timer:
sudo tee /etc/systemd/system/restic-check.timer >/dev/null <<'EOF'
[Unit]
Description=Run restic check weekly
[Timer]
OnCalendar=Sun *-*-* 03:40:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now restic-check.timer
Restore drill (monthly): prove you can recover your app
Once a month, restore a small subset to a temp directory. Confirm files open, dumps exist, and your runbook still matches what’s deployed.
sudo -E bash -c 'source /etc/restic/backup.env && restic snapshots --last 1'
sudo -E bash -c 'source /etc/restic/backup.env && restic restore latest --target /tmp/restore-test --include /etc/hostname --include /var/backups/db'
ls -lh /tmp/restore-test/var/backups/db
If you run a web app, do the drill on a staging VPS and start the service. That’s how you discover the missing environment variables and one-off secrets—before you actually need them.
For a more complete workflow around failures and containment, keep a link to VPS Incident Response Checklist (2026) in your internal docs.
Retention policy: keep enough history without exploding storage bills
Restic retention is straightforward. For small-to-mid VPS workloads, this is a solid default:
- Keep 7 daily snapshots
- Keep 4 weekly snapshots
- Keep 6 monthly snapshots
Run prune after backups, but not necessarily every night if the repo is large. Weekly prune is often the better trade.
Example command (manual run):
sudo -E bash -c 'source /etc/restic/backup.env && restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune'
Automate it with a systemd timer if you like. Just remember: forget/prune requires delete permissions on the bucket.
Common failure modes (and the fixes that save time)
- Backups succeed but restores fail: you never backed up DB roles/permissions. Fix by dumping globals (
pg_dumpall --globals-only) and testing a restore. - Huge daily uploads: you’re backing up cache/build directories. Tighten
/etc/restic/excludes.txtand confirm withrestic stats. - Credential leaks: you exported AWS keys in a shell profile or committed them to git. Keep them in
/etc/restic/backup.env(0600) and rotate keys immediately if exposed. - S3 throttling/timeouts: your provider enforces request limits. Add
--limit-uploadto restic or schedule backups away from peak usage. - Disk fills during dumps: DB dumps can spike usage. Dump to a dedicated partition or reduce dump retention on disk; restic will store history in the repo anyway.
If your VPS is already tight on disk, fix that first. Use VPS disk space troubleshooting to reclaim space safely.
Rollback and “oh no” scenarios
Two rollback situations come up in real life: you broke the backup job, or you need to restore from a known-good snapshot.
Rollback a broken backup change
- Disable timers so you stop generating noise:
sudo systemctl disable --now restic-backup.timer restic-check.timer - Revert scripts/config from your config management or git repo (you do version your ops scripts, right?).
- Run a manual backup with extra verbosity:
sudo /usr/local/sbin/backup-restic - Re-enable timers after a clean run.
Rollback an app deploy using backups
If a deploy corrupts data, you need a snapshot from the right time and a restore plan that includes the database.
- Identify the snapshot:
restic snapshots - Restore files to a staging directory:
restic restore <snapshot-id> --target /mnt/restore - Restore DB from the matching dump (example PostgreSQL):
pg_restore -d appdb --clean --if-exists appdb-....dump - Switch traffic only after app health checks pass.
If you want rollbacks that don’t feel like surgery, keep the previous version runnable during deploys. This pairs well with blue-green deployment on a VPS.
Security posture: backups are a data exfiltration path
If an attacker can read your backups, they can quietly take everything. Treat backup credentials like production credentials.
- Use least privilege on S3 credentials (bucket-scoped, preferably IP-restricted if possible).
- Consider object lock/immutability to reduce ransomware impact. Even if your VPS is compromised, deletions become harder.
- Store secrets outside the VPS: restic password, S3 keys, and your restore notes.
If you’re tightening the whole server, pair this with a clear access model (bastion/VPN) and firewall rules. Two useful references: SSH bastion host setup and UFW hardening playbook.
Next steps: make this strategy operational, not just configured
- Add alerting: ship systemd unit failures to your monitoring stack (email, Slack, PagerDuty). Silent failure is how backups turn into a false sense of safety.
- Document a 1-page restore runbook: where credentials live, how to fetch the repo, how to restore DB + app, and the exact services to restart.
- Practice restores: schedule a monthly restore drill to a scratch VPS and time it. Track the result like any other reliability metric.
Summary
A solid Linux VPS backup strategy in 2026 comes down to disciplined basics: back up the right data (including DB roles), encrypt before upload, check integrity weekly, and prove restores monthly. Restic + S3 works as a baseline because it’s portable and doesn’t tie you to any single snapshot format.
If you want a clean place to run these jobs—without fighting noisy neighbors—start with a HostMyCode VPS, and consider managed VPS hosting if you’d rather hand off routine ops (patching, monitoring baselines, and recovery assistance) while you focus on the application.
If your backups keep growing while your restore window keeps shrinking, your “set-and-forget” server setup is probably the bottleneck. HostMyCode gives you predictable performance for nightly backup jobs on a HostMyCode VPS, and you can move routine ops to managed VPS hosting when you want help keeping backups, monitoring, and recovery consistent.
FAQ
Should I rely on VPS snapshots instead of restic?
No. Snapshots are excellent for quick rollbacks, but they’re not a replacement for file-level restores and database-aware recovery. Use both if you can.
How do I know restic backups are restorable?
Run restic check weekly and do a real restore to a temp directory or staging VPS monthly. A restore drill is the only honest test.
Do I need to back up /etc?
Usually yes, but exclude noise like /proc, /sys, and large regenerable directories. Focus on configs that make your server unique: web server config, systemd units, app configs.
Where should I store the restic repository password?
In a password manager or secret vault outside the VPS. If you lose the VPS and the password lived only on it, the backups are effectively lost.
What if my S3 credentials are compromised?
Rotate them immediately, then validate the repository and consider enabling immutability/object lock. Restic encryption protects confidentiality, but an attacker can still delete backups if your bucket policy allows it.