Skip to main content
Back to Blog
Server & DevOpsApril 1, 202612 min read

How to Set Up Automated Server Backups with Restic and S3 on Ubuntu

A complete walkthrough for setting up encrypted, deduplicated server backups with Restic and S3-compatible storage on Ubuntu, including systemd timers, verification, and restore procedures.

OPS

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, and s3:DeleteObject permissions
  • 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.

Need help with this?

Our team handles this kind of work daily. Let us take care of your infrastructure.

Related Articles