Back to tutorials
Tutorial

Incremental Backup Tutorial (2026): Restic + S3 for VPS Websites with Retention, Encryption, and Restore Tests

Incremental backup tutorial for VPS sites using restic + S3: encryption, retention, automations, and restore verification in 2026.

By Anurag Singh
Updated on Jun 24, 2026
Category: Tutorial
Share article
Incremental Backup Tutorial (2026): Restic + S3 for VPS Websites with Retention, Encryption, and Restore Tests

Backups usually fail in the dullest way possible. The job “succeeds,” the logs look fine, and you only discover the problem during a real outage.

This incremental backup tutorial walks you through a practical restic setup for a hosting VPS in 2026. It’s encrypted, retention-managed, and verified with restore tests you can automate.

The examples assume a typical hosting stack (Nginx or Apache, PHP, WordPress). The same patterns work on managed environments too. You’ll mainly adapt where scripts live and how they’re scheduled.

What you’ll build (and why restic fits VPS hosting)

Restic is a fast backup tool with encryption and deduplication built in. On a VPS, it’s a solid fit because:

  • Incremental by design: daily runs usually upload only changed blocks, so bandwidth stays predictable.
  • Client-side encryption: your storage backend can be “dumb” and still safe.
  • S3-compatible support: it works with most object storage providers.
  • Restores are simple: pull back one file, a directory, or an entire site tree.

We’ll back up /var/www, key config files, and web server configuration directories.

Databases are handled via dumps (not raw datadir copies). That’s the safer default for MySQL/MariaDB with InnoDB on hosting VPS setups.

If a server move is on your near-term roadmap, pair this with a low-downtime DNS plan. See DNS cutover tutorial for low downtime migrations for TTL timing and rollback checks.

Prerequisites (VPS, access, and storage)

  • A Linux VPS (Ubuntu 22.04/24.04, Debian 12, AlmaLinux 9/10, Rocky 9/10) with root or sudo access.
  • S3-compatible bucket + access key/secret (or native AWS S3).
  • Enough local disk to store database dumps temporarily (usually a few GB).

For smoother dump and backup runs, NVMe storage helps a lot. This matters most if you host several sites.

If you need predictable I/O and control over schedules, retention, and restore drills, consider a HostMyCode VPS instead of a shared plan with hard limits.

Step 1: Install restic (Ubuntu/Debian and Alma/Rocky)

Ubuntu/Debian:

sudo apt update
sudo apt install -y restic cron

AlmaLinux/Rocky:

sudo dnf install -y epel-release
sudo dnf install -y restic cronie

Confirm the version:

restic version

In 2026, aim for restic 0.16+ from your distro repo or vendor packages. If your repo is unusually stale, use the official restic release binaries. Keep the install method consistent across servers.

Step 2: Create a dedicated backup identity and directory layout

Create a minimal user for backup-related state. Keep secrets out of shell history.

sudo useradd --system --home /var/lib/restic --shell /usr/sbin/nologin restic
sudo mkdir -p /etc/restic /var/log/restic /var/lib/restic
sudo chown -R restic:restic /var/log/restic /var/lib/restic
sudo chmod 0750 /etc/restic /var/log/restic /var/lib/restic

We’ll load environment variables from a file under /etc/restic.

You can grant the restic user access via groups. For most hosting VPS setups, running the backup as root is simpler and less brittle. It avoids ACL sprawl and “why can’t it read that vhost file?” surprises.

Step 3: Configure S3 credentials and repository variables

Create an env file. This example uses an S3-compatible endpoint. If you use AWS S3, omit AWS_ENDPOINT.

sudo tee /etc/restic/restic.env >/dev/null <<'EOF'
# Repository location
export RESTIC_REPOSITORY="s3:https://s3.example.com/hostmycode-vps01"

# S3 credentials
export AWS_ACCESS_KEY_ID="REPLACE_ME"
export AWS_SECRET_ACCESS_KEY="REPLACE_ME"
export AWS_DEFAULT_REGION="auto"
export AWS_ENDPOINT="https://s3.example.com"

