Rest Server 0.14.0 on AWS User Guide
Overview
This guide covers launching and using the Rest Server 0.14.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg backup and recovery listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Rest Server is the HTTP server that implements the REST backend protocol of restic, the fast, encrypting backup program. Point restic on your laptops, servers and containers at it and it becomes your own backup repository: restic encrypts and deduplicates everything on the client before it is uploaded, so the server only ever stores ciphertext, and you pay for an EC2 instance and an EBS volume instead of per request object storage charges.
Upstream Rest Server listens on plain HTTP and keeps repositories in /tmp unless you tell it otherwise. The cloudimg image makes it a production backup target:
- HTTPS from the first boot. nginx serves Rest Server on port 443 with a certificate generated on your instance's first boot, naming the instance's DNS name and IP addresses, so restic can verify it with
--cacert. Rest Server itself listens on127.0.0.1:8000only, and nothing listens on port 80. - A login unique to your instance. Every request needs HTTP Basic authentication as the user
restic, with a password generated on the first boot and written to/root/rest-server-info.txt(root only). Rest Server stores only a bcrypt hash of it. No password is baked into the image, and Rest Server is never run in its anonymous--no-authmode. - Private repositories per user.
--private-reposconfines each user to repositories under their own name:resticreachesrest:https://<host>/restic/...and nothing else. Add a login per machine or team with one command. - Ransomware resistant repositories on demand.
sudo rest-server-append-only onlets clients add snapshots but never delete or overwrite them, so a compromised client cannot destroy its own backups. - Fail closed. Rest Server and nginx refuse to start until first boot has generated the certificate and a bcrypt login.
- Locked down serving. Requests for host names the instance does not serve are dropped; the client address Rest Server sees comes from the connection, never from a forwarding header a client sends; Rest Server, nginx and the first boot service are blocked from the instance metadata service, and the AMI requires IMDSv2.
- Your repositories on their own disk. Repositories live on a separate 10 GiB EBS volume mounted at
/var/lib/resticwithout device files, set-uid programs or execution, which you can grow and snapshot on its own. - The official release, verified, and fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Rest Server 0.14.0 (the official release binary in /opt/rest-server, licence in /usr/share/doc/rest-server), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The restic client is not on the image: you run it on the machines you back up.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH) from your own networks only, and TCP 443 (the restic REST endpoint) from the networks of the machines you back up. Nothing else needs to be open.
- restic 0.17 or later on the machines you back up (the examples use 0.18.0), from your package manager or the restic releases page.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Rest Server 0.14.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, and a security group as above. The AMI already carries the 10 GiB repository volume; make it as large as your backups need on the launch page (you can also grow it later, Step 11).
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=rest-server}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Rest Server 0.14.0 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot generates the certificate and the login, then Rest Server and nginx start:
systemctl is-active rest-server nginx
sudo test -f /var/lib/cloudimg/rest-server-firstboot.done && echo FIRSTBOOT_DONE
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. Rest Server listens on loopback behind nginx, and the DNS resolver listens on loopback only:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort
0.0.0.0:22 users:(("sshd",...),("systemd",...))
0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...))
127.0.0.1:8000 users:(("rest-server",...))
127.0.0.53%lo:53 users:(("systemd-resolve",...))
127.0.0.54:53 users:(("systemd-resolve",...))
[::]:22 users:(("sshd",...),("systemd",...))
[::]:443 users:(("nginx",...),("nginx",...),("nginx",...))
Step 4: Get your URL and password
The credentials file is readable by root only. This prints it with the password hidden; sudo cat /root/rest-server-info.txt shows the password itself:
sudo sed -E 's/^(REST_SERVER_PASSWORD=).*/\1<hidden>/' /root/rest-server-info.txt
# Rest Server 0.14.0 - generated on first boot by rest-server-firstboot.service
# This password is unique to this instance; none was baked into the image. rest-server keeps only a bcrypt hash
# of it in /etc/rest-server/.htpasswd. Keep this file secret.
REST_SERVER_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
REST_SERVER_USER=restic
REST_SERVER_PASSWORD=<hidden>
REST_SERVER_REPOSITORY=rest:https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/restic/<repository-name>
REST_SERVER_CA_CERT=/etc/nginx/tls/rest-server.crt
REST_SERVER_CERT_SHA256=C6:5F:16:D7:02:08:E7:0E:C3:89:1F:0C:63:C3:5F:5F:27:3A:9C:46:D7:BF:C0:9C:04:93:D4:9E:45:31:6D:8C
#
# On a client (restic 0.17 or later), with a copy of the certificate above:
# export RESTIC_REST_USERNAME=restic RESTIC_REST_PASSWORD=<REST_SERVER_PASSWORD>
# export RESTIC_REPOSITORY=rest:https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/restic/<repository-name>
# restic --cacert rest-server.crt init
# Change the password: sudo rest-server-set-password
# Add a user (own namespace): sudo rest-server-set-password --user <name>
# Ransomware-resistant mode: sudo rest-server-append-only on
# Change the name it serves as: sudo rest-server-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/rest-server-0-14-0-on-ubuntu-24-04-aws/
The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ and https://<private-ip>/ work too. REST_SERVER_CERT_SHA256 is the certificate's fingerprint, to compare against the copy you give your clients. To serve under your own DNS name, see Step 10.
Step 5: Back up to the server
restic reads the server login from RESTIC_REST_USERNAME and RESTIC_REST_PASSWORD, the repository from RESTIC_REPOSITORY and the certificate to trust from RESTIC_CACERT. The repository path starts with the user name (/restic/...), because each user only reaches their own repositories. Here we try it from the instance itself; Step 6 does the same from another machine.
Install the restic client (on the instance only for this test) and check its download:
cd /tmp && curl -fsSLO https://github.com/restic/restic/releases/download/v0.18.0/restic_0.18.0_linux_amd64.bz2
echo "98f6dd8bf5b59058d04bfd8dab58e196cc2a680666ccee90275a3b722374438e restic_0.18.0_linux_amd64.bz2" | sha256sum -c -
bunzip2 -f restic_0.18.0_linux_amd64.bz2 && sudo install -m 0755 restic_0.18.0_linux_amd64 /usr/local/bin/restic && rm -f restic_0.18.0_linux_amd64
restic version
restic_0.18.0_linux_amd64.bz2: OK
restic 0.18.0 compiled with go1.24.1 on linux/amd64
Keep the settings in a root only file. The repository password is a second, separate secret: restic encrypts the repository with it, and without it nobody (including you) can read the backups, so store it somewhere safe as well:
sudo install -d -m 0700 /root/restic-demo
sudo bash -c 'umask 077; openssl rand -hex 24 > /root/restic-demo/repo-password'
sudo bash -c 'umask 077; P=$(grep "^REST_SERVER_PASSWORD=" /root/rest-server-info.txt | cut -d= -f2-)
cat > /root/restic-demo/env <<ENV
export RESTIC_REPOSITORY=rest:https://127.0.0.1/restic/demo
export RESTIC_REST_USERNAME=restic
export RESTIC_REST_PASSWORD=$P
export RESTIC_PASSWORD_FILE=/root/restic-demo/repo-password
export RESTIC_CACERT=/etc/nginx/tls/rest-server.crt
ENV'
sudo ls -l /root/restic-demo | awk 'NR>1{print $1, $NF}'
-rw------- env
-rw------- repo-password
Create the repository, back up two folders and list the snapshot:
sudo bash -c '. /root/restic-demo/env && restic init'
created restic repository a67c345e2b at rest:https://127.0.0.1/restic/demo/
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
sudo bash -c '. /root/restic-demo/env && restic backup /etc/nginx/sites-available /usr/share/doc/rest-server'
no parent snapshot found, will read all files
Files: 4 new, 0 changed, 0 unmodified
Dirs: 7 new, 0 changed, 0 unmodified
Added to the repository: 20.943 KiB (10.309 KiB stored)
processed 4 files, 17.174 KiB in 0:00
snapshot e25661b1 saved
sudo bash -c '. /root/restic-demo/env && restic snapshots'
ID Time Host Tags Paths Size
---------------------------------------------------------------------------------------------------
e25661b1 2026-10-19 20:56:59 ip-<private-ip-dashed> /etc/nginx/sites-available 17.174 KiB
/usr/share/doc/rest-server
---------------------------------------------------------------------------------------------------
1 snapshots
Restore a folder from the snapshot and compare it with the original, then let restic check the whole repository:
sudo bash -c '. /root/restic-demo/env && restic restore latest --target /tmp/restic-restore --include /usr/share/doc/rest-server'
diff -r /usr/share/doc/rest-server /tmp/restic-restore/usr/share/doc/rest-server && echo RESTORED_IDENTICAL
rm -rf /tmp/restic-restore
restoring snapshot e25661b1 of [/etc/nginx/sites-available /usr/share/doc/rest-server] at 2026-10-19 20:56:59.455804574 +0000 UTC by root@ip-<private-ip-dashed> to /tmp/restic-restore
Summary: Restored 6 / 3 files/dirs (12.749 KiB / 12.749 KiB) in 0:00
RESTORED_IDENTICAL
sudo bash -c '. /root/restic-demo/env && restic check'
using temporary cache in /tmp/restic-check-cache-2548086462
create exclusive lock for repository
load indexes
check all packs
check snapshots, trees and blobs
[0:00] 100.00% 1 / 1 snapshots
no errors were found
On the server the repository is a folder under the user's name on the repository volume, owned by the unprivileged restic service account and readable by nobody else:
sudo ls -l /var/lib/restic/restic/demo | awk 'NR>1{print $1, $3, $NF}'
-rw------- restic config
drwx------ restic data
drwx------ restic index
drwx------ restic keys
drwx------ restic locks
drwx------ restic snapshots
Step 6: Back up from your own machines
On each machine you back up, install restic 0.17 or later and copy the server's certificate from the instance (compare its fingerprint with REST_SERVER_CERT_SHA256 from Step 4):
# on your laptop or server
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/nginx/tls/rest-server.crt ~/rest-server.crt
openssl x509 -noout -fingerprint -sha256 -in ~/rest-server.crt
Then set the same variables, with the server's DNS name and the password from Step 4, and use restic as usual. Give each machine its own repository name, or its own login (Step 9):
# on your laptop or server
export RESTIC_REPOSITORY=rest:https://<public-dns-name>/restic/laptop
export RESTIC_REST_USERNAME=restic
export RESTIC_REST_PASSWORD='<your-password>'
export RESTIC_PASSWORD_FILE=~/.restic-repo-password
export RESTIC_CACERT=~/rest-server.crt
restic init
restic backup ~/Documents
restic snapshots
Prefer the environment variables to putting the password in the repository URL (rest:https://restic:<password>@host/...), which shows up in the process list. Schedule restic backup with cron or a systemd timer, and run restic forget --keep-daily 7 --keep-weekly 4 --prune now and then to expire old snapshots (or on the server, if you use append only mode, Step 8).
Step 7: Check the security posture
Requests without the login, or with a wrong password, are refused; the health check stays open for load balancers; a request for a host name the instance does not serve is dropped without an answer, and nothing answers on port 80:
C=/etc/nginx/tls/rest-server.crt
curl -s --cacert $C -o /dev/null -w 'no login -> HTTP %{http_code}\n' https://127.0.0.1/restic/
curl -s --cacert $C -K <(printf 'user = "restic:not-the-password"\n') -o /dev/null -w 'wrong password -> HTTP %{http_code}\n' https://127.0.0.1/restic/
curl -s --cacert $C -o /dev/null -w 'health check -> HTTP %{http_code}\n' https://127.0.0.1/healthz
curl -s --cacert $C -H 'Host: evil.example' -o /dev/null -w 'unknown host -> %{http_code}\n' https://127.0.0.1/healthz || true
curl -s -o /dev/null -w 'plain http :80 -> %{http_code}\n' --connect-timeout 3 http://127.0.0.1/ || true
no login -> HTTP 401
wrong password -> HTTP 401
health check -> HTTP 200
unknown host -> 000
plain http :80 -> 000
000 means no answer: nginx closes the connection for a host name it does not serve, and nothing listens on port 80.
With the login, the user's own repository answers and another user's path is refused. The password is handed to curl on standard input, so it never appears in the process list:
sudo bash -c '
P=$(grep "^REST_SERVER_PASSWORD=" /root/rest-server-info.txt | cut -d= -f2-)
auth() { printf "user = \"restic:%s\"\n" "$P"; }
C=/etc/nginx/tls/rest-server.crt
auth | curl -K - -s --cacert $C -o /dev/null -w "own repository -> HTTP %{http_code}\n" https://127.0.0.1/restic/demo/config
auth | curl -K - -s --cacert $C -o /dev/null -w "another user s path -> HTTP %{http_code}\n" https://127.0.0.1/alice/demo/config
'
own repository -> HTTP 200
another user s path -> HTTP 401
Rest Server runs as the unprivileged restic user with authentication and private repositories on, and Rest Server, nginx and the first boot service cannot reach the instance metadata service:
ps -o user=,args= -C rest-server
systemctl show rest-server nginx rest-server-firstboot -p Id -p IPAddressDeny
restic /opt/rest-server/rest-server --listen 127.0.0.1:8000 --path /var/lib/restic --htpasswd-file /etc/rest-server/.htpasswd --private-repos
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=rest-server.service
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=nginx.service
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
Id=rest-server-firstboot.service
Rest Server keeps only a bcrypt hash of the password; the plain text is in the root only credentials file, and the TLS key is root only:
sudo sed -E 's/^(restic:\$2y\$10\$).*/\1<bcrypt-hash>/' /etc/rest-server/.htpasswd
sudo stat -c '%a %U:%G %n' /etc/rest-server/.htpasswd /root/rest-server-info.txt /etc/nginx/tls/rest-server.key
restic:$2y$10$<bcrypt-hash>
640 root:restic /etc/rest-server/.htpasswd
600 root:root /root/rest-server-info.txt
600 root:root /etc/nginx/tls/rest-server.key
The certificate was generated on this instance and names its own addresses:
openssl x509 -noout -subject -ext subjectAltName -in /etc/nginx/tls/rest-server.crt
subject=CN = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
Step 8: Ransomware resistant repositories (append only mode)
A backup client holds the credentials of its repository, so malware on that machine could delete its backups too. In append only mode Rest Server lets clients create and read snapshots but refuses every delete or overwrite. Turn it on:
sudo rest-server-append-only on
sudo rest-server-append-only status
rest-server-append-only: ON
ON
Backups keep working, but restic forget can no longer remove anything:
sudo bash -c '. /root/restic-demo/env && restic backup -q /etc/nginx/sites-available /usr/share/doc/rest-server'
sudo bash -c '. /root/restic-demo/env && restic forget --keep-last 1' 2>&1 | grep -E '^(remove|Remove|unable)'
sudo bash -c '. /root/restic-demo/env && restic snapshots --compact'
remove 1 snapshots:
Remove(<snapshot/e25661b183>) failed: unexpected HTTP response (403): 403 Forbidden
unable to remove snapshot/e25661b1835a052ec8fcb1a14ae86f1c8fe4bae1da905f4077edf40ed86833e0 from the repository
ID Time Host Tags Size
-------------------------------------------------------------------
e25661b1 2026-10-19 20:56:59 ip-<private-ip-dashed> 17.174 KiB
b7aa2bb6 2026-10-19 20:57:19 ip-<private-ip-dashed> 17.174 KiB
-------------------------------------------------------------------
2 snapshots
The server refused the delete (403 Forbidden) and both snapshots are still there. restic still exits 0 in this case, so watch for unable to remove in your logs. To expire old snapshots, switch append only mode off for a moment (sudo rest-server-append-only off), run restic forget ... --prune from a trusted machine, then switch it on again. rest-server-append-only restarts Rest Server, so start the next backup a second or two later. Turn it off again for the rest of this guide:
sudo rest-server-append-only off
rest-server-append-only: OFF
Step 9: Change the password or add users
rest-server-set-password manages the login file. Run it with no arguments to generate a new random password for restic; it is written to /root/rest-server-info.txt. To choose the password yourself, hand it over on standard input (printf is a shell builtin, so the password never appears in the process list; start the line with a space so most shells keep it out of your history):
printf '%s\n' '<paste-your-new-password>' | sudo rest-server-set-password --stdin
To give a machine or a colleague their own login, add --user <name> with a password on standard input. That user reaches only rest:https://<host>/<name>/..., so their backups are invisible to everyone else. Remove the login again with --delete <name> (the repositories stay in /var/lib/restic/<name>). Passwords are 12 to 128 printable characters without spaces or :. Rest Server caches logins, so the helper restarts it to apply the change:
printf '%s\n' '<paste-a-password-for-alice>' | sudo rest-server-set-password --user alice --stdin
sudo rest-server-set-password --delete alice
Step 10: Your own DNS name and certificate
Point a DNS record at the instance, then set the name Rest Server is served as. This mints a new self signed certificate for the name (and the instance's IP addresses), updates nginx and the URL in the credentials file, and keeps the name across reboots. Give your clients the new certificate afterwards:
sudo rest-server-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running rest-server-set-hostname again replaces it with a self signed one). Clients that trust your authority no longer need RESTIC_CACERT:
sudo install -m 0644 <your-domain>.crt /etc/nginx/tls/rest-server.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/tls/rest-server.key
sudo nginx -t && sudo systemctl reload nginx
Step 11: The repository volume and backing up the server
Repositories live on the separate EBS volume, owned by the restic service account and mounted without device files, set-uid programs or execution:
findmnt -no SOURCE,FSTYPE,OPTIONS /var/lib/restic
df -h --output=size,used,avail,target /var/lib/restic
sudo stat -c '%a %U:%G %n' /var/lib/restic
/dev/nvme1n1 ext4 rw,nosuid,nodev,noexec,relatime
Size Used Avail Mounted on
9.8G 1.1M 9.3G /var/lib/restic
750 restic:restic /var/lib/restic
To grow it, modify the volume's size in the EC2 console, then run sudo resize2fs on its device (shown by findmnt above). Protect the backups themselves with EBS snapshots of this volume (from the EC2 console, aws ec2 create-snapshot, or Amazon Data Lifecycle Manager on a schedule), or copy the repositories to a second location with restic copy. The repository contents are encrypted by restic, so a snapshot is only useful together with the repository passwords your clients use.
Step 12: Server components
| Component | Path |
|---|---|
| Rest Server release binary | /opt/rest-server/rest-server |
| Licence and upstream README | /usr/share/doc/rest-server/ |
| Repositories (EBS volume) | /var/lib/restic/<user>/<repository> |
| Login file (bcrypt) | /etc/rest-server/.htpasswd |
| Service unit | /etc/systemd/system/rest-server.service (--private-repos, loopback only) |
| nginx site | /etc/nginx/sites-available/cloudimg-rest-server |
| Certificate and key | /etc/nginx/tls/ |
| First boot script | /usr/local/sbin/rest-server-firstboot.sh |
| Helpers | /usr/local/sbin/rest-server-set-password, rest-server-set-hostname, rest-server-append-only |
| Credentials | /root/rest-server-info.txt (mode 0600) |
/opt/rest-server/rest-server --version
cat /opt/rest-server/VERSION
nginx -v 2>&1
ls -1 /usr/share/doc/rest-server
rest-server version rest-server 0.14.0 compiled with go1.24.3 on linux/amd64
Rest Server 0.14.0 (official release, BSD-2-Clause, sha256 4c9c95bc079a0334e81fad379b19dc5c3353c71c2c88d652cafce2081c2b1c66)
nginx version: nginx/1.24.0 (Ubuntu)
LICENSE
README.md
Logs: sudo journalctl -u rest-server for Rest Server, /var/log/nginx/access.log for requests (with the real client address).
To move to a newer Rest Server release, download it from the Rest Server releases page, check it against the release's SHA256SUMS, replace /opt/rest-server/rest-server and run sudo systemctl restart rest-server. Repositories in /var/lib/restic are kept.
Step 13: Security recommendations
- Allow port 22 in the security group from your own networks only, and port 443 only from the machines you back up.
- Give every machine or team its own login (Step 9), so a leaked password exposes one namespace, and rotate a password if it may have been shared.
- Turn on append only mode (Step 8) for machines that could be compromised, and expire snapshots from a trusted place.
- Keep the restic repository passwords somewhere other than the machines you back up: without them the backups cannot be read.
- Use your own DNS name and certificate (Step 10), and snapshot the repository volume (Step 11).
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Rest Server is open source under the BSD 2-Clause licence, and restic under the BSD 2-Clause licence; there is no per user or per CPU licence fee. The Rest Server licence is in /usr/share/doc/rest-server/. cloudimg is not affiliated with or endorsed by the restic project. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA