Backup Nextcloud (or anything) to Proton Drive using restic, rclone and systemd
My Problem
Some of us will have heard of the 3-2-1 rule for backups - three copies, two different media types, one copy minimum offsite. I was in violation of this rule until recently. I had three copies on two different types of media, but they were all local. In this how-to, I'll explain how I set up automated, encrypted, and versioned backups for my self-hosted cloud.
Prerequisites
Check that you have the following:
- A Proton account with enough storage
- Basic knowledge of the command line
- Systemd installed (Linux)
This guide is written using Linux (Fedora). Restic and Rclone support macOS and Windows as well. Steps should be similar. Look into Windows Task Scheduler for automation.
Ensure your Proton account has enough storage for the original backup and versioning - double the size of the original data is a good starting point.
Overview
While rclone can sync data, it doesn't provide versioning. This is where restic comes in; restic will be responsible for the backups, and rclone will provide the connection. Rclone has built-in support for restic, making this process simple. Systemd will be used to create a socket and service for rclone and a timer and service for restic.
Rclone support for proton isn't stable. This should be considered experimental - which obviously isn't great for backups. Verify your backup when complete. Due to the state of the proton drive API, proton should only be considered if you have other existing services with them and have unused bundled storage. Otherwise use a different provider - with restic's client side encryption, one doesn't need to rely on the cloud providers encryption.
My current situation is the actual backups work, but anything related to checking fails. I've worked around this by using rclone to duplicate the remote backup locally, and then verifying it.
Configure rclone
Download rclone
On Fedora:
sudo dnf install rclone
Set up the remote (Proton Drive)
- Open the configuration:
rclone config
- Select
n
for a new remote - Name the remote
proton
- Select the type of storage by index - in my case,
42
It is possible that Proton isn't in the list of providers. In this case, we need to exit the config via CTRL+C and self-update Rclone using sudo rclone selfupdate
.
- Enter your Proton username
- Enter your Proton password (selecting
y
will prompt you to enter it) - Enter your 2-factor authentication TOTP code (e.g.,
000000
)
You can copy and paste your password using CTRL+SHIFT+V in the command line.
- Confirm and quit
- Test the remote with
rclone lsd proton:
This should return all directories in your Proton Drive.
Configure restic
Download restic
- Open a new terminal, leaving the Rclone server running
- On Fedora:
sudo dnf install restic
Initialise the repository
- Export ENVs for the Rclone server and restic password and then init:
$ export RESTIC_REPOSITORY=rest:http://localhost:8080/
$ export RESTIC_PASSWORD=createpassword
$ restic init
This will take a moment. When complete, it will output something like this:
# created restic backend xxxrepohashxxx at rest:http://localhost:8080/
# Please note that knowledge of your password is required to access
# the repository. Losing your password means that your data is
# irrecoverably lost.
Create a strong password and keep it secure - you will lose all access to your backups if the password is lost.
Initial Backup
My Nextcloud install uses nextcloud-aio, which uses Docker. The Nextcloud data directory is configured as an external mount. My approach is to backup the data directory, and then backup /var/lib/docker/
- This should be plug-and-play on restore, but as always, a backup is only valid if you test it.
I recommend doing the initial backup from the console rather than using the systemd units we'll set up later. This can be a very long-running command (days), so use tmux or screen to keep the session active even when closing the terminal window
- Install tmux if needed:
sudo dnf install tmux
- Create a new tmux session:
tmux
- Serve the endpoint:
rclone serve restic proton:backup
- Create a new window in tmux: CTRL+B followed by C
CTRL+B is used to access the command palette of tmux. N will navigate to the next window, and D will detach the session. Use tmux attach
to rejoin the session.
- Start the backup:
restic backup /mnt/ncdata
and thenrestic backup /var/lib/docker
If backing up a restricted directory, you can use sudo su
to switch to root. You'll need to re-export the ENVs and should clean up the cache when done (rm -rf ~/.cache/restic
). The systemd units won't run as root, so this is a temporary workaround.
Expect this to take a long time. On the first backup, expect proton to time out after uploading several hundred GB of data. If this happens, just restart. Restic index the backup as it goes, so it will not duplicate or re-upload the same data. You can check both using sudo systemctl status restic-backup rclone-serve-restic.service
- View the backup using
restic snapshots
- Verify the backup using
restic check
"By default, the check
command does not verify that the actual pack files on disk in the repository are unmodified, because doing so requires reading a copy of every pack file in the repository. To tell restic to also verify the integrity of the pack files in the repository, use the --read-data
flag" (docs)
Setting up the systemd units
Rclone
Protondrive API can go into a failed state, so I've built some error handling to recover on errors - if you have a better way, please let me know.
- Create the service:
sudo nano /etc/systemd/system/rclone-serve-restic.service
[Unit]
Description=Rclone Serve Restic Service
Wants=rclone-watchdog.service
After=rclone-serve-restic.socket
[Service]
User=youruser # set to your user or create a specific user for this
ExecStart=/usr/bin/rclone serve restic proton:backup
KillSignal=SIGTERM
SuccessExitStatus=143 # allow a sigterm exit to be considered a success - otherwise unit status will result in failed
Restart=on-failure
StandardOutput=truncate:/var/log/rclone_servelog
StandardError=truncate:/var/log/rclone_serve.log
[Install]
WantedBy=multi-user.target
If you run restic commands outside of the systemd unit, you may want to stop the rclone serve unit when done. sudo systemctl stop rclone-serve-restic
- Create the socket:
sudo nano /etc/systemd/system/rclone-serve-restic.socket
[Unit]
Description=Rclone Serve Restic Socket
[Socket]
ListenStream=127.0.0.1:8080 # will listen for requests here and trigger rclone-restic-serve.service
[Install]
WantedBy=sockets.target
- Create the watchdog:
sudo nano /etc/systemd/system/rclone-watchdog.service
[Unit]
Description=Rclone Watchdog Service
After=rclone-serve-restic.service
BindsTo=rclone-serve-restic.service
OnFailure=rclone-handlefailure.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/rclone_health.sh
SuccessExitStatus=TERM
[Install]
WantedBy=multi-user.target
- Create the failure handler:
sudo nano /etc/systemd/system/rclone-handlefailure.service
[Unit]
Description=Handle Failure Service
After=rclone-serve-restic.service
[Service]
Type=oneshot
ExecStart=/bin/systemctl kill --signal SIGINT rclone-serve-restic.service
[Install]
WantedBy=default.target
- Create the script:
sudo nano /usr/local/bin/rclone_health.sh
#!/bin/bash
# Log file path
LOG_FILE=/var/log/rclone_serve.log
# Error message to look for
# change if needed to match your error condition
ERROR_MESSAGE="received no response from API"
while true; do
# Check if the error message is present in the log file
if grep -q "$ERROR_MESSAGE" $LOG_FILE; then
exit 1
fi
# Wait for 1 second before checking again
sleep 1
done
- make the script executable
sudo chmod +x /usr/local/bin/rclone_health.sh
Restic
- Create the service:
sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic Backup Service
[Service]
Type=oneshot
User=youruser # set to your user or create a specific user for this
Environment="RESTIC_CACHE_DIR=/home/youruser/.cache/restic" # update user
Environment="RESTIC_REPOSITORY=rest:http://localhost:8080/"
Environment="RESTIC_PASSWORD=your_restic_password"
ExecStart=/usr/bin/restic backup /mnt/ncdata/
ExecStart=/usr/bin/restic backup /var/lib/docker
ExecStart=/usr/bin/restic backup /home/youruser/docker # update user
ExecStartPost=/bin/systemctl stop rclone-serve-restic.service
ReadOnlyPaths=/mnt/ncdata /var/lib/docker /home/youruser/docker # mount the required dirs as read-only
[Install]
WantedBy=multi-user.target
- Create the timer:
sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Restic Backup Timer
[Timer]
OnCalendar=*-*-* 03:00 # every day at 03 hours
Persistent=true # persist across reboots
[Install]
WantedBy=timers.target
Enable
$ sudo systemctl daemon-reload
$ sudo systemctl enable restic-backup.timer rclone-serve-restic.socket
$ sudo systemctl start restic-backup.timer rclone-serve-restic.socket
Everything should now be set up. Verify correct operation by using sudo systemctl status unit.name
.
Troubleshooting
- Verify no failed systemd units:
sudo systemctl --failed
- Verify that the Rclone is connected:
rclone lsd proton:backup
- Read the docs:
- Post a comment, and I can attempt to help.
To Do
While this is a good start, it's missing some features. I'll update this write-up as I implement them:
- Pruning (see Pruning). Add this into the restic-backup.service unit.
- Automated checks (see Checks). Add this as a separate service/timer and make it conflict with restic-backup.service to prevent concurrency issues.
- Notification of some sort on error. I haven't looked into this yet - this is critical.
- Do a test restore - both of an individual file, and an entire snapshot.