Mahbubur Riad
Back to blog
DevOps 7 min read

Step‑By‑Step Guide: Automated Incremental Backups with Restic to MinIO S3 on a $5 VPS

Jul 02, 2026 · Mahbubur Riad

Learn how to configure Restic on a cheap VPS, push encrypted incremental backups to a self‑hosted MinIO S3 bucket, schedule them with systemd timers, and verify restores.

On this page

Introduction

Running a $5 VPS is a great way to get a Linux foothold without breaking the bank, but it also means you have to be extra careful with data durability. A single disk failure or accidental rm -rf can wipe out weeks of work. In this guide I’ll walk you through a real‑world, production‑ready backup pipeline:

  1. Install and configure Restic – a fast, deduplicating, encrypted backup tool.
  2. Deploy MinIO as a self‑hosted S3‑compatible object store on the same VPS (or a second cheap droplet).
  3. Wire Restic to push incremental backups to MinIO.
  4. Automate the whole thing with systemd timers.
  5. Verify the restore process end‑to‑end.

Everything is done with free, open‑source software, runs on a 1 CPU / 1 GB RAM VPS, and costs virtually nothing beyond the VPS fee.

Why MinIO?
It gives you an S3‑compatible API you can point any tool at, without paying for a third‑party bucket. You keep the data in your control, and you can later migrate to any real S3 provider if you need more space.


Prerequisites

Item Minimum requirement Why it matters
VPS 1 vCPU, 1 GB RAM, 25 GB SSD (any $5 plan) Restic is lightweight; MinIO needs ~200 MB RAM for small workloads
OS Ubuntu 22.04 LTS (or Debian 12) Official packages and documentation
Root or sudo access Needed to install packages and set up system services
Domain (optional) backup.example.com pointing to VPS Makes TLS setup with Let’s Encrypt easier

If you already have a VPS, you can skip the provisioning steps and jump straight to the installation sections.


1. Install MinIO

1.1 Download the binary

Bash
# Create a dedicated user
sudo useradd -r -s /usr/sbin/nologin minio
sudo mkdir -p /opt/minio/{data,config}
sudo chown -R minio:minio /opt/minio

# Grab the latest stable release (as of writing)
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/

1.2 Create a systemd service

Create /etc/systemd/system/minio.service:

INI
[Unit]
Description=MinIO Object Storage
After=network.target

[Service]
User=minio
Group=minio
ExecStart=/usr/local/bin/minio server /opt/minio/data \
          --address ":9000" \
          --console-address ":9001"
Environment="MINIO_ROOT_USER=adminuser"
Environment="MINIO_ROOT_PASSWORD=StrongP@ssw0rd!"
Restart=always
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Tip: Replace adminuser and StrongP@ssw0rd! with a strong, unique password. Store them in a password manager – you’ll need them for Restic later.

Enable and start the service:

Bash
sudo systemctl daemon-reload
sudo systemctl enable --now minio

1.3 Verify MinIO

Open http://<VPS_IP>:9000 in a browser, log in with the credentials you set, and you should see the MinIO console. For a headless test, use mc (MinIO client) later.


2. Set Up an S3 Bucket for Restic

We’ll create a bucket called restic-backups.

Bash
# Install mc (MinIO client)
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/

# Configure alias for our server
mc alias set local http://127.0.0.1:9000 adminuser StrongP@ssw0rd!

# Create bucket
mc mb local/restic-backups

You can also enable versioning (optional but handy):

Bash
mc version enable local/restic-backups

3. Install Restic

Bash
# Add the official Restic repository (Ubuntu/Debian)
echo "deb http://download.opensuse.org/repositories/home:/stevenroose:/restic/Debian_12/ /" \
  | sudo tee /etc/apt/sources.list.d/restic.list
wget -qO - https://download.opensuse.org/repositories/home:/stevenroose:/restic/Debian_12/Release.key \
  | sudo apt-key add -
sudo apt update
sudo apt install restic -y

Verify:

Bash
restic version
# restic 0.16.4 compiled with go1.22.2 on linux/amd64

4. Initialise the Restic Repository

Restic stores its metadata inside the bucket. Initialise it once:

Bash
export RESTIC_REPOSITORY=s3:http://127.0.0.1:9000/restic-backups
export RESTIC_PASSWORD=AnotherStrongPassphrase!

