--- title: "How to Back Up Ubuntu 24.04 to S3 Storage with Restic" description: "Back up an Ubuntu 24.04 server to S3-compatible object storage with restic: encrypted snapshots, database dumps, a systemd timer, retention rules and restores." category: "Backup Recovery" language: en source: https://cubepath.com/docs/backup-recovery/backup-with-restic updated: 2026-09-25 --- # How to Back Up Ubuntu 24.04 to S3 Storage with Restic restic is a single-binary backup program that encrypts and deduplicates data on the client and writes it directly to local disks, SFTP servers or object storage. Because it speaks the S3 API, it is a simple way to keep an offsite copy of a server without running a second machine. In this tutorial you will back up an Ubuntu 24.04 server to an S3-compatible bucket with restic, include a database dump, schedule it with a systemd timer, apply a retention policy and restore files. ## Prerequisites To follow this guide you need: - A server running Ubuntu 24.04 LTS (for example a CubePath VPS) with a non-root user with `sudo` privileges. - An S3-compatible object storage bucket in a different location from the server, with: - the S3 endpoint URL, referred to as `your_s3_endpoint` (for example `s3.example.com`), - an empty bucket, referred to as `your_bucket`, - an access key and secret key with read and write access to that bucket. - A password manager or other safe place outside the server to store the repository password. > Note: If you do not use object storage, restic also works over SFTP with any SSH server. Replace the repository string in Step 2 with `sftp:your_user@your_backup_server_ip:/srv/restic/web01`; everything else in this guide stays the same. ## Step 1 - Installing restic restic is available in the Ubuntu repositories: ```bash sudo apt update sudo apt install restic ``` Check the installed version: ```bash restic version ``` ```output restic 0.16.4 compiled with go1.22.2 on linux/amd64 ``` The Ubuntu package is fully usable for this guide. Newer releases are published as prebuilt binaries on the [restic GitHub releases page](https://github.com/restic/restic/releases) if you need a feature added later; the repository format is compatible across these versions. ## Step 2 - Storing the repository settings restic reads its repository location, password and cloud credentials from environment variables. Keeping them in one file lets you use the same settings in your shell and in the systemd service later. Create a configuration directory that only root can read: ```bash sudo install -d -m 700 /etc/restic ``` Generate a random repository password: ```bash openssl rand -base64 32 | sudo tee /etc/restic/password > /dev/null sudo chmod 600 /etc/restic/password ``` Print it once and save it in your password manager. Without this password, the backups cannot be decrypted by anyone, including you: ```bash sudo cat /etc/restic/password ``` Now create the environment file: ```bash sudo nano /etc/restic/env ``` ```ini RESTIC_REPOSITORY=s3:https://your_s3_endpoint/your_bucket/web01 RESTIC_PASSWORD_FILE=/etc/restic/password AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key ``` The path after the bucket name (`web01`) is a prefix inside the bucket, so several servers can share one bucket with separate repositories. The `AWS_*` variable names are used for every S3-compatible provider, not only AWS. Some providers also require a region; if yours does, add `AWS_DEFAULT_REGION=your_region`. Protect the file, since it contains credentials: ```bash sudo chmod 600 /etc/restic/env ``` ## Step 3 - Initializing the repository The remaining commands run as root so restic can read every file. Open a root shell and load the settings into it: ```bash sudo -i set -a; source /etc/restic/env; set +a ``` `set -a` exports every variable defined while it is active, which is what restic needs. Now create the repository: ```bash restic init ``` ```output created restic repository 5a2c8e91f0 at s3:https://your_s3_endpoint/your_bucket/web01 Please note that knowledge of your password is required to access the repository. Losing your password means that your data is irrecoverably lost. ``` If you see an access denied error instead, check the keys, the bucket name and the endpoint URL. ## Step 4 - Creating your first backup Create a list of paths to leave out: ```bash nano /etc/restic/excludes ``` ```text /home/*/.cache /root/.cache /var/www/*/cache *.swp ``` Run the first backup of the directories that matter on a typical server: ```bash restic backup --exclude-caches --exclude-file /etc/restic/excludes \ /etc /home /root /var/www ``` `--exclude-caches` skips directories that contain a standard `CACHEDIR.TAG` file. At the end, restic prints a summary: ```output Files: 48213 new, 0 changed, 0 unmodified Dirs: 6120 new, 0 changed, 0 unmodified Added to the repository: 912.482 MiB (905.117 MiB stored) processed 48213 files, 2.140 GiB in 1:52 snapshot 3f1a2b4c saved ``` Run the same command again. This time nearly every file is listed as `unmodified` and only a few kilobytes are added, because restic only uploads data it has not seen before. ### Backing up a database Never back up the raw files of a running database. restic can read a dump from standard input and store it as a file in the snapshot, so nothing is written to the local disk. For MySQL or MariaDB: ```bash mysqldump --single-transaction --routines --events --all-databases \ | restic backup --stdin --stdin-filename mysql-all.sql --tag database ``` For PostgreSQL, use `sudo -u postgres pg_dumpall` in place of `mysqldump`. On Ubuntu 24.04, root connects to MySQL and MariaDB through the Unix socket, so no password is needed. ## Step 5 - Browsing and restoring snapshots List the snapshots in the repository: ```bash restic snapshots ``` ```output ID Time Host Tags Paths ------------------------------------------------------------------------------ 3f1a2b4c 2026-09-25 10:30:11 web01 /etc /home /root /var/www 9c0d4e1b 2026-09-25 10:34:02 web01 /etc /home /root /var/www e7f1a0c3 2026-09-25 10:36:45 web01 database /mysql-all.sql ------------------------------------------------------------------------------ 3 snapshots ``` List the files of one directory in the most recent file snapshot: ```bash restic ls latest --path /etc /etc/nginx ``` `--path /etc` makes `latest` pick the newest snapshot that includes `/etc`, so the database snapshot is ignored. Restore `/etc/nginx` into a temporary directory and compare it with the live copy: ```bash restic restore latest --path /etc --target /tmp/restic-restore --include /etc/nginx diff -r /etc/nginx /tmp/restic-restore/etc/nginx && echo "identical" ``` ```output identical ``` restic recreates the full path under the target directory and restores ownership and permissions. To go back further in time, use a snapshot ID such as `9c0d4e1b` instead of `latest`. To restore the database dump, write it to standard output and pipe it where you need it: ```bash restic dump latest --tag database /mysql-all.sql > /tmp/mysql-all.sql ``` ## Step 6 - Applying a retention policy `restic forget` decides which snapshots to keep and `--prune` deletes the data only they used. Preview it first: ```bash restic forget --dry-run --keep-daily 7 --keep-weekly 4 --keep-monthly 6 ``` This keeps the latest 7 daily, 4 weekly and 6 monthly snapshots. restic applies the policy separately to each group of host and paths, so the database snapshots and the file snapshots each keep their own history. When the output looks right, run it for real: ```bash restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune ``` ## Step 7 - Scheduling backups with a systemd timer A systemd service can load the same environment file you created in Step 2, so no wrapper script is needed. Each `ExecStart` line runs in order and the service stops at the first failure. Create the service unit: ```bash nano /etc/systemd/system/restic-backup.service ``` ```ini [Unit] Description=restic backup to object storage Wants=network-online.target After=network-online.target [Service] Type=oneshot EnvironmentFile=/etc/restic/env Environment=RESTIC_CACHE_DIR=/var/cache/restic CacheDirectory=restic ExecStart=/usr/bin/restic backup --exclude-caches --exclude-file /etc/restic/excludes /etc /home /root /var/www ExecStart=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune Nice=10 IOSchedulingClass=idle ``` `CacheDirectory=restic` makes systemd create `/var/cache/restic`, where restic keeps a local metadata cache that avoids downloading index data on every run. If you back up a database, add a line before the `forget` line that calls a small script running the `mysqldump | restic backup --stdin` pipeline from Step 4, because systemd does not interpret pipes in `ExecStart`. Create the timer unit: ```bash nano /etc/systemd/system/restic-backup.timer ``` ```ini [Unit] Description=Nightly restic backup [Timer] OnCalendar=*-*-* 03:00:00 RandomizedDelaySec=30m Persistent=true [Install] WantedBy=timers.target ``` Enable the timer and trigger one run to verify it works under systemd: ```bash systemctl daemon-reload systemctl enable --now restic-backup.timer systemctl start restic-backup.service journalctl -u restic-backup.service -n 30 --no-pager ``` The journal shows the backup summary, the `forget` report and `Finished restic-backup.service`. Check the next scheduled run with: ```bash systemctl list-timers restic-backup.timer ``` ## Step 8 - Checking repository integrity `restic check` verifies the repository structure. Adding `--read-data-subset` also downloads and verifies a portion of the actual data, which catches corruption in the storage without downloading everything each time: ```bash restic check --read-data-subset=5% ``` The command lists each phase as it runs and ends with: ```output no errors were found ``` Run this monthly. Object storage providers may charge for the data downloaded by the check, so keep the percentage modest. Leave the root shell when you are done: ```bash exit ``` ## Troubleshooting - **`Fatal: unable to open config file: Stat: ... Access Denied`**: the keys do not have access to the bucket, or the endpoint or bucket name is wrong. Test the values with your provider's console. - **`Fatal: wrong password or no key found`**: `/etc/restic/password` does not match the repository. Restore the file from your password manager. - **`unable to create lock in backend: repository is already locked`**: another restic process is running, or one was killed. When you are sure nothing is running, remove stale locks with `restic unlock`. - **The service fails with exit code 3**: some files could not be read during the backup, for example because they were deleted mid-run. The snapshot was still saved; check the journal for the file names and exclude them if they are temporary. ## Conclusion Your server now uploads encrypted, deduplicated snapshots to object storage every night, keeps a defined history and verifies it can read the data back. That gives you the offsite copy of the [3-2-1 backup rule](/docs/backup-recovery/backup-strategies-3-2-1-and-best-practices). Next, consider enabling object lock or versioning on the bucket if your provider supports it, so a compromised server cannot delete old backups, and pair restic with a fast local copy using [BorgBackup](/docs/backup-recovery/backup-with-borgbackup-deduplication) for quicker restores.