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 sudo privileges.
  • 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/www and 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

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 8 uploads eight files in parallel (the default is 4). Raise it for many small files.
  • --bwlimit 20M caps bandwidth at 20 MiB/s so the backup does not saturate the server's uplink during the day.
  • --exclude-from /etc/rclone-exclude.txt reads 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 without sudo, so rclone looked in your own user's configuration. Always use sudo rclone with this setup.
  • SignatureDoesNotMatch or InvalidAccessKeyId: the access key or secret is wrong, or the endpoint belongs to another region. Recreate the remote with sudo rclone config update offsite secret_access_key=... or edit it interactively with sudo rclone config.
  • BucketAlreadyExists or AccessDenied on 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 add no_check_bucket=true to the remote with sudo 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. Keep current/ and archive/ 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.service alerts you, for example with OnFailure= pointing to a notification unit.
  • Use rclone mount to browse the encrypted backup as a local directory when you need to look for a file.