restic init

You should see something like:

Text
create repository s3:http://127.0.0.1:9000/restic-backups
created new restic repository f3b5... at s3:http://127.0.0.1:9000/restic-backups

Important: Keep RESTIC_PASSWORD safe. Without it you cannot read the backups.


5. Create a Backup Script

We’ll back up three typical directories:

  • /etc – system configuration
  • /var/www – web app files (if you host a site)
  • /home – user data

Create /usr/local/bin/restic-backup.sh:

Bash
#!/bin/bash
set -euo pipefail

# ---------- Environment ----------
export RESTIC_REPOSITORY=s3:http://127.0.0.1:9000/restic-backups
export RESTIC_PASSWORD=AnotherStrongPassphrase!
export AWS_ACCESS_KEY_ID=adminuser
export AWS_SECRET_ACCESS_KEY=StrongP@ssw0rd!

# ---------- Logging ----------
LOGFILE="/var/log/restic-backup.log"
exec > >(tee -a "$LOGFILE") 2>&1
echo "=== Restic backup started $(date -u) ==="

# ---------- Backup ----------
restic backup /etc /var/www /home \
  --exclude="/home/*/.cache" \
  --exclude="/var/www/tmp" \
  --tag "$(hostname)" \
  --verbose

# ---------- Prune old snapshots ----------
# Keep last 7 daily, 4 weekly, 6 monthly
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

echo "=== Restic backup finished $(date -u) ==="

Make it executable:

Bash
sudo chmod +x /usr/local/bin/restic-backup.sh
sudo chown root:root /usr/local/bin/restic-backup.sh

5.1 Why the forget command?

Restic stores incremental snapshots; each backup only adds new or changed chunks. Over time you’ll accumulate many snapshots. restic forget combined with --prune removes old snapshots and frees space in the bucket while keeping a sensible retention policy.


6. Automate with systemd Timer

6.1 Service unit

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

INI
[Unit]
Description=Restic incremental backup
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh

6.2 Timer unit

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

INI
[Unit]
Description=Run Restic backup daily at 02:30

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=15m   # prevents thundering herd if many VPS start together

[Install]
WantedBy=timers.target

Enable and start the timer:

Bash
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer

You can verify the next run:

Bash
systemctl list-timers --all | grep restic-backup

6.3 Testing the timer

Trigger a manual run to ensure everything works:

Bash
sudo systemctl start restic-backup.service

Check the log:

Bash
tail -n 20 /var/log/restic-backup.log

You should see a summary like:

Text
snapshot 1a2b3c4d saved
files: 3,123, total size: 4.5 GiB
...

7. Restic Restore Procedure

Never assume a backup works until you’ve restored it once. Here’s a quick checklist:

7.1 List available snapshots

Bash
restic snapshots

You’ll get a table with IDs, dates, and tags.

7.2 Restore a specific snapshot

Bash
# Example: restore the most recent snapshot to /tmp/restore-test
SNAP=$(restic snapshots --latest 1 --json | jq -r '.[0].short_id')
restic restore $SNAP --target /tmp/restore-test

7.3 Verify critical files

Bash
diff -r /etc /tmp/restore-test/etc | less

If the diff is empty, your backup is good. Delete the test directory afterwards:

Bash
rm -rf /tmp/restore-test

7.4 Full disaster recovery

If the VPS disk is completely lost, you can spin up a fresh VPS, install MinIO and Restic, then pull the repository:

Bash
# On the new machine
sudo apt install restic mc -y
export RESTIC_REPOSITORY=s3:http://<OLD_VPS_IP>:9000/restic-backups
export RESTIC_PASSWORD=AnotherStrongPassphrase!
export AWS_ACCESS_KEY_ID=adminuser
export AWS_SECRET_ACCESS_KEY=StrongP@ssw0rd!

# Verify we can list snapshots
restic snapshots

# Restore everything to root
restic restore latest --target /

Caution: Restoring to / will overwrite existing files. In a brand‑new VM this is fine; otherwise restore to a temporary location and copy selectively.


8. Monitoring & Alerts (Optional)

For a $5 VPS you probably don’t have a full monitoring stack, but a simple email alert on failure costs almost nothing.

Create /etc/systemd/system/[email protected]:

