Introduction
Backups are the single most important safety net for any production server. Yet far too many teams rely on ad-hoc rsync scripts or manual snapshots that are never tested. Restic is an open-source backup program that is fast, secure, and efficient. It encrypts every backup by default, deduplicates data at the block level, and supports a wide range of storage backends including Amazon S3, Backblaze B2, and any S3-compatible endpoint such as MinIO.
In this guide we walk through installing Restic on Ubuntu, configuring an S3 backend, writing a backup script, scheduling it with a systemd timer, and verifying and restoring backups. By the end, we will have a fully automated backup pipeline that runs without human intervention.
Prerequisites
- Ubuntu 22.04 or 24.04 server with root or sudo access
- An S3 bucket (AWS, Backblaze B2, or MinIO) with an IAM user that has
s3:PutObject,s3:GetObject,s3:ListBucket, ands3:DeleteObjectpermissions - AWS CLI configured or environment variables ready
1. Install Restic
The version in the Ubuntu repositories is usually outdated. We recommend downloading the latest binary directly from GitHub:
# Download the latest Restic binary
RESTIC_VERSION=0.17.3
wget https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_amd64.bz2
bunzip2 restic_${RESTIC_VERSION}_linux_amd64.bz2
chmod +x restic_${RESTIC_VERSION}_linux_amd64
sudo mv restic_${RESTIC_VERSION}_linux_amd64 /usr/local/bin/restic
# Verify
restic version
2. Configure S3 Backend Credentials
Create a credentials file that the backup script will source. Store it outside any web-accessible directory:
sudo mkdir -p /etc/restic
sudo tee /etc/restic/env.sh > /dev/null << 'EOF'
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export RESTIC_REPOSITORY="s3:s3.us-east-1.amazonaws.com/my-backup-bucket"
export RESTIC_PASSWORD="a-very-strong-encryption-passphrase"
EOF
sudo chmod 600 /etc/restic/env.sh
Important: Replace the placeholder values with your real credentials. The RESTIC_PASSWORD is used to encrypt the repository. Losing it means losing access to all backups permanently.
3. Initialize the Repository
source /etc/restic/env.sh
restic init
Restic creates the repository structure inside the S3 bucket. We only need to do this once.
4. Create the Backup Script
sudo tee /usr/local/bin/restic-backup.sh > /dev/null << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
# Load credentials
source /etc/restic/env.sh
# Directories to back up
BACKUP_PATHS="/etc /home /var/www /var/lib/mysql"
EXCLUDE_FILE="/etc/restic/excludes.txt"
# Create exclude file if it does not exist
if [ ! -f "$EXCLUDE_FILE" ]; then
cat > "$EXCLUDE_FILE" << 'EXCL'
*.tmp
*.log
*.cache
node_modules
.git
EXCL
fi
# Run the backup
restic backup $BACKUP_PATHS \
--exclude-file="$EXCLUDE_FILE" \
--tag "auto" \
--verbose
# Prune old snapshots: keep 7 daily, 4 weekly, 6 monthly
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
# Verify repository integrity
restic check
echo "[$(date)] Backup completed successfully"
SCRIPT
sudo chmod +x /usr/local/bin/restic-backup.sh
5. Schedule with systemd Timer
Using a systemd timer instead of cron gives us better logging, dependency management, and failure notifications.
5.1 Create the Service Unit
sudo tee /etc/systemd/system/restic-backup.service > /dev/null << 'EOF'
[Unit]
Description=Restic Backup to S3
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh
Nice=10
IOSchedulingClass=idle
[Install]
WantedBy=multi-user.target
EOF
5.2 Create the Timer Unit
sudo tee /etc/systemd/system/restic-backup.timer > /dev/null << 'EOF'
[Unit]
Description=Run Restic Backup Daily at 2 AM
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=900
[Install]
WantedBy=timers.target
EOF
5.3 Enable and Start
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
# Verify the timer is active
systemctl list-timers | grep restic
6. Verifying Backups
A backup that has never been tested is not a backup. We should verify regularly:
source /etc/restic/env.sh
# List all snapshots
restic snapshots
# Check repository integrity
restic check --read-data-subset=5%
# Mount a snapshot to browse files (requires FUSE)
sudo apt install -y fuse
mkdir -p /mnt/restic
restic mount /mnt/restic &
ls /mnt/restic/snapshots/latest/
7. Restoring from a Backup
When disaster strikes, Restic makes restoration straightforward:
source /etc/restic/env.sh
# Restore the latest snapshot to a target directory
restic restore latest --target /tmp/restore
# Restore a specific snapshot
restic snapshots # find the snapshot ID
restic restore abc12345 --target /tmp/restore
# Restore specific files
restic restore latest --target /tmp/restore --include "/etc/nginx"
8. Monitoring and Alerting
For production environments, we recommend piping backup results to a monitoring system. A simple approach is to send a webhook on failure:
# Add to the end of restic-backup.sh
if [ $? -ne 0 ]; then
curl -X POST -H "Content-Type: application/json" \
-d '{"text":"Restic backup FAILED on '"$(hostname)"'"}' \
https://hooks.slack.com/services/YOUR/WEBHOOK/URL
fi
Alternatively, use a dead man's switch service like Healthchecks.io. Add a curl ping at the end of a successful backup so the service alerts us if the ping stops arriving.
Conclusion
With Restic and S3, we get encrypted, deduplicated, and versioned backups that run automatically via systemd. The entire setup takes less than 30 minutes and protects us against data loss, ransomware, and accidental deletion. We recommend testing a full restore at least once a quarter to ensure the pipeline works end to end.