Automate LVM snapshots and incremental Restic uploads to S3 with systemd timers on Debian
Hospedagem & VPS · By Kevin Marcondes · Published on · 10 min read
This tutorial walks you step-by-step to automate LVM snapshots and incremental uploads to S3 using Restic on Debian, orchestrated with systemd timers. You’ll get ready-to-use scripts, systemd units and tips for verification and recovery. Root access is required—follow the steps carefully to avoid service interruptions.
Backup architecture: LVM snapshot + Restic + S3
The strategy combines three pieces: an LVM snapshot to freeze a consistent point-in-time of the volume, Restic for encrypted incremental backups, and S3 as remote storage. systemd schedules and ensures reliable execution, with centralized logs in the journal.
High-level flow: create an LVM snapshot (copy-on-write), mount it read-only, run restic backup against the snapshot, then unmount and remove the snapshot. During the backup window the system continues running on the original volume as usual.
Restic stores only changed blocks between runs, reducing bandwidth and time. Retention is managed with forget + prune. If you still use cron, consider migrating to timers for better observability and robustness.
- LVM snapshot: capture a point-in-time without stopping services.
- Restic: deduplication, encryption by default, efficient incremental backups.
- S3: resilient remote storage.
- systemd: scheduler with logs, retries and dependencies.
Prerequisites and capacity planning
Before starting, confirm your environment and size the snapshot. Incorrect LVM operations can cause downtime or data loss—test first on a staging server.
For basic server security, review the checklist in the VPS Linux security guide linked below.
- Debian 11/12 with root access.
- Packages: lvm2, restic.
- An LVM volume (VG/LV) with free space in the VG for the snapshot (snapshot size depends on write rate during backup).
- An S3-accessible bucket and IAM credentials with restricted permissions.
- Time synchronization (timers depend on correct clock).
- Plan exclusions (caches, tmp, mounts) to avoid noise in the backup.
S3 bucket and minimum IAM credentials
Create a dedicated bucket for backups (per project/environment) and an IAM user with access restricted only to that bucket. Use dedicated access keys. See the Amazon S3 documentation for creating buckets and policies.
Restic expects the repository URL in the form s3:https://endpoint/bucket/prefix. For standard S3: s3:https://s3.amazonaws.com/your-bucket/prefix. Adjust the region according to your bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ResticBackupAccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:ListBucketVersions"
],
"Resource": [
"arn:aws:s3:::SEU-BUCKET"
]
},
{
"Sid": "ResticObjectsAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucketMultipartUploads",
"s3:AbortMultipartUpload"
],
"Resource": [
"arn:aws:s3:::SEU-BUCKET/*"
]
}
]
}
Install Restic and store secure variables
Install the packages and centralize Restic credentials/variables in a root-only file. Protect this file (0600). Also create an excludes file to avoid backing caches or temporary mounts.
Common variables are in the table below—adapt them to your environment.
| Variable | Example | Description |
|---|---|---|
| RESTIC_REPOSITORY | s3:https://s3.amazonaws.com/meu-bucket/projeto1 | Restic repository URL on S3 |
| RESTIC_PASSWORD | uma-senha-forte | Repository encryption password |
| AWS_ACCESS_KEY_ID | AKIA... | AWS access key ID |
| AWS_SECRET_ACCESS_KEY | xxxxxxxx | AWS secret access key |
| AWS_DEFAULT_REGION | us-east-1 | Bucket region |
| BACKUP_TAGS | vps-01,lvm-snapshot | Tags applied to Restic snapshots |
set -euo pipefail
apt update && apt install -y lvm2 restic
install -d -m 0700 /etc/restic
cat > /etc/restic.env <<'EOF'
RESTIC_REPOSITORY=s3:https://s3.amazonaws.com/SEU-BUCKET/projeto1
RESTIC_PASSWORD=troque-esta-senha
AWS_ACCESS_KEY_ID=COLOQUE_SUA_CHAVE
AWS_SECRET_ACCESS_KEY=COLOQUE_SUA_SECRETA
AWS_DEFAULT_REGION=us-east-1
BACKUP_TAGS=vps-01,lvm-snapshot
EOF
chmod 0600 /etc/restic.env
# Exclusões do backup (ajuste conforme necessário)
install -d -m 0755 /etc/restic
cat > /etc/restic/excludes.txt <<'EOF'
/proc/*
/sys/*
/run/*
/tmp/*
/var/tmp/*
/var/cache/*
/mnt/*
/media/*
/swapfile
EOF
Initialize the repository and validate connection
Initialize the repository on S3 and run a quick test backup/list. Load credentials via environment file before calling Restic. Store the RESTIC_PASSWORD securely—without it you cannot recover data.
set -euo pipefail
set -a
. /etc/restic.env
set +a
# Inicializa o repositório (executar apenas uma vez)
restic init
# Teste rápido de backup (arquivo pequeno)
restic backup --tag test /etc/hosts --exclude-file=/etc/restic/excludes.txt
# Lista snapshots para confirmar
restic snapshots
Backup script with atomic LVM snapshot and cleanup
The script below creates a read-only LVM snapshot, mounts it, runs incremental Restic backup, and removes the snapshot with guaranteed cleanup via trap. It includes a simple lock to avoid concurrency. Adjust VG/LV, snapshot size and paths.
If you back up the root volume, ensure there is free space in the VG. For databases, consider a coordinated dump or a short filesystem freeze to assure logical consistency.
#!/usr/bin/env bash
set -euo pipefail
# Configuração específica do host
VG_NAME="vg0" # Volume Group
LV_NAME="root" # Logical Volume original
SNAP_SIZE="5G" # Tamanho do snapshot (depende da taxa de escrita)
MOUNT_POINT="/mnt/backup-snap"
SNAP_NAME="${LV_NAME}-snap-$(date +%Y%m%d%H%M%S)"
LOCK_FILE="/var/lock/restic-lvm-backup.lock"
exec 9>"$LOCK_FILE" || true
if ! flock -n 9; then
echo "Outro backup está em execução. Abortando." >&2
exit 1
fi
cleanup() {
set +e
if mountpoint -q "$MOUNT_POINT"; then umount "$MOUNT_POINT"; fi
if lvdisplay "/dev/${VG_NAME}/${SNAP_NAME}" >/dev/null 2>&1; then lvremove -f "/dev/${VG_NAME}/${SNAP_NAME}"; fi
}
trap cleanup EXIT
# Garante mountpoint
install -d -m 0755 "$MOUNT_POINT"
# Carrega variáveis do Restic
set -a
. /etc/restic.env
set +a
LV_PATH="/dev/${VG_NAME}/${LV_NAME}"
SNAP_PATH="/dev/${VG_NAME}/${SNAP_NAME}"
# (Opcional) Freeze curto do FS se souber o mount do LV original
# fsfreeze -f / && trap 'fsfreeze -u /; cleanup' EXIT # Exemplo perigoso; ajuste ao seu mount real
# Cria snapshot copy-on-write
lvcreate -s -L "$SNAP_SIZE" -n "$SNAP_NAME" "$LV_PATH"
# Descongela se tiver congelado
# fsfreeze -u /
# Monta snapshot como readonly
mount -o ro "$SNAP_PATH" "$MOUNT_POINT"
# Executa backup incremental com Restic
HOSTNAME_FQDN="$(hostname -f || hostname)"
ionice -c2 -n7 nice -n 10 \
restic backup \
--host "$HOSTNAME_FQDN" \
--tag "$BACKUP_TAGS" \
--exclude-file=/etc/restic/excludes.txt \
"$MOUNT_POINT"
# (Opcional) Verificação rápida de integridade do último snapshot
restic snapshots --last --compact
# Cleanup é feito no trap
Scheduling with systemd: service + timers (backup and retention)
Create systemd units to orchestrate backup and retention. The daily timer runs the backup; the weekly timer applies retention and prune. Use RandomizedDelaySec to avoid simultaneous spikes across hosts.
If you still use cron, see how to migrate in the Migrate cron to systemd timers guide.
# /usr/local/sbin/backup-lvm-restic.sh deve estar executável
chmod +x /usr/local/sbin/backup-lvm-restic.sh
# Unidade de backup
cat > /etc/systemd/system/backup-lvm-restic.service <<'EOF'
[Unit]
Description=Backup com LVM snapshot + Restic para S3
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic.env
ExecStart=/usr/local/sbin/backup-lvm-restic.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
UMask=0077
[Install]
WantedBy=multi-user.target
EOF
# Timer diário do backup
cat > /etc/systemd/system/backup-lvm-restic.timer <<'EOF'
[Unit]
Description=Agendamento diário do backup LVM+Restic
[Timer]
OnCalendar=02:30
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Serviço de retenção e prune semanal
cat > /etc/systemd/system/restic-prune.service <<'EOF'
[Unit]
Description=Restic forget+prune (retenção)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic.env
ExecStart=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
UMask=0077
EOF
# Timer semanal de retenção (domingo 03:30)
cat > /etc/systemd/system/restic-prune.timer <<'EOF'
[Unit]
Description=Agendamento semanal Restic prune
[Timer]
OnCalendar=Sun *-*-* 03:30:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Aplica e inicia timers
systemctl daemon-reload
systemctl enable --now backup-lvm-restic.timer
systemctl enable --now restic-prune.timer
# Primeira execução manual (opcional)
# systemctl start backup-lvm-restic.service
Verification, quick restore and best practices
Validate timer execution, inspect journal logs and confirm snapshots in the repository. Perform a sample restore to test the recovery path. Test before you need it.
If you centralize logs, integrate the journal with tools as described in the Fluentd + Elasticsearch guide linked below.
- Confirm timers are active and next run times.
- Check service logs and Restic output.
- List snapshots and tags.
- Restore a test file/directory to validate keys and access permissions.
- Automate periodic restore tests in an isolated environment.
# Timers e próximas execuções
systemctl list-timers | grep -E 'backup-lvm-restic|restic-prune'
# Logs da última execução do backup
journalctl -u backup-lvm-restic.service -n 200 --no-pager
# Lista snapshots
set -a; . /etc/restic.env; set +a
restic snapshots --compact
# Restauração de teste (ex.: arquivo do /etc)
mkdir -p /tmp/restore-test
restic restore latest --target /tmp/restore-test --include etc/hosts
ls -l /tmp/restore-test/etc/hosts
Common troubleshooting
Common errors involve insufficient snapshot space, invalid credentials, and repository locks. Below are symptoms and actions. Never force LVM removals on the wrong volume.
On systems with heavy writes, increase SNAP_SIZE or reduce the backup window. Confirm DNS/firewall reachability to the S3 endpoint.
- Snapshot fills/invalidates: increase SNAP_SIZE or reduce write activity during backup.
- lvcreate failed: verify correct VG/LV and free space in the VG (use vgs/lvs).
- S3 permission denied: validate IAM policy and variables in /etc/restic.env.
- Repository locked: use 'restic unlock' with caution, ensuring no processes are running.
- Long backup time: adjust excludes, use tags and schedule outside peak hours.
# Diagnóstico LVM
vgs; lvs -a -o +devices
# Verificação de mountpoint do snapshot
mount | grep backup-snap || lsblk -f
# Teste de credenciais/ambiente do Restic
set -a; . /etc/restic.env; set +a
restic snapshots || echo "Verifique RESTIC_REPOSITORY e variáveis AWS"
# Remover lock do repositório com cuidado
restic unlock
# Logs detalhados
journalctl -u backup-lvm-restic.service --since "-2h" -e --no-pager
Frequently asked questions
What snapshot size should I use for the LVM snapshot?
It depends on the write rate during the backup window. The more the volume changes, the larger the snapshot needs to be (copy‑on‑write area). Start with a few gigabytes and adjust after observing whether the snapshot fills. Monitor the first runs.
Can I use S3-compatible endpoints?
Yes, Restic works with S3-compatible APIs by specifying the endpoint in the repository URL. Check your provider's authentication, region and TLS requirements, and test with restic init and restic snapshots before automating.
Are database backups consistent using an LVM snapshot?
An LVM snapshot guarantees block-level consistency. For application-level consistency, perform a transactional dump or a short filesystem/service freeze before lvcreate and unfreeze after creating the snapshot. Assess the impact on your workload.
How do I retain backups without losing useful restores?
Use restic forget with a policy that matches your business needs, followed by prune. For example: keep 7 daily, 4 weekly and 6 monthly. Adjust to your RPO/RTO and always test restores after changing retention policies.
Is it safe to keep credentials in /etc/restic.env?
Yes, if the file has strict permissions (0600) and is accessible only by root. Consider running Restic under a less-privileged user when possible and rotate IAM keys regularly.
How much will S3 storage cost?
Costs vary with total GB stored, number of objects/versions, requests and bucket region. Incremental uploads and proper retention reduce costs. Check current provider pricing and adjust retention accordingly.
Need to adapt the backup to your environment?
If you want help reviewing retention policies, exclusions and run windows for your workload, get in touch and describe your scenario.
Discuss backup and automationSee also
- Tutorial: Implement Canary releases with Traefik in Docker Compose using headers and weighted routing for gradual PHP deployments
- Tutorial: Configure HAProxy with Let's Encrypt and health checks to load balance traffic between Docker containers on a VPS (step-by-step)
- Hands‑On Tutorial: Implement mTLS Between Nginx and Microservices with smallstep CA in Docker Compose
- Tutorial: configure cgroup v2 and systemd ResourceControl to limit CPU, memory and I/O for Docker containers on Debian