{"id":165099,"date":"2026-03-30T16:51:04","date_gmt":"2026-03-30T13:51:04","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=165099"},"modified":"2026-03-30T16:53:36","modified_gmt":"2026-03-30T13:53:36","slug":"restic-backup-s3-linux","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/restic-backup-s3-linux\/","title":{"rendered":"Restic Backup to S3-Compatible Storage on Linux"},"content":{"rendered":"\n<p>Restic takes a different approach to backups. Every piece of data is encrypted on the client before it leaves the machine, deduplication happens at the block level across all snapshots, and the repository can live on local disk, SFTP, or any S3-compatible object store. That last part is what makes it especially practical: you point restic at a bucket and forget about managing backup file hierarchies yourself.<\/p>\n\n\n\n<p>This guide walks through setting up restic with <a href=\"https:\/\/min.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">MinIO<\/a> as a self-hosted S3 backend. The exact same restic commands work with AWS S3, Backblaze B2, or Wasabi. If you already have an S3 bucket from any provider, skip the MinIO section and jump straight to initializing the repository. Restic ships as a single static binary with no dependencies, so the installation steps work on any Linux distribution. We also cover automated backups using systemd timers. For other backup tools worth considering, see our guides on <a href=\"https:\/\/computingforgeeks.com\/borgbackup-borgmatic-linux\/\" target=\"_blank\" rel=\"noreferrer noopener\">BorgBackup with Borgmatic<\/a> and <a href=\"https:\/\/computingforgeeks.com\/backup-linux-windows-kopia\/\" target=\"_blank\" rel=\"noreferrer noopener\">Kopia<\/a>.<\/p>\n\n\n\n<p><em>Tested <strong>March 2026<\/strong> | restic 0.18.1 on Ubuntu 24.04 and Rocky Linux 10.1. Backends verified: self-hosted MinIO, AWS S3 (eu-west-1), and Google Cloud Storage. Works on any Linux distribution with systemd.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Linux server as the backup client (10.0.1.50 in this guide). Any distribution with <code>systemd<\/code> works: Ubuntu, Debian, Rocky, AlmaLinux, RHEL, Fedora, SUSE, Arch, etc.<\/li>\n<li>A second server for MinIO (10.0.1.51), or an existing AWS S3 \/ Backblaze B2 \/ Wasabi account<\/li>\n<li>Root or sudo access on both machines<\/li>\n<li><code>wget<\/code>, <code>bzip2<\/code>, and <code>curl<\/code> for downloading binaries<\/li>\n<li>Tested with: <a href=\"https:\/\/restic.net\/\" target=\"_blank\" rel=\"noreferrer noopener\">restic<\/a> 0.18.1 (GitHub binary), MinIO RELEASE.2025-09-07<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Install Restic<\/h2>\n\n\n\n<p>Restic is a single static binary with zero dependencies. Distribution package managers often ship older versions, so the recommended approach is to grab the latest release directly from GitHub. This works on any Linux distribution.<\/p>\n\n\n\n<p>Pull the latest version number from the GitHub API:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VER=$(curl -sL https:\/\/api.github.com\/repos\/restic\/restic\/releases\/latest | grep tag_name | head -1 | sed 's\/.*\"v\\([^\"]*\\)\".*\/\\1\/')\necho $VER<\/code><\/pre>\n\n\n\n<p>At the time of writing, this returns:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>0.18.1<\/code><\/pre>\n\n\n\n<p>Download, extract, and install the binary:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wget https:\/\/github.com\/restic\/restic\/releases\/download\/v${VER}\/restic_${VER}_linux_amd64.bz2\nbunzip2 restic_${VER}_linux_amd64.bz2\nsudo mv restic_${VER}_linux_amd64 \/usr\/local\/bin\/restic\nsudo chmod +x \/usr\/local\/bin\/restic<\/code><\/pre>\n\n\n\n<p>On RHEL-based distributions with SELinux enforcing, fix the file context so systemd can execute it later:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo restorecon -v \/usr\/local\/bin\/restic<\/code><\/pre>\n\n\n\n<p>Verify the installation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic version<\/code><\/pre>\n\n\n\n<p>The output confirms the installed version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic 0.18.1 compiled with go1.25.1 on linux\/amd64<\/code><\/pre>\n\n\n\n<p>The <code>$VER<\/code> variable ensures you always get the latest stable release. When a new version ships, re-run the same commands to upgrade.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prepare Your Storage Backend<\/h2>\n\n\n\n<p>Restic works with any S3-compatible object store, local repositories, SFTP, and native cloud provider APIs. Pick one backend below, complete its setup, then continue to &#8220;Initialize the Repository&#8221; where the workflow is the same regardless of which option you chose.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Option A: Self-Hosted MinIO<\/h3>\n\n\n\n<p>MinIO provides an S3-compatible API on your own hardware. If you already have an AWS S3 bucket, Backblaze B2 account, or Wasabi subscription, skip this entire section. The restic commands in the rest of the guide work identically regardless of the S3 provider.<\/p>\n\n\n\n<p>Run these steps on the MinIO server (10.0.1.51 in this example). MinIO distributes a single static binary, so installation is straightforward:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wget https:\/\/dl.min.io\/server\/minio\/release\/linux-amd64\/minio\nsudo mv minio \/usr\/local\/bin\/minio\nsudo chmod +x \/usr\/local\/bin\/minio<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Fix SELinux Context for MinIO<\/h4>\n\n\n\n<p>On distributions with SELinux enforcing (RHEL, Rocky, AlmaLinux, Fedora), the downloaded binary carries a <code>user_tmp_t<\/code> context. Without fixing this, systemd refuses to execute it with exit code 203 and a &#8220;Permission denied&#8221; entry in journalctl. The fix is a single restorecon command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo restorecon -v \/usr\/local\/bin\/minio<\/code><\/pre>\n\n\n\n<p>The relabeling output confirms the context change:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Relabeled \/usr\/local\/bin\/minio from unconfined_u:object_r:user_tmp_t:s0 to unconfined_u:object_r:bin_t:s0<\/code><\/pre>\n\n\n\n<p>Skip this step on distributions using AppArmor (Ubuntu, Debian, SUSE) since AppArmor does not enforce file context labels on binaries this way.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Create the MinIO Service<\/h4>\n\n\n\n<p>Create a dedicated user and data directory for MinIO:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo useradd -r -s \/sbin\/nologin minio-user\nsudo mkdir -p \/data\/minio\nsudo chown minio-user:minio-user \/data\/minio<\/code><\/pre>\n\n\n\n<p>Create the environment file with MinIO credentials:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/default\/minio<\/code><\/pre>\n\n\n\n<p>Set these values:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MINIO_ROOT_USER=minioadmin\nMINIO_ROOT_PASSWORD=your-minio-password\nMINIO_VOLUMES=\"\/data\/minio\"\nMINIO_OPTS=\"--console-address :9001\"<\/code><\/pre>\n\n\n\n<p>Lock down permissions on that file since it contains credentials:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chmod 600 \/etc\/default\/minio<\/code><\/pre>\n\n\n\n<p>Now create the systemd unit file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/systemd\/system\/minio.service<\/code><\/pre>\n\n\n\n<p>Paste this unit file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>[Unit]\nDescription=MinIO Object Storage\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=notify\nUser=minio-user\nGroup=minio-user\nEnvironmentFile=\/etc\/default\/minio\nExecStart=\/usr\/local\/bin\/minio server $MINIO_VOLUMES $MINIO_OPTS\nRestart=always\nRestartSec=5\nLimitNOFILE=65536\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p>On Rocky Linux, set the correct SELinux context on the data directory as well:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo semanage fcontext -a -t bin_t \"\/usr\/local\/bin\/minio\"\nsudo restorecon -v \/usr\/local\/bin\/minio<\/code><\/pre>\n\n\n\n<p>Enable and start MinIO:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl daemon-reload\nsudo systemctl enable --now minio<\/code><\/pre>\n\n\n\n<p>Check the service status:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl status minio<\/code><\/pre>\n\n\n\n<p>The service should show active (running) with MinIO listening on ports 9000 (API) and 9001 (console).<\/p>\n\n\n\n<p>Open the firewall ports. On distributions using firewalld (RHEL, Rocky, AlmaLinux, Fedora):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo firewall-cmd --permanent --add-port=9000\/tcp --add-port=9001\/tcp\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n\n<p>On distributions using ufw (Ubuntu, Debian):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo ufw allow 9000\/tcp\nsudo ufw allow 9001\/tcp<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Create the Backup Bucket<\/h4>\n\n\n\n<p>Install the MinIO client (<code>mc<\/code>) to manage buckets from the command line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wget https:\/\/dl.min.io\/client\/mc\/release\/linux-amd64\/mc\nsudo mv mc \/usr\/local\/bin\/mc\nsudo chmod +x \/usr\/local\/bin\/mc<\/code><\/pre>\n\n\n\n<p>On SELinux distributions, apply the same context fix:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo restorecon -v \/usr\/local\/bin\/mc<\/code><\/pre>\n\n\n\n<p>Configure the alias and create a bucket for restic:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mc alias set local http:\/\/127.0.0.1:9000 minioadmin your-minio-password\nmc mb local\/restic-backup<\/code><\/pre>\n\n\n\n<p>The bucket creation confirms with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Bucket created successfully `local\/restic-backup`.<\/code><\/pre>\n\n\n\n<p>The MinIO Object Browser at <code>http:\/\/10.0.1.51:9001<\/code> shows the bucket. After restic initializes and runs a backup, the repository structure appears as directories (config, data, index, keys, snapshots):<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"761\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-repo.png\" alt=\"MinIO Object Browser showing restic repository structure with config, data, index, keys, and snapshots directories\" class=\"wp-image-165116\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-repo.png 1400w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-repo-300x163.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-repo-1024x557.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-repo-768x417.png 768w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><\/figure>\n\n\n\n<p>Inside the <code>data\/<\/code> directory, restic stores encrypted and deduplicated chunks in hex-prefixed subdirectories. These files are opaque without the encryption password:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"761\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-data.png\" alt=\"Restic encrypted data chunks stored in MinIO bucket data directory\" class=\"wp-image-165117\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-data.png 1400w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-data-300x163.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-data-1024x557.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/minio-restic-data-768x417.png 768w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Option B: AWS S3<\/h3>\n\n\n\n<p>Restic supports AWS S3 natively. The setup involves creating a dedicated S3 bucket, an IAM user with minimal permissions, and passing the credentials to restic.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Create an S3 Bucket and IAM User<\/h4>\n\n\n\n<p>From any machine with the AWS CLI configured, create the bucket in your preferred region:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws s3 mb s3:\/\/your-restic-backup --region eu-west-1<\/code><\/pre>\n\n\n\n<p>Create a dedicated IAM user for restic (never use your root account credentials for automated backups):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws iam create-user --user-name restic-backup<\/code><\/pre>\n\n\n\n<p>Attach an inline policy that limits this user to the backup bucket only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws iam put-user-policy --user-name restic-backup --policy-name restic-s3-policy --policy-document '{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:ListBucket\", \"s3:GetBucketLocation\"],\n      \"Resource\": \"arn:aws:s3:::your-restic-backup\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:DeleteObject\"],\n      \"Resource\": \"arn:aws:s3:::your-restic-backup\/*\"\n    }\n  ]\n}'<\/code><\/pre>\n\n\n\n<p>Generate access keys for the new user:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>aws iam create-access-key --user-name restic-backup<\/code><\/pre>\n\n\n\n<p>Save the <code>AccessKeyId<\/code> and <code>SecretAccessKey<\/code> from the output. You will need both on the backup client.<\/p>\n\n\n\n<p>Once restic initializes and backs up data, the S3 bucket contains the encrypted repository structure:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/aws-s3-bucket-contents.png\" alt=\"AWS S3 console showing restic backup repository with config, data, index, keys, and snapshots objects\" class=\"wp-image-165120\" title=\"\"><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Option C: Google Cloud Storage<\/h3>\n\n\n\n<p>Restic also has native Google Cloud Storage support. The authentication uses a service account key file rather than environment variables.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Create a GCS Bucket and Service Account<\/h4>\n\n\n\n<p>From a machine with the <code>gcloud<\/code> CLI, create the bucket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>gcloud storage buckets create gs:\/\/your-restic-backup --location=europe-west1 --project=your-project-id<\/code><\/pre>\n\n\n\n<p>Create a dedicated service account for restic:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>gcloud iam service-accounts create restic-backup \\\n  --display-name=\"Restic Backup Service Account\" \\\n  --project=your-project-id<\/code><\/pre>\n\n\n\n<p>Grant the service account permission to read and write objects in the backup bucket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>gcloud storage buckets add-iam-policy-binding gs:\/\/your-restic-backup \\\n  --member=\"serviceAccount:restic-backup@your-project-id.iam.gserviceaccount.com\" \\\n  --role=\"roles\/storage.objectAdmin\"<\/code><\/pre>\n\n\n\n<p>Generate a JSON key file and copy it to the backup client:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>gcloud iam service-accounts keys create restic-gcs-key.json \\\n  --iam-account=restic-backup@your-project-id.iam.gserviceaccount.com<\/code><\/pre>\n\n\n\n<p>Transfer the key file to the backup client and secure it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>scp restic-gcs-key.json root@10.0.1.50:\/etc\/restic-gcs-key.json\nssh root@10.0.1.50 \"chmod 600 \/etc\/restic-gcs-key.json\"<\/code><\/pre>\n\n\n\n<p>The GCS bucket in the Cloud Storage browser shows the same restic repository layout after initialization:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/03\/gcp-gcs-bucket-contents.png\" alt=\"Google Cloud Storage bucket showing restic backup repository with config, data, index, keys, and snapshots folders\" class=\"wp-image-165121\" title=\"\"><\/figure>\n\n\n\n<p>With your storage backend ready, the restic commands from here forward are identical regardless of which option you chose.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Initialize the Restic Repository<\/h2>\n\n\n\n<p>Back on the backup client (10.0.1.50), set the environment variables that restic needs to connect to the storage backend and encrypt the repository. The specific variables depend on which backend you configured above.<\/p>\n\n\n\n<p>For MinIO:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export AWS_ACCESS_KEY_ID=minioadmin\nexport AWS_SECRET_ACCESS_KEY=your-minio-password\nexport RESTIC_REPOSITORY=s3:http:\/\/10.0.1.51:9000\/restic-backup<\/code><\/pre>\n\n\n\n<p>For AWS S3:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export AWS_ACCESS_KEY_ID=your-access-key-id\nexport AWS_SECRET_ACCESS_KEY=your-secret-access-key\nexport RESTIC_REPOSITORY=s3:s3.eu-west-1.amazonaws.com\/your-restic-backup<\/code><\/pre>\n\n\n\n<p>For Google Cloud Storage:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export GOOGLE_APPLICATION_CREDENTIALS=\/etc\/restic-gcs-key.json\nexport GOOGLE_PROJECT_ID=your-project-id\nexport RESTIC_REPOSITORY=gs:your-restic-backup:\/<\/code><\/pre>\n\n\n\n<p>All backends need the encryption password:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export RESTIC_PASSWORD=your-restic-encryption-password<\/code><\/pre>\n\n\n\n<p><strong>Critical:<\/strong> the <code>RESTIC_PASSWORD<\/code> is the encryption key for every snapshot in this repository. If you lose it, the backups become unrecoverable. Store it in a password manager or a secure vault, not just in a shell history.<\/p>\n\n\n\n<p>Initialize the repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic init<\/code><\/pre>\n\n\n\n<p>Restic creates the repository structure inside the S3 bucket:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>created restic repository d4fa81b8e9 at s3:http:\/\/10.0.1.51:9000\/restic-backup\n\nPlease note that knowledge of your password is required to access\nthe repository. Losing your password means that your data is\nirrecoverably lost.<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create Your First Backup<\/h2>\n\n\n\n<p>With the repository initialized, run a backup of the directories you want to protect. This example backs up <code>\/srv\/data<\/code> and <code>\/etc<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic backup \/srv\/data \/etc --tag initial --verbose<\/code><\/pre>\n\n\n\n<p>Restic scans, deduplicates, encrypts, and uploads the data. The verbose output shows exactly what happened:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>open repository\nrepository d4fa81b8 opened (version 2, compression level auto)\nlock repository\nno parent snapshot found, will read all files\nload index files\n\nstart scan on [\/srv\/data \/etc]\nstart backup on [\/srv\/data \/etc]\n\nFiles:          81 new,     0 changed,     0 unmodified\nDirs:            7 new,     0 changed,     0 unmodified\nAdded to the repository: 44.779 MiB (44.765 MiB stored)\n\nprocessed 81 files, 44.749 MiB in 0:01\nsnapshot 02a654d5 saved<\/code><\/pre>\n\n\n\n<p>All 81 files (44.749 MiB) were encrypted and stored. The &#8220;stored&#8221; size is nearly identical because random binary data does not compress, which is expected. Text-heavy directories like <code>\/etc<\/code> typically compress to 30-50% of original size.<\/p>\n\n\n\n<p>List all snapshots in the repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic snapshots<\/code><\/pre>\n\n\n\n<p>The snapshot table shows the ID, timestamp, hostname, tags, and paths:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>repository d4fa81b8 opened (version 2, compression level auto)\nID        Time                 Host        Tags        Paths\n-----------------------------------------------------------------------\n02a654d5  2026-03-28 14:22:01  backup01    initial     \/srv\/data\n                                                       \/etc\n-----------------------------------------------------------------------\n1 snapshots<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Incremental Backups and Deduplication<\/h2>\n\n\n\n<p>Restic&#8217;s block-level deduplication is where it really shines. On subsequent runs, only new and changed blocks get uploaded. To demonstrate, modify an existing file and add a new one:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"updated config\" | sudo tee -a \/srv\/data\/configs\/app.conf\ndd if=\/dev\/urandom of=\/srv\/data\/newfile.bin bs=1K count=200 2>\/dev\/null<\/code><\/pre>\n\n\n\n<p>Now run the backup again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic backup \/srv\/data \/etc --verbose<\/code><\/pre>\n\n\n\n<p>The difference is dramatic compared to the initial backup:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>repository d4fa81b8 opened (version 2, compression level auto)\nlock repository\nusing parent snapshot 02a654d5\n\nFiles:           1 new,     1 changed,    80 unmodified\nDirs:            0 new,     2 changed,     5 unmodified\nAdded to the repository: 234.370 KiB (213.473 KiB stored)\n\nprocessed 82 files, 44.944 MiB in 0:00\nsnapshot 7b3e91f2 saved<\/code><\/pre>\n\n\n\n<p>Out of 82 files totaling 45 MiB, restic transferred only 234 KiB. It recognized the 80 unchanged files from the parent snapshot and skipped them entirely. This is what makes restic practical for daily automated backups: the network and storage cost after the first run is minimal.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Restore from Backup<\/h2>\n\n\n\n<p>Restoring an entire snapshot takes a single command. Specify the target directory where restic should write the restored files (it recreates the original directory structure inside it):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic restore latest --target \/tmp\/restore-test<\/code><\/pre>\n\n\n\n<p>The restore completes almost instantly for this dataset:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>repository d4fa81b8 opened (version 2, compression level auto)\nrestoring snapshot 7b3e91f2 of [\/srv\/data \/etc] at 2026-03-28 14:25:33\nSummary: Restored 88 files\/dirs (44.944 MiB) in 0:00<\/code><\/pre>\n\n\n\n<p>Verify the restored files match the originals:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>diff -r \/srv\/data \/tmp\/restore-test\/srv\/data<\/code><\/pre>\n\n\n\n<p>No output from diff means every file matches. You can also use <code>restic diff<\/code> to compare two snapshots directly if you need to see what changed between backups.<\/p>\n\n\n\n<p>For partial restores, use the <code>--include<\/code> flag to extract only specific paths:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic restore latest --target \/tmp\/partial --include \"\/srv\/data\/configs\/\"<\/code><\/pre>\n\n\n\n<p>This pulls just the configs directory without touching anything else. Useful when a single config file gets corrupted and you need it back quickly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Retention and Pruning<\/h2>\n\n\n\n<p>Without a retention policy, the repository grows indefinitely. The <code>restic forget<\/code> command marks old snapshots for removal based on your retention rules, and <code>--prune<\/code> reclaims the storage immediately. A sensible starting policy keeps 7 daily, 4 weekly, and 6 monthly snapshots:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune<\/code><\/pre>\n\n\n\n<p>Restic evaluates each snapshot against the retention rules and reports the outcome:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>repository d4fa81b8 opened (version 2, compression level auto)\nApplying Policy: keep 7 daily, 4 weekly, 6 monthly snapshots\n\nkeep 2 snapshots:\nID        Time                 Host        Tags        Reasons        Paths\n---------------------------------------------------------------------------------\n02a654d5  2026-03-28 14:22:01  backup01    initial     daily snapshot  \/srv\/data, \/etc\n7b3e91f2  2026-03-28 14:25:33  backup01                daily snapshot  \/srv\/data, \/etc\n---------------------------------------------------------------------------------\n2 snapshots\n\nno snapshots were removed, running prune\ncounting files in repo\n[0:00] 100.00%  8 \/ 8 packs\n\nfinding old index files\nsaved new indexes as [4a8b2c1d]\n\ndone<\/code><\/pre>\n\n\n\n<p>Since we only have two snapshots, nothing was removed. Once you accumulate more than 7 daily snapshots, the oldest ones outside the retention window get pruned automatically. Run this command after every backup (the systemd timer in the next section handles this).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Repository Integrity<\/h2>\n\n\n\n<p>Restic can verify the internal consistency of a repository regardless of backend. Run this periodically (weekly is a good starting point) to catch corruption before you need to restore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic check<\/code><\/pre>\n\n\n\n<p>A healthy repository shows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using temporary cache in \/tmp\/restic-check-cache-1547467285\ncreate exclusive lock for repository\nload indexes\ncheck all packs\ncheck snapshots, trees and blobs\n[0:00] 100.00%  1 \/ 1 snapshots\nno errors were found<\/code><\/pre>\n\n\n\n<p>For a deeper check that re-reads and verifies every data blob (slower, uses bandwidth on cloud backends):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>restic check --read-data<\/code><\/pre>\n\n\n\n<p>Use the quick <code>check<\/code> daily or weekly. Reserve <code>--read-data<\/code> for monthly verification since it downloads and re-verifies every chunk in the repository.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Automate with Systemd Timer<\/h2>\n\n\n\n<p>Manual backups are backups that don&#8217;t happen. A systemd timer runs the backup on a schedule, retries on failure, and logs everything to journald. Start by storing the credentials in a secure environment file.<\/p>\n\n\n\n<p>Create the environment file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/restic-env<\/code><\/pre>\n\n\n\n<p>Add the repository credentials matching your chosen backend. For MinIO:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>AWS_ACCESS_KEY_ID=minioadmin\nAWS_SECRET_ACCESS_KEY=your-minio-password\nRESTIC_PASSWORD=your-restic-encryption-password\nRESTIC_REPOSITORY=s3:http:\/\/10.0.1.51:9000\/restic-backup<\/code><\/pre>\n\n\n\n<p>For AWS S3, use your IAM access key and the S3 repository URL instead:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>AWS_ACCESS_KEY_ID=your-access-key-id\nAWS_SECRET_ACCESS_KEY=your-secret-access-key\nRESTIC_PASSWORD=your-restic-encryption-password\nRESTIC_REPOSITORY=s3:s3.eu-west-1.amazonaws.com\/your-restic-backup<\/code><\/pre>\n\n\n\n<p>For GCS, point to the service account key file and set the project ID:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GOOGLE_APPLICATION_CREDENTIALS=\/etc\/restic-gcs-key.json\nGOOGLE_PROJECT_ID=your-project-id\nRESTIC_PASSWORD=your-restic-encryption-password\nRESTIC_REPOSITORY=gs:your-restic-backup:\/<\/code><\/pre>\n\n\n\n<p>Lock down the file permissions so only root can read it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chmod 600 \/etc\/restic-env<\/code><\/pre>\n\n\n\n<p>Create the backup service unit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/systemd\/system\/restic-backup.service<\/code><\/pre>\n\n\n\n<p>The service runs restic backup and then prunes old snapshots in a single shot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>[Unit]\nDescription=Restic Backup to S3\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=oneshot\nEnvironmentFile=\/etc\/restic-env\nExecStart=\/usr\/local\/bin\/restic backup \/srv\/data \/etc --tag automated\nExecStartPost=\/usr\/local\/bin\/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune\nNice=10\nIOSchedulingClass=best-effort\nIOSchedulingPriority=7<\/code><\/pre>\n\n\n\n<p>The <code>Nice<\/code> and <code>IOSchedulingPriority<\/code> settings keep the backup from starving other processes on a busy server.<\/p>\n\n\n\n<p>Create the timer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vi \/etc\/systemd\/system\/restic-backup.timer<\/code><\/pre>\n\n\n\n<p>Add the timer configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>[Unit]\nDescription=Run Restic Backup Daily\n\n[Timer]\nOnCalendar=*-*-* 03:00:00\nRandomizedDelaySec=600\nPersistent=true\n\n[Install]\nWantedBy=timers.target<\/code><\/pre>\n\n\n\n<p>The <code>Persistent=true<\/code> setting ensures the backup runs on next boot if the server was powered off at 3 AM. The 10-minute random delay prevents multiple servers from hammering the S3 backend simultaneously.<\/p>\n\n\n\n<p>Enable and start the timer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl daemon-reload\nsudo systemctl enable --now restic-backup.timer<\/code><\/pre>\n\n\n\n<p>Verify the timer is scheduled:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl list-timers restic-backup.timer<\/code><\/pre>\n\n\n\n<p>The output shows when the next backup will trigger:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>NEXT                        LEFT          LAST  PASSED  UNIT                 ACTIVATES\nSun 2026-03-29 03:00:00 UTC 12h left      n\/a   n\/a     restic-backup.timer  restic-backup.service\n\n1 timers listed.<\/code><\/pre>\n\n\n\n<p>To test the backup immediately without waiting for the schedule:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl start restic-backup.service\njournalctl -u restic-backup.service --no-pager -n 20<\/code><\/pre>\n\n\n\n<p>Check the journal output to confirm the backup and pruning completed successfully. Any errors (network timeouts, permission issues, wrong credentials) appear here.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Choosing a Storage Backend<\/h2>\n\n\n\n<p>All the backends covered in this guide (MinIO, AWS S3, Google Cloud Storage) use the same restic commands for backup, restore, and maintenance. The choice comes down to cost, control, and where your infrastructure lives.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Storage Provider<\/th><th>Cost (per TB\/month)<\/th><th>Durability<\/th><th>Best For<\/th><\/tr><\/thead><tbody><tr><td>MinIO (self-hosted)<\/td><td>Hardware cost only<\/td><td>Depends on disk setup<\/td><td>Full control, no egress fees, air-gapped backups<\/td><\/tr><tr><td>AWS S3 Standard<\/td><td>$23<\/td><td>99.999999999%<\/td><td>Enterprise integration, lifecycle policies, Glacier tiering<\/td><\/tr><tr><td>Google Cloud Storage<\/td><td>$20 (Standard)<\/td><td>99.999999999%<\/td><td>GCP-native workloads, multi-region replication<\/td><\/tr><tr><td>Backblaze B2<\/td><td>$6<\/td><td>99.999999999%<\/td><td>Budget cloud backup, free egress to Cloudflare<\/td><\/tr><tr><td>Wasabi<\/td><td>$7<\/td><td>99.999999999%<\/td><td>No egress fees, predictable pricing<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>For homelab and small business setups, Backblaze B2 or Wasabi hit the sweet spot between cost and durability. If you need air-gapped or on-premises backups (compliance, data sovereignty), MinIO on a separate physical server with RAID gives you that without monthly fees. AWS S3 and GCS make sense when you are already in those ecosystems and want lifecycle rules for automatic tiering to cheaper storage classes.<\/p>\n\n\n\n<p>For more Linux backup strategies, check out our guide on <a href=\"https:\/\/computingforgeeks.com\/backup-linux-mac-windows-systems-using-duplicati\/\" target=\"_blank\" rel=\"noreferrer noopener\">Duplicati for cross-platform backups<\/a> or our <a href=\"https:\/\/computingforgeeks.com\/bash-script-to-automate-linux-directories-backups\/\" target=\"_blank\" rel=\"noreferrer noopener\">bash script approach for simple directory backups<\/a>. The <a href=\"https:\/\/restic.readthedocs.io\/en\/stable\/\" target=\"_blank\" rel=\"noreferrer noopener\">restic documentation<\/a> covers additional backends including SFTP, REST server, and Azure Blob Storage.<\/p>\n\n\n\n","protected":false},"excerpt":{"rendered":"<p>Restic takes a different approach to backups. Every piece of data is encrypted on the client before it leaves the machine, deduplication happens at the block level across all snapshots, and the repository can live on local disk, SFTP, or any S3-compatible object store. That last part is what makes it especially practical: you point &#8230; <a title=\"Restic Backup to S3-Compatible Storage on Linux\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/restic-backup-s3-linux\/\" aria-label=\"Read more about Restic Backup to S3-Compatible Storage on Linux\">Read more<\/a><\/p>\n","protected":false},"author":15,"featured_media":165100,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[299,50,35910,75,663,81],"tags":[],"class_list":["post-165099","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to","category-linux-tutorials","category-rocky-linux","category-security","category-storage","category-ubuntu"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165099","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=165099"}],"version-history":[{"count":9,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165099\/revisions"}],"predecessor-version":[{"id":165124,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/165099\/revisions\/165124"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/165100"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=165099"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=165099"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=165099"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}