# Strong passphrase (store securely)
export RESTIC_PASSWORD="REPLACE_WITH_A_LONG_RANDOM_PASSPHRASE"

# Optional: reduce API chatter on some S3 providers
export RESTIC_CACHE_DIR="/var/lib/restic/cache"
EOF

sudo chmod 0640 /etc/restic/restic.env
sudo chown root:root /etc/restic/restic.env

Security note: treat /etc/restic/restic.env like an SSH private key. If you manage more than a couple of servers, keep secrets in a password manager. Then inject them via automation.

Step 4: Initialize the restic repository (one time)

sudo -E bash -c 'source /etc/restic/restic.env && restic init'

Then confirm the repository is reachable:

sudo -E bash -c 'source /etc/restic/restic.env && restic snapshots'

Step 5: Decide what to back up (hosting-focused include list)

On a VPS hosting one or more websites, this is a practical baseline:

  • Web roots: /var/www (or /home/*/public_html in some layouts)
  • Web server configs: /etc/nginx and/or /etc/apache2 (Debian) / /etc/httpd (RHEL-family)
  • PHP config: /etc/php
  • TLS material: /etc/letsencrypt (if you use Let’s Encrypt)
  • System-level app configs: /etc (selectively), or app-specific files you know you need

Avoid backing up all of / out of habit. You’ll sweep in transient mounts, caches, and device files. Restores then turn into a scavenger hunt.

Step 6: Create an exclusion file (cache, sessions, node_modules, backups-of-backups)

Add exclusions that match what actually bloats hosting backups.

sudo tee /etc/restic/excludes.txt >/dev/null <<'EOF'
# OS noise
/proc
/sys
/dev
/run
/tmp
/var/tmp

# Logs (keep separately if you need them)
/var/log

# Package caches
/var/cache

# WordPress / PHP caches (keep if you want, but they bloat backups)
*/wp-content/cache
*/wp-content/w3tc-cache
*/wp-content/litespeed
*/wp-content/uploads/cache

# Node / build artifacts
*/node_modules
*/.next/cache
*/dist
*/build

# Don’t back up backup destinations
/backup
/backups
EOF

If you’re already fighting disk bloat, clean that up first. Backing up garbage makes every run slower. It also makes every restore bigger than it needs to be.

See VPS cleanup tutorial for disk bloat for safe cleanup targets.

Step 7: Add database dumps (the hosting-safe way)

For MySQL/MariaDB, back up SQL dumps, not /var/lib/mysql. Use a small pre-backup script that writes dumps to a dedicated directory. It should also rotate them locally.

sudo mkdir -p /var/backups/db
sudo chmod 0700 /var/backups/db

Create /usr/local/sbin/dbdump.sh:

sudo tee /usr/local/sbin/dbdump.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

STAMP=$(date +%F)
OUTDIR=/var/backups/db

# Find databases excluding system schemas
DBS=$(mysql -N -e "SHOW DATABASES" | grep -Ev "^(information_schema|performance_schema|mysql|sys)$")

# Dump each DB with sane defaults
for db in $DBS; do
  mysqldump --single-transaction --routines --triggers --events \
    --set-gtid-purged=OFF \
    "$db" | gzip -1 > "$OUTDIR/${db}-${STAMP}.sql.gz"
done

# Keep 7 days locally (restic will enforce long-term retention)
find "$OUTDIR" -type f -name "*.sql.gz" -mtime +7 -delete
EOF

sudo chmod 0750 /usr/local/sbin/dbdump.sh
sudo chown root:root /usr/local/sbin/dbdump.sh

Credential handling: use /root/.my.cnf or a restricted MySQL client config for unattended dumps. Example:

sudo tee /root/.my.cnf >/dev/null <<'EOF'
[client]
user=root
password=REPLACE_ME
EOF
sudo chmod 0600 /root/.my.cnf

Step 8: Run your first backup (manual, with clear tags)

Start with a one-shot run that does three things: dumps databases, runs restic, and produces a snapshot you can restore from.

sudo -E bash -c '
set -e
/usr/local/sbin/dbdump.sh
source /etc/restic/restic.env
restic backup \
  --tag vps01 \
  --tag daily \
  --exclude-file=/etc/restic/excludes.txt \
  /var/www /etc/nginx /etc/apache2 /etc/httpd /etc/php /etc/letsencrypt /var/backups/db \
  2>&1 | tee -a /var/log/restic/backup.log
'

Your server won’t have every path listed. Most machines run either Nginx or Apache, not both.

Restic will error if a path doesn’t exist. On mixed fleets, it’s cleaner to use an include list and only add directories that are present:

sudo tee /etc/restic/includes.txt >/dev/null <<'EOF'
/var/www
/etc/nginx
/etc/apache2
/etc/httpd
/etc/php
/etc/letsencrypt
/var/backups/db
EOF

Then run:

sudo -E bash -c '
set -e
/usr/local/sbin/dbdump.sh
source /etc/restic/restic.env
restic backup --tag vps01 --tag daily --exclude-file=/etc/restic/excludes.txt --files-from=/etc/restic/includes.txt \
  2>&1 | tee -a /var/log/restic/backup.log
'

Step 9: Set retention (forget + prune) you can explain to a client

Retention should match how quickly you notice problems and how far back you may need to roll. A sensible starting point for VPS hosting:

  • Keep 7 daily snapshots
  • Keep 4 weekly snapshots
  • Keep 6 monthly snapshots
sudo -E bash -c '
source /etc/restic/restic.env
restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 6 \
  --prune \
  2>&1 | tee -a /var/log/restic/prune.log
'

Pitfall: don’t run --prune while another machine is backing up to the same repository.

For small fleets, the clean operational model is one repo per server.

Step 10: Automate with systemd timers (preferred) or cron

Use systemd timers if you can. You get predictable environments and better logging than a crontab entry that silently fails.

Create /etc/systemd/system/restic-backup.service:

sudo tee /etc/systemd/system/restic-backup.service >/dev/null <<'EOF'
[Unit]
Description=Restic backup (web + configs + db dumps)
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/restic.env
ExecStartPre=/usr/local/sbin/dbdump.sh
ExecStart=/usr/bin/restic backup --tag vps01 --tag daily --exclude-file=/etc/restic/excludes.txt --files-from=/etc/restic/includes.txt
ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
User=root
Group=root
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7

# Keep logs in journal; optionally also write to a file via StandardOutput=append:
EOF

Create /etc/systemd/system/restic-backup.timer:

sudo tee /etc/systemd/system/restic-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Nightly restic backup timer

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=15m

[Install]
WantedBy=timers.target
EOF

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
sudo systemctl list-timers | grep restic

Check the last run:

sudo systemctl status restic-backup.service --no-pager
sudo journalctl -u restic-backup.service -n 100 --no-pager

If you’re also standardizing server hygiene (updates, log cleanup, reboot windows), schedule those jobs so they don’t collide with backup I/O.

See Linux server maintenance automation for a clean baseline.

Step 11: Verify backups with real restore tests (not just “snapshots exist”)

A restore test should prove two things:

  • Can you pull back a specific site directory quickly?
  • Can you restore and actually use a database dump?

Create a test restore directory:

sudo mkdir -p /root/restore-test
sudo chmod 0700 /root/restore-test

List snapshots and pick the latest:

sudo -E bash -c 'source /etc/restic/restic.env && restic snapshots --tag vps01'

Restore only one site directory (example):

sudo -E bash -c '
source /etc/restic/restic.env
SNAP=$(restic snapshots --tag vps01 --latest 1 --json | jq -r ".[0].short_id")
restic restore "$SNAP" --target /root/restore-test --include /var/www/example.com
'

If you don’t have jq, install it. Or copy the snapshot ID from the non-JSON output. On Ubuntu:

sudo apt install -y jq

Restore a DB dump and test import into a temporary database:

sudo -E bash -c '
source /etc/restic/restic.env
SNAP=$(restic snapshots --tag vps01 --latest 1 --json | jq -r ".[0].short_id")
restic restore "$SNAP" --target /root/restore-test --include /var/backups/db
'
sudo mysql -e "CREATE DATABASE IF NOT EXISTS restore_test_db"
LATEST_DUMP=$(ls -1 /root/restore-test/var/backups/db/*restore_test_db*.sql.gz 2>/dev/null | tail -n 1)
# If you don't have a dump with that DB name, pick a specific file:
ls -1 /root/restore-test/var/backups/db/*.sql.gz | tail -n 3

# Example import
gunzip -c /root/restore-test/var/backups/db/YOURDB-$(date +%F).sql.gz | sudo mysql restore_test_db

If you want a more realistic drill that ties backups to DNS and a clean rebuild, run it as a documented exercise. See VPS restore drill tutorial for a full rebuild flow.

Step 12: Add integrity checks and alerting

Restic can verify repository integrity with check. Run it weekly (not nightly). It’s useful, but it can be I/O heavy.

sudo -E bash -c 'source /etc/restic/restic.env && restic check'

For alerting, pick something you’ll actually see. Good options are local mail to root or a webhook to your chat system.

If the VPS sends outbound mail, make sure authentication is correct. Otherwise, alerts can vanish into spam. Use Email authentication setup: SPF DKIM DMARC rDNS as your baseline.

Quick diagnostic checklist if backups aren’t running:

  • Permissions: can the service read /etc/restic/restic.env and the included directories?
  • DNS/egress: can the server resolve and reach your S3 endpoint (curl -I)?
  • Clock drift: large skew can break TLS; verify timedatectl.
  • Disk pressure: db dumps need space; check df -h.

Operational notes for hosting providers and resellers

If you run client sites, the tool choice matters less than doing the same thing every time. Consistency is what makes restores fast and predictable.

  • Standardize paths per stack. Even a simple convention like /var/www/<domain> cuts restore time.
  • Tag snapshots by server and purpose: --tag vps01, --tag pre-upgrade, --tag migration.
  • Practice “restore to staging” for one representative WordPress site each month.

If you’re hosting on a busy node with many accounts, a managed environment can take the edge off the 2 a.m. incident. managed VPS hosting fits when you want offsite backups and restore assistance without maintaining every script yourself.

Summary: a backup setup you can trust

You now have encrypted, incremental offsite backups with retention and restore tests. That’s the difference between “we have backups” and “we can recover quickly.”

If you want a VPS sized for consistent backup windows and predictable I/O, start with a HostMyCode VPS. If you’d rather have the backup plan maintained alongside you (and tested), consider managed VPS hosting from HostMyCode.

If your sites are outgrowing shared hosting—or you need control over offsite backups and regular restore drills—HostMyCode can help you move cleanly. Use a HostMyCode VPS for full control, or choose managed VPS hosting if you want help with ongoing operations and recovery planning.

FAQ

Is restic safe for production backups on a VPS?

Yes—if you keep permissions tight, store the repository password securely, and run regular restore tests. Restic encrypts client-side, so a storage compromise won’t expose your content.

Can I use one S3 bucket for multiple servers?

Yes. Use separate prefixes or repositories per server, for example s3:https://.../vps01 and .../vps02. Avoid multiple servers writing to the same restic repo.

Should I back up /var/lib/mysql instead of SQL dumps?

Not for typical hosting VPS setups. SQL dumps are portable and restore cleanly. Raw datadir backups can work, but they require careful service coordination and are easier to corrupt.

How often should I run restic check?

Weekly is a good default for most small hosting servers. Run it during low-traffic hours because it can be disk- and network-intensive.

What’s the minimum restore test I should do monthly?

Restore one full site directory to a separate path and import at least one database dump into a temporary database. If both steps work, your backups are materially safer.