How to Make Your Server a Backup Target for Other Machines (restic REST Server)

Tutorial 55 backed the server up with restic. Turn it round: other machines — the Pi in the shed, the laptops, a relative's server over the site-to-site VPN (tutorial 108) — back up to it. SFTP works, but restic's own REST server is faster, needs no shell accounts, gives each client a separate repository behind its own login, and has an append-only mode: a client can add backups but can never delete or overwrite them. That last part is what makes it ransomware-proof.
Quick reference
| Task | What |
|---|---|
| Folder | ~/docker/rest-server/compose.yaml |
| Data | /mnt/data/restic — on the big drive, ideally the mirror |
| Logins | .htpasswd in the data folder; one user per client |
| Client URL | rest:https://backup.example.com// |
| Append-only | --append-only flag — clients can't prune or delete |
| Prune on the server | a cron job with the repository password, locally |
1. The compose file
mkdir -p ~/docker/rest-server && cd ~/docker/rest-server
nano compose.yaml
services:
rest-server:
image: restic/rest-server:latest
container_name: rest-server
ports:
- "127.0.0.1:8000:8000"
environment:
OPTIONS: "--append-only --private-repos --prometheus"
PASSWORD_FILE: /data/.htpasswd
volumes:
- /mnt/data/restic:/data
restart: unless-stopped
- --private-repos: each user can only reach the repository with their name.
- --append-only: no deletes from clients.
- Bound to localhost; the reverse proxy adds HTTPS (clients send a repository password, but transport encryption keeps the login private).
2. Users
sudo mkdir -p /mnt/data/restic
sudo apt install apache2-utils -y
sudo htpasswd -B -c /mnt/data/restic/.htpasswd laptop-john
sudo htpasswd -B /mnt/data/restic/.htpasswd pi-shed
sudo htpasswd -B /mnt/data/restic/.htpasswd mum-server
docker compose up -d
One login per client machine. The repository for laptop-john lives at /mnt/data/restic/laptop-john/.
3. Reverse proxy
backup.example.com → 127.0.0.1:8000 (tutorial 28) with:
client_max_body_size 0;
proxy_request_buffering off;
Restic uploads big pack files; those two lines stop Nginx buffering them. Certbot for HTTPS. Over the VPN only? The LAN address with plain http is acceptable, but HTTPS costs nothing.
4. On a client
Linux/Pi:
export RESTIC_REPOSITORY=rest:https://laptop-john:PASSWORD@backup.example.com/laptop-john/
export RESTIC_PASSWORD=repo-password-for-this-machine
restic init
restic backup /home /etc
(Windows: the same in PowerShell, tutorial 115.) Put the two variables in a chmod 600 env file and schedule the backup with cron or a timer (tutorial 69); ping Healthchecks at the end (tutorial 104). First backup over Ethernet; after that, deltas only.
A relative's server over the site-to-site tunnel (tutorial 108) uses the server's LAN address and backs up nightly to a box in your house, for free.
5. Pruning, done on the server
Append-only means clients can't run forget --prune. Do it locally, where the files are, with each repository's password:
sudo nano /usr/local/bin/restic-server-prune.sh
#!/bin/bash
set -e
for repo in laptop-john pi-shed; do
RESTIC_PASSWORD_FILE=/root/restic-pw/$repo restic -r /mnt/data/restic/$repo \
forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune --quiet
RESTIC_PASSWORD_FILE=/root/restic-pw/$repo restic -r /mnt/data/restic/$repo check --quiet
done
Weekly from root's cron. The repository passwords live only in /root/restic-pw/ (chmod 600) and on the client — never in the compose file.
6. Why append-only matters
If a laptop gets ransomware, the malware can run restic with the credentials it finds there. With a normal target it would delete the backups. With --append-only, the worst it can do is add an encrypted mess as a new snapshot — the old snapshots stay. Your weekly prune on the server removes the bad ones later.
7. Watch it
--prometheus exposes /metrics for Grafana (tutorial 90): bytes stored and request counts per repository. Simpler: du -sh /mnt/data/restic/* in the monthly review.
Troubleshooting
"401 Unauthorized". Wrong user/password in the URL, or the username doesn't match the repository path (/laptop-john/ for user laptop-john with --private-repos).
"413 Request Entity Too Large" / uploads stall. The two Nginx lines in step 3.
Client's restic forget fails. Correct — append-only. Prune on the server.
Repository "locked" on the server when pruning. A client backup is running; the script retries next week, or restic unlock if a client crashed mid-backup.
Wrapping up
One container, an htpasswd user per machine, append-only on, pruning from the server's side. Every laptop, Pi and relative's box backs up to your server over HTTPS with its own encrypted repository, and nothing that happens on the client can touch the history.
