Most server backup plans fail in one of two ways: the backups live on the same machine they're meant to protect, or nobody has ever tried a restore. This walkthrough fixes both with rclone, an S3-compatible storage box and one cron job. Everything here works with any S3 endpoint; I'll use ShoHost SDrive, an S3-compatible storage box as the example target because it also speaks SFTP, WebDAV and SMB and keeps its own snapshots.
1. Install rclone
curl https://rclone.org/install.sh | sudo bash
rclone version
2. Add the storage box as a remote
Grab the S3 endpoint, access key and secret key from your storage provider's dashboard, then:
rclone config create sdrive s3 provider=Other \
access_key_id=YOUR_ACCESS_KEY \
secret_access_key=YOUR_SECRET_KEY \
endpoint=https://YOUR_S3_ENDPOINT
rclone mkdir sdrive:server-backups
rclone lsd sdrive:
If you'd rather not use S3, the same box over SFTP works too: rclone config create sdrive sftp host=YOUR_HOST user=YOUR_USER key_file=~/.ssh/id_ed25519.
3. Encrypt before anything leaves the server
An rclone crypt remote wraps the bucket so file contents and names are encrypted on your side:
rclone config create sdrive-crypt crypt \
remote=sdrive:server-backups \
password='LONG_RANDOM_PASSPHRASE' --obscure
Store that passphrase somewhere other than the server. Lose it and the backups are unreadable, which is the point.
4. The backup script
#!/usr/bin/env bash
# /usr/local/bin/offsite-backup.sh
set -euo pipefail
STAMP=$(date +%F)
mkdir -p /var/backups/db
# Consistent MySQL/MariaDB dump without locking InnoDB tables
mysqldump --single-transaction --all-databases | gzip > /var/backups/db/all-$STAMP.sql.gz
# Databases: copy new dumps, keep 30 days remote
rclone copy /var/backups/db sdrive-crypt:db --log-file /var/log/rclone-backup.log
rclone delete sdrive-crypt:db --min-age 30d
# Site files: mirror, but move changed/deleted files into a dated folder instead of losing them
rclone sync /var/www sdrive-crypt:www \
--backup-dir sdrive-crypt:www-archive/$STAMP \
--transfers 4 --log-file /var/log/rclone-backup.log
# Local dumps older than 7 days
find /var/backups/db -name '*.sql.gz' -mtime +7 -delete
--backup-dir is the part people skip. Plain sync deletes remote files that were deleted locally, so a bad deploy or ransomware on the server would be mirrored straight into your backup. With --backup-dir, anything overwritten or removed is kept under a dated folder.
5. Schedule it
sudo chmod 700 /usr/local/bin/offsite-backup.sh
sudo crontab -e
# 02:30 every night
30 2 * * * /usr/local/bin/offsite-backup.sh
6. Prove the restore works
A backup you haven't restored is a guess. Once a month:
mkdir -p /tmp/restore-test
rclone copy sdrive-crypt:db /tmp/restore-test --max-age 2d
gunzip -t /tmp/restore-test/*.sql.gz && echo "dump OK"
rclone check /var/www sdrive-crypt:www --one-way
rclone check compares sizes and hashes between the server and the remote, so you find a silent failure now rather than on the day you need the files.
Where the storage box helps
rclone handles encryption and retention, but a second safety net on the storage side is worth having. Snapshots on the box let you roll back even if a compromised server pushes bad data during a sync. That's the main reason I like pointing this setup at a storage box rather than a plain bucket. SDrive includes snapshots and versioning on its plans, and there's a small free tier if you want to test the script before committing.
That's the whole setup: one remote, one encrypted wrapper, one script, one cron line and a monthly restore test.
Top comments (0)