INI
[Unit]
Description=Send email on Restic backup failure
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-failure-notify.sh %i

And the script /usr/local/bin/restic-failure-notify.sh:

Bash
#!/bin/bash
set -euo pipefail
RECIPIENT="[email protected]"
SUBJECT="Restic backup failed on $(hostname)"
BODY="/var/log/restic-backup.log"

if grep -q "error" "$BODY"; then
    echo -e "Subject: $SUBJECT\n\n$(tail -n 20 $BODY)" | sendmail -t "$RECIPIENT"
fi

Install sendmail (or mailutils) and enable the failure service via a OnFailure= directive in the main service unit:

INI
# In restic-backup.service, add:
OnFailure=restic-backup-failure@%p.service

Now any non‑zero exit from the backup will trigger an email.


9. Comparison: Restic vs. Other Backup Tools on a $5 VPS

Feature Restic BorgBackup Duplicati
Encryption Built‑in AES‑256 (client‑side) Built‑in (AES‑256) AES‑256 (via .NET)
Deduplication Chunk‑level, works across snapshots Chunk‑level, similar File‑level only
S3 Compatibility Native s3: backend Requires borgmatic + rclone Direct S3 support
Resource Usage ~30 MB RAM, low CPU ~50 MB RAM, moderate CPU ~100 MB RAM, Java/.NET overhead
Ease of Automation systemd timers + restic forget similar, but extra scripts for prune Web UI needed for schedule
Community & Docs Very active, simple CLI Active, but more complex CLI Smaller, UI‑centric

For a tiny VPS, Restic wins on memory footprint and native S3 support, making it the most pragmatic choice.


10. Checklist – Are You Ready for Production?

  • MinIO installed, running, and reachable on port 9000 (or behind a reverse proxy).
  • Bucket restic-backups created with versioning (optional).
  • Restic repository initialized with a strong password.
  • Backup script (restic-backup.sh) tested manually and logs written to /var/log/restic-backup.log.
  • systemd service & timer enabled, next run shown by systemctl list-timers.
  • Retention policy (restic forget) matches your storage budget.
  • Restore test performed on a separate directory and critical files verified.
  • Failure alerts (email or Slack) configured (optional).
  • Credentials stored securely (consider a .env file with chmod 600).

If you tick all the boxes, you have a resilient, off‑site backup pipeline that costs virtually nothing beyond your $5 VPS.


11. Frequently Asked Questions

Question Answer
Do I need a separate VPS for MinIO? Not strictly. You can run MinIO and your apps on the same box if you keep the storage volume separate (e.g., /opt/minio-data). For higher durability, spin a second cheap droplet and point Restic to it via its public IP.
How much storage does MinIO need? MinIO itself stores only metadata; the bulk is your backup data. Start with a 10 GB volume; Restic’s deduplication usually reduces a 50 GB source to 5–15 GB in the bucket.
Can I encrypt the MinIO bucket itself? MinIO supports server‑side encryption (SSE‑S3) but Restic already encrypts data before upload. Double‑encrypting adds CPU overhead without much benefit.
What happens if the VPS clock drifts? Restic timestamps snapshots using the system clock. Use systemd-timesyncd or chrony to keep the clock accurate; otherwise snapshot ordering may look odd.
Is it safe to expose MinIO to the internet? Yes, if you enable TLS (via a reverse proxy like Caddy or Nginx) and use strong credentials. Consider firewall rules that only allow your own IPs, or use a VPN.

12. Wrap‑up

You now have a fully automated, incremental backup solution that fits comfortably on a $5 VPS. By leveraging Restic’s encryption, MinIO’s S3 compatibility, and systemd timers, you get:

  • Zero‑cost off‑site storage (the same VPS or a second cheap node)
  • Fast, deduplicated backups – only changed chunks travel over the network
  • Built‑in retention – keep daily, weekly, and monthly snapshots without manual cleanup
  • Simple restore – one command brings back any file or full system

Remember, backups are only as good as the last successful restore you performed. Schedule a quarterly full‑system restore test, rotate your passwords, and keep an eye on MinIO’s disk usage.

If you run into any quirks or want to share your own tweaks, feel free to drop a comment on mahbuburriad.com – we love hearing how the community makes low‑budget backups rock. Happy backing up!

Related

Related posts