Back up a directory with restic

Back up a directory to a local disk or S3-compatible storage with restic: init a repo, exclude files, restore, prune old snapshots and schedule runs.

how-to8 steps

Updated

in short

Create a repository with restic init, keep the password in a file referenced by RESTIC_PASSWORD_FILE, run restic backup on a schedule, and trim old snapshots with restic forget --prune.

restic stores encrypted, deduplicated snapshots in a repository. The repository can be a local directory, an SFTP server, or an S3-compatible bucket, and every command takes the same flags whichever backend you use.

This guide backs up one directory, restores from it, sets a retention policy, verifies the repository, and runs the whole thing from cron or a systemd timer.

Tools used

Before you start

  • Linux, macOS, BSD or Windows
  • A target: a mounted disk, or an S3-compatible bucket with an access key and secret key
  • Somewhere safe to keep a copy of the repository password, such as a password manager

Steps

  1. Step 1: Install restic

    Install it from your package manager. Distribution packages can lag behind; the official binaries from the GitHub releases page can update themselves with restic self-update.

    sh
    # macOS or Linux with Homebrew
    brew install restic
    
    # Debian / Ubuntu
    sudo apt-get install restic
    
    # Fedora
    sudo dnf install restic
    
    # Arch Linux
    sudo pacman -S restic
    sh
    restic version
  2. Step 2: Store the repository password in a file

    Every restic command needs the repository password. Instead of typing it, put it in a file readable only by you and point RESTIC_PASSWORD_FILE at it. RESTIC_REPOSITORY sets the repository so you can drop -r from every command.

    If you lose this password, the data in the repository cannot be recovered. Keep a second copy somewhere other than the machine you are backing up.

    sh
    mkdir -p ~/.config/restic
    head -c 32 /dev/urandom | base64 > ~/.config/restic/password
    chmod 600 ~/.config/restic/password
    sh
    export RESTIC_PASSWORD_FILE=~/.config/restic/password
    
    # Local disk
    export RESTIC_REPOSITORY=/mnt/backup/restic-repo
    
    # Or an S3-compatible bucket
    export RESTIC_REPOSITORY=s3:https://s3.example.com/my-bucket/restic
    export AWS_ACCESS_KEY_ID=<your-access-key-id>
    export AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
  3. Step 3: Initialize the repository

    restic init creates the repository structure and encryption keys at RESTIC_REPOSITORY. Run it once per repository. For Amazon S3 itself, use the region endpoint, for example s3:s3.us-east-1.amazonaws.com/bucket_name. Other S3-compatible services take s3:https://server:port/bucket_name; if the service needs a region, set AWS_DEFAULT_REGION.

    sh
    restic init
    output
    created restic repository 085b3c76b9 at /mnt/backup/restic-repo
    
    Please note that knowledge of your password is required to access
    the repository. Losing your password means that your data is
    irrecoverably lost.
  4. Step 4: Run a backup with excludes

    --exclude takes a pattern and can be repeated. --exclude-file reads one pattern per line, with # comments. --exclude-caches skips the contents of any directory that contains a CACHEDIR.TAG file. Patterns are matched against full paths, must match whole path components, and support ** for any number of directories. ~ is not expanded inside an exclude file; use $HOME.

    Add --dry-run -vv first to see what would be stored without writing anything.

    sh
    cat > ~/.config/restic/excludes.txt <<'EOF'
    # build output and dependencies
    node_modules
    $HOME/projects/**/target
    *.tmp
    EOF
    sh
    restic backup ~/projects \
      --exclude-file ~/.config/restic/excludes.txt \
      --exclude-caches \
      --tag daily
  5. Step 5: List snapshots and restore

    restic snapshots lists every snapshot with its ID, time, host, tags and paths. restic restore takes a snapshot ID or latest and writes files under --target. --include restricts the restore to matching paths. Restore into an empty directory first, check the result, then copy files back.

    sh
    restic snapshots
    sh
    # whole snapshot
    restic restore latest --target /tmp/restore
    
    # a single directory from a specific snapshot
    restic restore 79766175 --target /tmp/restore --include /home/me/projects/site
    sh
    # find where a file lives in a snapshot
    restic ls latest | grep report.odt
  6. Step 6: Apply a retention policy with forget --prune

    forget removes snapshots that fall outside the policy; --prune then deletes the data no remaining snapshot references. Without --prune the repository does not shrink. The policy is applied per group of host and paths, so one machine's policy does not delete another's snapshots. Run with --dry-run first to see which snapshots would go.

    sh
    restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --dry-run
    sh
    restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --prune
  7. Step 7: Check the repository

    restic check verifies the repository structure. It does not read the stored file contents unless you ask: --read-data reads everything, and --read-data-subset reads a part, either a fraction like 1/5 or a random percentage like 5%. Reading a small random subset after each backup spreads the cost over time.

    sh
    restic check
    sh
    restic check --read-data-subset=5%
  8. Step 8: Schedule it with cron or a systemd timer

    Put the variables in one file, without export, so systemd and a shell script can both read it. Make it readable only by its owner, since it holds credentials.

    For cron, wrap the commands in a script that loads the file with set -a, then add a crontab entry. For systemd, use a oneshot service plus a timer; Persistent=true runs a missed backup at the next boot.

    sh
    # /etc/restic/env  (chmod 600)
    RESTIC_REPOSITORY=s3:https://s3.example.com/my-bucket/restic
    RESTIC_PASSWORD_FILE=/etc/restic/password
    AWS_ACCESS_KEY_ID=<your-access-key-id>
    AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
    sh
    #!/bin/sh
    # /usr/local/bin/restic-backup
    set -e
    set -a; . /etc/restic/env; set +a
    restic backup /home/me/projects --exclude-file /etc/restic/excludes.txt --exclude-caches
    restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --prune
    restic check --read-data-subset=5%
    sh
    # crontab -e: every night at 03:00
    0 3 * * * /usr/local/bin/restic-backup >> /var/log/restic-backup.log 2>&1
    ini
    # /etc/systemd/system/restic-backup.service
    [Unit]
    Description=restic backup
    Wants=network-online.target
    After=network-online.target
    
    [Service]
    Type=oneshot
    EnvironmentFile=/etc/restic/env
    ExecStart=/usr/bin/restic backup /home/me/projects --exclude-file /etc/restic/excludes.txt --exclude-caches
    ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --prune
    ini
    # /etc/systemd/system/restic-backup.timer
    [Unit]
    Description=Nightly restic backup
    
    [Timer]
    OnCalendar=*-*-* 03:00:00
    Persistent=true
    RandomizedDelaySec=15m
    
    [Install]
    WantedBy=timers.target
    sh
    sudo systemctl daemon-reload
    sudo systemctl enable --now restic-backup.timer
    systemctl list-timers restic-backup.timer
    journalctl -u restic-backup.service

