rclone is a command-line tool that copies and synchronizes files between a server and more than 70 storage services, including Amazon S3, Backblaze B2, Wasabi, Cloudflare R2, MinIO, Google Drive and plain SFTP servers. It is often described as "rsync for cloud storage", and it is a simple way to get a copy of your backups off the server. In this tutorial you will install rclone on Ubuntu 24.04, connect it to an S3-compatible bucket, add client-side encryption, run a sync that keeps deleted and changed files for 30 days, restore files, and schedule the job with a systemd timer.
Prerequisites
To follow this tutorial, you will need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges. - An account with an S3-compatible object storage provider, with an empty bucket and an access key limited to that bucket. You need the endpoint URL, the access key ID and the secret access key.
- Data to protect. The examples back up
/var/wwwand the local backup directory/var/backups; replace them with your own paths.
In the commands, replace your_bucket with your bucket name and your_endpoint with the endpoint URL from your provider (for example https://s3.eu-central-003.backblazeb2.com).
Step 1 - Installing rclone
Ubuntu's own rclone package lags far behind upstream. Install the current release from the .deb package that the rclone project publishes:
cd /tmp
curl -LO https://downloads.rclone.org/rclone-current-linux-amd64.deb
sudo apt install ./rclone-current-linux-amd64.deb
On an ARM server, download rclone-current-linux-arm64.deb instead. Check that the binary works:
rclone version
rclone v1.71.1
- os/version: ubuntu 24.04 (64 bit)
- os/kernel: 6.8.0-84-generic (x86_64)
- os/type: linux
- os/arch: amd64
Your version number will be different. To update later, download the package again and repeat the apt install command.
Step 2 - Connecting rclone to your bucket
rclone stores its connections, called remotes, in a configuration file. The backup will run as root so it can read every file, so create the remote with sudo; on Ubuntu, sudo sets HOME to /root, and the configuration goes to root's home directory. Confirm the path:
sudo rclone config file
Configuration file doesn't exist, but rclone will use this path:
/root/.config/rclone/rclone.conf
Create a remote named offsite of type s3. Read the secret key into a shell variable first, so it does not end up in your shell history:
read -rsp 'Secret access key: ' S3_SECRET; echo
sudo rclone config create offsite s3 \
provider=Other \
endpoint=your_endpoint \
access_key_id=your_access_key_id \
secret_access_key="$S3_SECRET"
unset S3_SECRET
provider=Other works for any S3-compatible service. rclone also has dedicated provider values (such as AWS, Wasabi, Cloudflare or Minio) that tune a few defaults; rclone help backend s3 lists them.
List the contents of your bucket to test the credentials. A new bucket is empty, so no output and no error means the connection works:
sudo rclone lsd offsite:your_bucket
If the key or endpoint is wrong, you will see an error such as AccessDenied or SignatureDoesNotMatch instead.
Step 3 - Adding client-side encryption
With a crypt remote, rclone encrypts file contents and names on the server before uploading them, so the storage provider only ever sees encrypted data. The crypt remote wraps the S3 remote: everything you write to offsite-crypt: is stored encrypted inside offsite:your_bucket/server1.
Generate two long random passwords and save them in your password manager before continuing:
openssl rand -base64 32
openssl rand -base64 32
Warningrclone cannot recover encrypted files without these two passwords. If you lose the server and did not save them elsewhere, the offsite backup is useless.
Create the crypt remote, entering the two passwords when prompted:
read -rsp 'Crypt password: ' CRYPT_PASS; echo
read -rsp 'Crypt salt password: ' CRYPT_SALT; echo
sudo rclone config create offsite-crypt crypt \
remote=offsite:your_bucket/server1 \
password="$CRYPT_PASS" \
password2="$CRYPT_SALT" \
--obscure
unset CRYPT_PASS CRYPT_SALT
The --obscure flag stores the passwords in obscured form in rclone.conf. Obscuring is not encryption, so the file itself must stay private. Check its permissions:
sudo ls -l /root/.config/rclone/rclone.conf
-rw------- 1 root root 412 Sep 25 12:04 /root/.config/rclone/rclone.conf
Test the encryption by uploading one file and looking at it through both remotes:
echo "rclone test" | sudo rclone rcat offsite-crypt:test.txt
sudo rclone ls offsite-crypt:
sudo rclone ls offsite:your_bucket/server1
12 test.txt
60 v0g1rtsn1khcm6nfm2qj2l3mvg
Through offsite-crypt: you see the real name; in the bucket itself, the name and contents are encrypted. Delete the test file:
sudo rclone deletefile offsite-crypt:test.txt
Step 4 - Running the first backup
rclone sync makes the destination identical to the source, including deleting files that no longer exist locally. That is what you want for a mirror, but it also means an accidental deletion on the server would be copied to the backup. The --backup-dir option fixes this: instead of deleting or overwriting a file in the destination, rclone moves the old version into a separate directory. Use one directory per day under archive/:
sudo rclone sync /var/www offsite-crypt:current/var/www \
--backup-dir offsite-crypt:archive/$(date +%F)/var/www \
--exclude '*.log' --exclude 'cache/**' \
--dry-run
--dry-run shows what would be transferred without changing anything. Review the list, then run the same command without --dry-run, adding --progress to follow the upload:
sudo rclone sync /var/www offsite-crypt:current/var/www \
--backup-dir offsite-crypt:archive/$(date +%F)/var/www \
--exclude '*.log' --exclude 'cache/**' \
--progress
Transferred: 412.583 MiB / 412.583 MiB, 100%, 38.211 MiB/s, ETA 0s
Checks: 0 / 0, -, Listed 9512
Transferred: 9431 / 9431, 100%
Elapsed time: 11.2s
Now verify the upload. Because the remote is encrypted, compare with rclone cryptcheck, which encrypts local checksums and compares them with the stored files:
sudo rclone cryptcheck /var/www offsite-crypt:current/var/www --exclude '*.log' --exclude 'cache/**'
2026/09/25 12:21:47 NOTICE: Encrypted drive 'offsite-crypt:current/var/www': 0 differences found
2026/09/25 12:21:47 NOTICE: Encrypted drive 'offsite-crypt:current/var/www': 9431 matching files
A few options are worth knowing for larger backups:
--transfers 8uploads eight files in parallel (the default is 4). Raise it for many small files.--bwlimit 20Mcaps bandwidth at 20 MiB/s so the backup does not saturate the server's uplink during the day.--exclude-from /etc/rclone-exclude.txtreads exclusion patterns from a file, one per line.
Step 5 - Restoring files
To restore, copy in the other direction. Restore into a staging directory first and check the result before replacing live files:
sudo rclone copy offsite-crypt:current/var/www/your_domain /srv/restore/your_domain --progress
sudo ls /srv/restore/your_domain
A file that was deleted or changed on the server is kept under archive/ on the day it changed. Find it with rclone ls and a filter:
sudo rclone ls offsite-crypt:archive --include '**/wp-config.php'
3212 2026-09-24/var/www/your_domain/wp-config.php
Copy that specific version back:
sudo rclone copyto offsite-crypt:archive/2026-09-24/var/www/your_domain/wp-config.php /srv/restore/wp-config.php
Restoring on a new server works the same way, as long as you recreate rclone.conf there with the same credentials and the same two crypt passwords.
Step 6 - Scheduling the backup
A short script keeps the sync command, the list of paths and the cleanup of old versions in one place. Create it:
sudo nano /usr/local/bin/rclone-backup.sh
#!/usr/bin/env bash
# Sync local paths to encrypted object storage, keeping old versions for 30 days.
set -euo pipefail
REMOTE="offsite-crypt:"
SOURCES=(/var/www /var/backups)
KEEP_VERSIONS="30d"
TODAY=$(date +%F)
for src in "${SOURCES[@]}"; do
rclone sync "$src" "${REMOTE}current${src}" \
--backup-dir "${REMOTE}archive/${TODAY}${src}" \
--exclude '*.log' --exclude 'cache/**' \
--transfers 8 \
--log-level INFO --stats 0
done
# Remove old versions and the empty directories they leave behind.
rclone delete "${REMOTE}archive" --min-age "$KEEP_VERSIONS"
rclone rmdirs "${REMOTE}archive" --leave-root
Including /var/backups means that local database dumps and tar archives created by other jobs are also copied offsite. Make the script executable:
sudo chmod 700 /usr/local/bin/rclone-backup.sh
Create the service unit:
sudo nano /etc/systemd/system/rclone-backup.service
[Unit]
Description=Offsite backup with rclone
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/rclone-backup.sh
Nice=10
IOSchedulingClass=idle
Create the timer. It runs at 04:00, after the local backup jobs have finished writing to /var/backups:
sudo nano /etc/systemd/system/rclone-backup.timer
[Unit]
Description=Nightly offsite backup with rclone
[Timer]
OnCalendar=*-*-* 04:00:00
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer, start one run by hand and read the log:
sudo systemctl daemon-reload
sudo systemctl enable --now rclone-backup.timer
sudo systemctl start rclone-backup.service
journalctl -u rclone-backup.service -n 20 --no-pager
Sep 25 12:40:03 server rclone-backup.sh[51022]: 2026/09/25 12:40:03 INFO : There was nothing to transfer
Sep 25 12:40:09 server rclone-backup.sh[51022]: 2026/09/25 12:40:09 INFO : db/20260925-103102/mysql-your_database.sql.gz: Copied (new)
Sep 25 12:40:12 server systemd[1]: rclone-backup.service: Deactivated successfully.
Sep 25 12:40:12 server systemd[1]: Finished rclone-backup.service - Offsite backup with rclone.
systemctl list-timers rclone-backup.timer shows when the next run is due.
Troubleshooting
Failed to create file system for "offsite:...": didn't find section in config file: the command ran withoutsudo, so rclone looked in your own user's configuration. Always usesudo rclonewith this setup.SignatureDoesNotMatchorInvalidAccessKeyId: the access key or secret is wrong, or the endpoint belongs to another region. Recreate the remote withsudo rclone config update offsite secret_access_key=...or edit it interactively withsudo rclone config.BucketAlreadyExistsorAccessDeniedon the first upload: the key cannot create buckets. Create the bucket in your provider's panel first; if the key is restricted to one bucket, also addno_check_bucket=trueto the remote withsudo rclone config update offsite no_check_bucket=true.destination and parameter to --backup-dir mustn't overlap: the backup directory must be on the same remote as the destination but outside it. Keepcurrent/andarchive/side by side as in this guide.
Conclusion
Your server now sends an encrypted copy of its files and local backups to object storage every night, keeps 30 days of changed and deleted files, and you have verified both the upload and the restore. Together with the local copies, this gives you a backup that survives the loss of the server. Next, you can:
- Store the rclone configuration and the two crypt passwords in a password manager, and test a full restore from a fresh VPS.
- Add monitoring so a failed
rclone-backup.servicealerts you, for example withOnFailure=pointing to a notification unit. - Use
rclone mountto browse the encrypted backup as a local directory when you need to look for a file.
