For reliable VPS backups, it is not enough to store the only backup archives on the same server: if a disk fails, the VPS is deleted, or the system is compromised, you can lose both production data and local backups at the same time. Therefore, we will send backups to external S3-compatible Object Storage using Restic.
In this guide, we will configure:
- A dedicated S3 bucket for backups;
- Secure storage for access keys and the Restic password;
- Backup encryption with Restic;
- Backing up VPS files and a database dump;
- Automatic scheduled execution;
- A retention policy and removal of outdated snapshots;
- Repository integrity checks;
- A mandatory test restore of files and the database from external storage.
As a result, backups will be stored independently of the VPS, and their recoverability will be verified in practice.
How VPS backups work with Restic
A backup of data from a VPS must be able to survive not only accidental file deletion, but also more serious scenarios: disk corruption, accidental deletion of the virtual machine, infrastructure failure, or compromise of the server itself. For this reason, in this setup we will store backup data not alongside the original, but in external S3-compatible Object Storage.
To work with remote storage, we will use Restic, a backup utility that supports S3 and S3-compatible services, deduplication, and data encryption. Restic transfers only the necessary data to the repository and encrypts the contents of backups before sending them to storage.
Why a Backup Should Be Stored Outside the VPS
Creating an archive of a website or database, for example in /backup on the same VPS, protects only against some user errors, although it is convenient for quick data recovery. However, if the virtual machine itself is lost, both the original data and the archive stored alongside it will be lost as well.
An external copy solves this problem:
- Production data remains on the VPS;
- Backups are sent to a separate Object Storage;
- Deletion or corruption of the VPS does not destroy the S3 repository;
- Recovery can be performed on another virtual machine.
This approach is also useful during migration: the new server does not need access to the disks of the old VPS; it only needs access to the backup repository.
Object Storage, however, is not a substitute for a backup policy. In addition to storing copies externally, you need to configure a schedule and a retention policy, and regularly verify that recovery is possible.
How Restic Works with S3-Compatible Object Storage
Restic creates its own repository inside the bucket. Instead of working with individual .tar.gz archives, the user works with snapshots—the state of the selected files at the time the backup was performed.
To connect, you use:
- S3 endpoint URL;
- Bucket name;
- Access Key;
- Secret Key;
- Restic repository password.
For an S3-compatible service, the repository is specified in the following format: s3:https://s3.example.com/vps-backups
The specific endpoint depends on the Object Storage service being used.
S3 credentials are passed through standard environment variables:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
Restic officially supports Amazon S3 and S3-compatible storage services and uses the corresponding credentials to access the bucket.
An important feature of Restic is built-in encryption. When a repository is created, a separate password is set and is required to decrypt the backup data. Even if someone gains access to the contents of the bucket, the files themselves cannot be read without Restic’s key material. Conversely, losing the password means losing the ability to restore the data, so it must be stored separately and securely.
What we will back up: files and the database
In this example, we will back up two types of data:
/var/www/example
/var/backups/database
The first directory contains the application or website files.
The database requires a separate approach. Instead of simply copying PostgreSQL or MySQL files directly from the DBMS working directory, a logical dump is created first: /var/backups/database/app.sql
Restic then includes this file in the overall snapshot.
The script will look like this:

After a successful backup, the temporary SQL dump can be removed from the VPS. The permanent backup will remain in the external Object Storage.
Preparing S3-compatible storage
Before installing Restic, you need to prepare the backup destination. We will create a dedicated bucket and separate credentials specifically for backups.
Do not use the same keys for backups that are used to manage other buckets or the entire Object Storage infrastructure: if they are leaked, the consequences will be significantly broader.
Creating a dedicated bucket for backups
In the console of the S3-compatible Object Storage service you are using, create a new bucket, for example: vps-restic-backups
This bucket is intended exclusively for Restic. There is no need to store website files, user uploads, or other application data in it.
If the provider allows you to choose a region, it is best to use one that meets your requirements for geographic location and storage cost.
For a truly external backup, the bucket should preferably not depend on the disk or file system of the same VPS. It is good practice to use S3 with another cloud provider so that you are not dependent on a single vendor. Amazon S3 and compatible object storage services are designed, among other things, for backup and restore scenarios.
After creating it, note the following:
Bucket: vps-restic-backups
S3 endpoint: https://s3.example.com
Endpoints vary between providers, so in the commands below you must use the address of your own Object Storage service.
Creating Access Keys for Object Storage
The next step is to create a separate pair of credentials for Restic.
An S3-compatible API typically uses two values:
- Access Key
- Secret Key
The Access Key identifies the user or service account, while the Secret Key is used to sign requests.
Restic will need these values on the VPS:
AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
Save the Secret Key immediately after creating it: some control panels display it only once.
For backups, it is best to create a dedicated service account, for example: restic-backup
In this case, the key can be rotated or revoked independently of other applications.
Restricting key access to only the required bucket
The Restic credentials do not need access to all of Object Storage. They only need to work with the backup bucket.
If the service you use supports IAM-like policies, permissions should be limited to the following resources:
vps-restic-backups
vps-restic-backups/*
For example, for AWS S3, a restricted access policy can include operations on a specific bucket and the objects inside it. AWS also recommends applying the principle of least privilege: grant only the actions and resources that are actually required for the task.
In simplified form, the policy might look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::vps-restic-backups"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::vps-restic-backups/*"
]
}
]
}
For a specific S3-compatible provider, the permission set and the policy creation interface may differ, but the principle remains the same: the backup key should work only with the dedicated bucket.
After configuration, make sure the bucket has been created and is available in the Object Storage control panel.
Installing and Preparing Restic on a VPS
After creating the external storage, you can move on to the VPS. Here, we will install Restic and prepare the configuration so that the S3 keys and repository password do not need to be specified directly in the backup commands.
Installing Restic
On Ubuntu, first update the package list: sudo apt update
Install Restic: sudo apt install -y restic
Check the version: restic version
After installation, Restic does not require a separate continuously running server or daemon: backups are run with individual commands and will later be automated using systemd.
Configuring Variables for Connecting to S3
To avoid specifying the endpoint and keys manually in every command, create a separate configuration file: sudo nano /etc/restic.env
Add:
AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY
AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
RESTIC_REPOSITORY=s3:https://s3.example.com/vps-restic-backups
RESTIC_PASSWORD_FILE=/etc/restic-password
Replace:
YOUR_ACCESS_KEY
YOUR_SECRET_KEY
with your Object Storage parameters.
The RESTIC_REPOSITORY variable lets you avoid passing the -r parameter each time you run Restic. For automated operation, Restic also supports reading the password from the file specified in RESTIC_PASSWORD_FILE.
To load the configuration into the current shell session later, you can use:
set -a
source /etc/restic.env
set +a
After that, restic commands will use the specified remote repository and credentials.
Secure storage of S3 keys and the Restic password

Do not place the S3 Secret Key and encryption password directly in the backup script. If the script ends up in a Git repository or becomes accessible to other VPS users, the credentials will be exposed as well, and with them access to backups containing sensitive information.
Store the configuration separately: /etc/restic.env
Store the Restic password in a separate file as well: sudo nano /etc/restic-password
Add a long random password to this file as a single line, for example: CHANGE_THIS_TO_A_LONG_RANDOM_PASSWORD
This password is used to encrypt the repository and will be required to restore data. Keep a copy outside the VPS, for example in a password manager or another secure storage location. If the only copy of the password is lost, the backup data will become inaccessible.
Now restrict access to both files:
sudo chown root:root /etc/restic.env /etc/restic-password
sudo chmod 600 /etc/restic.env /etc/restic-password
You can check the permissions with this command: sudo ls -l /etc/restic.env /etc/restic-password
The output should look something like this:
-rw——- 1 root root … /etc/restic-password
-rw——- 1 root root … /etc/restic.env
Creating a Restic Repository in Object Storage
After installing Restic and preparing the environment variables, you can create a remote backup repository inside an S3 bucket. This is where Restic will write snapshots, internal indexes, and encrypted data blocks.
Initializing the remote repository
First, load the variables from the configuration file:
set -a
source /etc/restic.env
set +a
Now initialize the repository: sudo -E restic init
Restic will create the required structure inside the bucket specified in RESTIC_REPOSITORY. The password will be read from the file specified via RESTIC_PASSWORD_FILE.
If initialization is successful, a message will appear indicating that a new repository has been created.
You do not need to run restic init again for the same repository. After initialization, all subsequent commands work with the existing storage.
Checking the connection to S3 storage

You can check whether the repository is available with the following command: sudo -E restic snapshots
Immediately after creation, the snapshot list will be empty, but Restic should successfully connect to the bucket and read the repository structure.
You can also run: sudo -E restic check
This command checks the repository’s internal structure and lets you verify that Restic can actually access the data in Object Storage.
After that, the remote storage is ready to use. You can now proceed to creating the first backup from the VPS itself.
Backing Up VPS Files
You usually do not need to copy the server’s entire file system. A backup should include the data you will actually need for recovery: application files, user uploads, service configurations, and other important directories.
Selecting directories to back up
For this example, assume that the application is located at: /var/www/example
Create a test directory and a few files:
sudo mkdir -p /var/www/example
echo “Application data” | sudo tee /var/www/example/index.txt
echo “Important configuration” | sudo tee /var/www/example/config.txt
Before creating a backup, it is useful to check the contents: sudo find /var/www/example -maxdepth 2 -type f
In a real project, you can back up several directories at once, for example:
/var/www/example
/etc/nginx
/etc/systemd/system
Keep in mind that the backup should contain data that is actually useful for recovery, not the entire set of temporary OS files.
Excluding temporary and unnecessary files
Caches, development logs, temporary archives, and other easily recoverable data should be excluded. This reduces the size of snapshots and the number of unnecessary changes between backups.
Create an exclusion file: sudo nano /etc/restic-excludes
For example:
*.tmp
*.cache
__pycache__
node_modules
.cache
After saving the file, set standard system permissions:
sudo chown root:root /etc/restic-excludes
sudo chmod 644 /etc/restic-excludes
Restic lets you pass this file using the –exclude-file option. Exclusions are applied before files are sent to the backup.
Creating the first backup

Make sure the Restic variables are loaded:
set -a
source /etc/restic.env
set +a
Create a backup:
sudo -E restic backup /var/www/example \
–exclude-file=/etc/restic-excludes \
–tag files
Restic will scan the directory, identify new data, encrypt it, and transfer it to the remote S3 repository.
When the operation is complete, Restic will display statistics: the number of files processed, the amount of data added, and the snapshot ID.
Check the list: sudo -E restic snapshots
The first entry with the files tag should appear in the list.
If necessary, you can view the contents of the latest snapshot: sudo -E restic ls latest
Subsequent backups of the same directory are usually faster and use less additional storage because Restic uses deduplication and does not store identical data blocks again.
Database Backup
Application files are only part of the data. If the service uses databases such as PostgreSQL or MySQL, they must also be handled separately.
Directly copying the files of a running DBMS can result in an inconsistent copy. For small and medium-sized projects, it is simpler to first create a logical dump using the database’s own tools, and then send that file to Restic.
Creating a PostgreSQL or MySQL Dump
For PostgreSQL, use pg_dump.
For example:
sudo mkdir -p /var/backups/database
sudo chmod 700 /var/backups/database
Create the dump: sudo -u postgres pg_dump app_db > /var/backups/database/app.sql
If you use a separate PostgreSQL user: pg_dump -h 127.0.0.1 -U app_user app_db > /var/backups/database/app.sql
For MySQL or MariaDB, the same operation is performed with mysqldump: mysqldump -u app_user -p app_db > /var/backups/database/app.sql
In both cases, the result is a standard SQL file that can then be backed up together with other data.
You can verify that it exists with the following command: sudo ls -lh /var/backups/database/app.sql
Adding a database dump to the backup

After creating the dump, load the Restic environment again:
set -a
source /etc/restic.env
set +a
Add the directory containing the dump to the remote repository:
sudo -E restic backup /var/backups/database \
–tag database
After the command completes successfully, check the snapshots: sudo -E restic snapshots
The list should now contain at least two backups with different tags:
files
database
You can also check the contents of the latest database snapshot: sudo -E restic ls latest
The list should include: /var/backups/database/app.sql
Deleting the temporary local dump after the backup
After Restic has successfully uploaded the dump to the remote S3 repository, the local file no longer needs to be kept permanently on the VPS.
Delete it: sudo rm -f /var/backups/database/app.sql
You can check the directory with the following command: sudo ls -la /var/backups/database
This is especially important if dumps are created regularly: without cleanup, they will accumulate on the local disk and gradually consume free space.
In an automated workflow, deletion should be performed only after the restic backup command completes successfully. If the data upload fails, keep the dump until the next attempt so that the prepared backup is not lost.
Automating backups
A manual run is suitable for the initial check, but regular backups should be created automatically. To do this, we will move the sequence of steps into a separate script and run it on a schedule using a systemd timer.
The script will include creating a database dump, backing up the files and the dump with Restic, and deleting the temporary SQL file only after the data has been successfully uploaded to Object Storage.
Creating a backup script
Create the file: sudo nano /usr/local/sbin/restic-backup.sh
Add the following:
#!/bin/bash
set -euo pipefail
set -a
source /etc/restic.env
set +a
BACKUP_DIR="/var/backups/database"
DUMP_FILE="$BACKUP_DIR/app.sql"
mkdir -p "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"
echo "Creating database dump…"
sudo -u postgres pg_dump app_db > "$DUMP_FILE"
echo "Backing up application files…"
restic backup /var/www/example \
–exclude-file=/etc/restic-excludes \
–tag files
echo "Backing up database dump…"
restic backup "$BACKUP_DIR" \
–tag database
echo "Removing local database dump…"
rm -f "$DUMP_FILE"
echo "Backup completed successfully"
The set -euo pipefail construct
causes the script to exit if a command fails, an undefined variable is referenced, or an error occurs in a pipeline. This is important for backups: for example, if pg_dump or Restic fails, the script must not continue running as if the backup had been created.
Make the file executable: sudo chmod 700 /usr/local/sbin/restic-backup.sh
Before automating it, be sure to test it manually: sudo /usr/local/sbin/restic-backup.sh
If it runs successfully, the following message will appear at the end: Backup completed successfully
This way, we first validate the backup logic itself and only then hand control over to systemd.
Configuring scheduled execution with a systemd timer
To run backups on a schedule, you can use crontab, but we will use systemd. We need two unit files: a service that defines the backup command, and a timer that determines when to run it.
Create the service: sudo nano /etc/systemd/system/restic-backup.service
Add:
[Unit]
Description=Restic VPS backup to S3-compatible storage
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/restic-backup.sh
Type=oneshot is suitable for tasks that start, perform a set of actions, and then exit. Restic does not require a continuously running process.
Now create the timer: sudo nano /etc/systemd/system/restic-backup.timer
For example, to run it daily at 03:00:
[Unit]
Description=Daily Restic backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
Unit=restic-backup.service
[Install]
WantedBy=timers.target
The Persistent=true setting is useful if the VPS was powered off at the scheduled time. On the next boot, systemd can run the missed job.
Reload the configuration: sudo systemctl daemon-reload
Enable and start the timer: sudo systemctl enable –now restic-backup.timer
You can check the schedule with the following command: systemctl list-timers –all | grep restic-backup
The output will show the next and previous run times.
Verifying Scheduled Backup Execution

You do not need to wait until 03:00 to check it. The service can be started manually in the same way the timer will invoke it: sudo systemctl start restic-backup.service
After it completes, check the status: sudo systemctl status restic-backup.service –no-pager
For a oneshot service, after a successful run it is normal to see a status like:
Active: inactive (dead)
along with:
status=0/SUCCESS
You can view the result in more detail in the journal: sudo journalctl -u restic-backup.service -n 50 –no-pager
Then make sure that new snapshots have appeared:
set -a
source /etc/restic.env
set +a
sudo -E restic snapshots
Thus, creating future backups no longer requires manually logging in to the VPS: systemd will run the same verified script on the configured schedule.
Configuring a Retention Policy
Regular backups will quickly create dozens or hundreds of snapshots. Keeping every copy indefinitely is usually unnecessary, so Restic lets you define a retention policy and automatically delete outdated snapshots.
A retention policy does not control backup creation; it determines which previously created versions should remain available for restore.
How many daily, weekly, and monthly backups to retain
The specific retention scheme depends on how often the data changes and on the project requirements. For a small VPS, you could use, for example:
7 daily backups
4 weekly backups
6 monthly backups
This approach provides a detailed history for the past week while also retaining several older restore points.
In Restic, this is expressed with the following parameters:
–keep-daily 7
–keep-weekly 4
–keep-monthly 6
It is important to keep in mind that Restic uses deduplication, so each snapshot does not necessarily take up storage space equal to the full size of the source data. Unchanged blocks are not stored again.
Removing old snapshots with forget
First, it is useful to see which snapshots Restic would remove according to the policy, without actually deleting anything:
sudo -E restic forget \
–keep-daily 7 \
–keep-weekly 4 \
–keep-monthly 6 \
–dry-run
–dry-run lets you check the result of the policy without modifying the repository.
If the list looks correct, run the command without this option:
sudo -E restic forget \
–keep-daily 7 \
–keep-weekly 4 \
–keep-monthly 6
forget removes references to snapshots that no longer match the retention policy.
However, space in Object Storage may not be freed immediately: unused data blocks still remain in the repository.
Reclaiming space with prune

To physically remove data that no longer belongs to any retained snapshot, use: sudo -E restic prune
Restic analyzes the repository contents and removes unnecessary blocks.
The two operations are often combined:
sudo -E restic forget \
–keep-daily 7 \
–keep-weekly 4 \
–keep-monthly 6 \
–prune
For a small repository, this approach is convenient. However, prune can add extra load and perform a significant number of operations in object storage. Therefore, for large repositories, it can be run less frequently, for example once a week or once a month.
After applying the policy, list the snapshots again: sudo -E restic snapshots
The list will contain only the snapshots that match the configured retention rules. It is best to use this together with the S3 bucket’s own policies to get immutable backups (for example, allowing files to be added for one week but not deleted). If an attacker gains access to the Restic credentials, they still will not be able to delete current data. However, this configuration depends on the S3 provider and is outside the scope of this guide.
The retention policy can be added to the same backup script or moved to a separate systemd timer. The second option is more convenient if backups are created daily but prune needs to run much less frequently.
Verifying Backups
The presence of files in an S3 bucket does not guarantee that data can actually be restored from them. For this reason, the backup repository must be checked periodically using Restic’s own tools.
This check complements the test restore, which we will perform in the next step.
Viewing available snapshots
To list all saved restore points, run:
set -a
source /etc/restic.env
set +a
sudo -E restic snapshots
For each snapshot, Restic shows the ID, creation time, hostname, tags, and saved paths.
For example, you can use tags to view file backups separately: sudo -E restic snapshots –tag files
And database snapshots: sudo -E restic snapshots –tag database
This tagging makes it easier to find the required restore point, especially when a single repository is used for multiple data types.
You can view the contents of a specific snapshot with the following command: sudo -E restic ls latest
Checking repository integrity with restic check
Use the following command to check the repository structure: sudo -E restic check
Restic checks the metadata, links between objects, and the correctness of the repository structure.
For a more thorough check, you can also read data from the storage: sudo -E restic check –read-data
This check takes more time and network traffic because Restic actually reads the stored data from Object Storage.
For large repositories, you can check only part of the data: sudo -E restic check –read-data-subset=10%
This makes it possible to monitor the storage state regularly without reading the entire backup set on every run.
A successful restic check confirms the integrity of the repository structure, but an actual restore remains the definitive test. That is why, in the next section, files will be extracted from S3 into a separate directory and verified independently of the originals on the VPS.
Test Restore from S3
Successfully creating snapshots, or even running the restic check command, does not prove that a backup is suitable for real-world recovery. The backup process should therefore be tested at least periodically end to end: retrieve the data from the external S3 storage, open the restored files, and make sure that the database dump is also accessible.
It is best to perform the test in a separate directory. This prevents the restored data from overwriting production files on the VPS and allows you to compare the result with the original.
Creating a separate directory for restoration
Create a test directory: sudo mkdir -p /var/restore-test
Restrict access to it: sudo chmod 700 /var/restore-test
Before restoring, load the Restic configuration:
set -a
source /etc/restic.env
set +a
View the available snapshots: sudo -E restic snapshots
To restore application files, you can select the latest snapshot with the relevant tag: sudo -E restic snapshots –tag files
This lets you restore the file backup specifically, without relying only on the creation time of the other snapshots.
Restoring Files from the Latest Snapshot
Let’s restore the latest snapshot tagged files to a test directory:
sudo -E restic restore latest \
–tag files \
–target /var/restore-test
Restic will recreate the original directory structure under the target path. If the snapshot contained the path /var/www/example, the restored files will appear here: /var/restore-test/var/www/example
The original application files are not modified. This lets you safely verify the backup even on a running VPS.
Verifying restored files
List the contents of the restored directory: sudo find /var/restore-test/var/www/example -maxdepth 2 -type f
For test files, you can also check the contents: sudo cat /var/restore-test/var/www/example/index.txt
For example, the file created earlier should contain: Application data
If necessary, you can compare the original and the restored copy:
sudo diff -r \
/var/www/example \
/var/restore-test/var/www/example
If there are no differences, diff will produce no output.
This verifies not only that a snapshot exists in the repository, but also that the original data can actually be retrieved from external Object Storage.
Restoring a database dump

Separately, restore the snapshot containing the database dump: sudo mkdir -p /var/restore-database
Run:
sudo -E restic restore latest \
–tag database \
–target /var/restore-database
After the restore, the dump will be located at its original path inside the target directory: /var/restore-database/var/backups/database/app.sql
Check it: sudo ls -lh /var/restore-database/var/backups/database/app.sql
For PostgreSQL, you can also inspect the SQL dump: sudo head -n 20 /var/restore-database/var/backups/database/app.sql
In a full disaster recovery test, the dump should be loaded into a separate test database. For example: sudo -u postgres createdb app_restore_test
Then:
sudo -u postgres psql app_restore_test \
< /var/restore-database/var/backups/database/app.sql
After the restore, you can verify that the tables exist: sudo -u postgres psql -d app_restore_test -c "\dt"
This test confirms two things at once: the SQL file was indeed saved in the remote repository, and the DBMS can use it to restore the data.
After the check, you can delete the temporary test database: sudo -u postgres dropdb app_restore_test
Then clean up the restore directories: sudo rm -rf /var/restore-test /var/restore-database
At this point, the full backup cycle has been verified: the data was created on the VPS, sent to external S3 storage, deleted from the local temporary directory, and then successfully retrieved.
Backup Maintenance
After automated backups have been configured, the main tasks are to monitor job execution, periodically check the repository, and manage credentials. Pay particular attention to Object Storage access errors: if they go unnoticed, the schedule may continue to run, but no up-to-date external copies will be created.
Ideally, configure alerts for errors in the logs and for missing backups if, for some reason, the backup process did not run at all. At a minimum, send backup job results by email.
Viewing backup service logs
Because backups are started via systemd, you can view the log from the most recent run with: sudo journalctl -u restic-backup.service -n 100 –no-pager
To view entries for the current day: sudo journalctl -u restic-backup.service –since today
You can also check the timer itself separately: systemctl status restic-backup.timer –no-pager
And the schedule for upcoming runs: systemctl list-timers –all | grep restic-backup
In the logs, pay attention not only to the service completion status, but also to messages from Restic itself: connection errors, access denied errors, inability to open the repository, or failure to create a snapshot.
After resolving the issue, you can start the backup manually: sudo systemctl start restic-backup.service
Then verify that a new snapshot appears:
set -a
source /etc/restic.env
set +a
sudo -E restic snapshots
What to do if an Object Storage access error occurs
If Restic can no longer connect to the remote repository, first determine whether the issue is related to the network, the endpoint, or the credentials.
Verify that the configuration files exist and have the correct permissions: sudo ls -l /etc/restic.env /etc/restic-password
Then load the environment:
set -a
source /etc/restic.env
set +a
Then try to list the snapshots: sudo -E restic snapshots
Errors such as Access Denied usually indicate an issue with the Access Key, Secret Key, or bucket access policy. A connection error or name resolution error may be caused by an incorrect S3 endpoint or by network reachability issues with Object Storage.
If the key has been revoked or replaced, update the following values:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
in /etc/restic.env.
After changing the configuration, run the command again: sudo -E restic snapshots
It is important not to create a new repository with restic init if the existing repository is temporarily unavailable. You must first restore access to the original backup repository.
Rotating S3 Keys and the Restic Password
S3 keys should be replaced periodically, especially if there is any suspicion that the credentials have been compromised.
A safe sequence is as follows:
- Create a new Access Key and Secret Key pair with the same limited permissions for the backup bucket.
- Update /etc/restic.env.
- Verify access with the restic snapshots command.
- Create a test backup.
- Revoke the old S3 key only after the check has completed successfully.
This prevents the backup setup from losing access to Object Storage during rotation.
The Restic password serves a different purpose: it protects the repository contents through encryption and is not an S3 credential. Restic provides the key command for managing repository passwords.
You can view existing keys as follows: sudo -E restic key list
To add a new password, use: sudo -E restic key add
After verifying the new password, you can remove the old key with: sudo -E restic key remove ID
During rotation, it is important not to delete the last working key until access with the new password has been confirmed.
The new password must also be stored outside the VPS. The S3 bucket alone is not sufficient to restore encrypted data without the Restic password.
Conclusion

As a result, VPS backups are stored independently of the server itself in S3-compatible Object Storage. Restic encrypts the data before uploading it, creates snapshots, and uses deduplication, while separate S3 keys with limited permissions reduce the impact of a potential credential compromise.
The backup includes both the application files and a logical database dump. Backup creation is automated with a systemd timer, the retention policy limits the number of old snapshots, and the forget and prune commands make it possible to remove obsolete data from the repository.
The true test of a backup is not whether the command completed successfully, but whether the data can be restored. Therefore, the final step was to retrieve the files and the database dump from S3 as a test. Only after such a restore can the backup setup be considered practically verified.
FAQ
Why shouldn’t you store a backup only on the same VPS?
A local copy helps if individual files are accidentally deleted, but it does not protect against losing the virtual machine itself, disk corruption, or server compromise. That is why the primary backup in this setup is stored in external S3-compatible Object Storage.
Does Restic encrypt data before sending it to S3?
Yes. Restic encrypts the repository contents on the client side before transferring them to the backend. Access to backups is controlled by the repository password, so it must be stored separately from the VPS and S3 keys.
Should you copy the PostgreSQL or MySQL directory directly?
For a standard logical backup, it is better to first create a dump using the DBMS’s native tools, such as pg_dump or mysqldump, and then add the resulting file to Restic. This reduces the risk of creating an inconsistent copy of a running database’s files.
What is the difference between restic forget and restic prune?
forget removes snapshots from the list of available restore points according to the specified retention policy. prune also removes data blocks from the repository that are no longer used by any stored snapshot.
Therefore, Object Storage usage may not decrease immediately after running forget.
How often should you run restic check?
The frequency depends on the repository size and backup requirements. A standard restic check can be run regularly, while the more resource-intensive check with –read-data or –read-data-subset can be run less often.
However, restic check is not a substitute for a test restore.
Can I restore a backup on a different VPS?
Yes. This is one of the advantages of using an external repository. On the new machine, you only need to install Restic, gain access to the same S3 bucket, specify the correct repository password, and run restic restore.
What happens if you lose the Restic password?
You may lose access to the encrypted data. Therefore, the Restic password or an additional key must be stored separately from the VPS being backed up, for example, in a secure secrets manager or another trusted storage location.
Do I need to keep the SQL dump on the VPS permanently?
No. The dump can be used as a temporary file: create it before the backup, send it via Restic, and delete it after the backup completes successfully. The permanent copy will remain in the external repository.