Troubleshooting

  • wrong password or no key found: the password file does not match this repository. Check RESTIC_PASSWORD_FILE and RESTIC_REPOSITORY.
  • repository is already locked: another restic process is running, or one crashed. If none is running, restic unlock removes stale locks.
  • Exit code 3 from backup: some source files could not be read. A snapshot was still saved; read the log for the paths.
  • Repository size does not drop after forget: you ran it without --prune. Run restic prune.
  • Cron job fails but the manual run works: cron has a minimal environment. Load the variables in the script and use full paths.
  • systemd: the binary path differs by install method. Use the output of command -v restic in ExecStart.

Tips

  • --password-command (or RESTIC_PASSWORD_COMMAND) reads the password from a command, such as a password manager CLI.
  • restic backup --tag adds tags; restic forget --keep-tag keeps tagged snapshots regardless of the other rules.
  • Since restic 0.17, exit codes 10, 11 and 12 mean missing repository, lock failure and wrong password.

FAQ

Is the data encrypted on S3?
Yes. restic encrypts file contents and metadata before upload, so the storage provider only sees encrypted blobs. The key is protected by your repository password.
How often should I run prune?
`forget --prune` rewrites part of the repository and takes longer than a backup. Running it daily is fine for small repositories; for large ones, back up daily and prune weekly.
Can two machines share one repository?
Yes. Snapshots record the host name, and retention policies are applied per host and path by default, so each machine's snapshots are handled separately.

related