Set up automated encrypted backups using GPG 2.4 and rsync with systemd timers. This tutorial covers GPG key management, encrypted backup scripts, and restoration procedures for production environments.
Prerequisites
- Root or sudo access
- At least 2GB free disk space for backups
- Network access for remote backup sync
- Basic understanding of Linux file permissions
What this solves
Automated backups are essential, but storing sensitive data in plaintext creates security risks. This tutorial shows you how to implement GPG encryption with rsync for secure automated backups that protect your data both in transit and at rest. You'll create production-ready backup scripts with systemd timers that encrypt files before transfer and include verification procedures.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you get the latest versions of GPG and related tools.
sudo apt update && sudo apt upgrade -y
sudo apt install -y gnupg2 rsync coreutils
Verify GPG 2.4 installation
Check that GPG 2.4 or later is installed and working correctly.
gpg --version | head -1
gpg --list-keys
Generate GPG key pair for backups
Create a dedicated GPG key pair specifically for backup encryption. Use a strong passphrase and store it securely.
gpg --full-generate-key
Select the following options when prompted:
- Key type: (1) RSA and RSA (default)
- Key size: 4096
- Valid for: 2y (2 years)
- Real name: Backup System
- Email: backups@example.com
- Comment: Automated backup encryption
Export and backup the GPG keys
Export both public and private keys to secure locations. Store the private key backup in a safe location separate from your backup server.
gpg --list-secret-keys --keyid-format=long
export KEYID="your_key_id_here"
gpg --armor --export $KEYID > /root/backup-public-key.asc
gpg --armor --export-secret-keys $KEYID > /root/backup-private-key.asc
chmod 600 /root/backup-private-key.asc
Create backup directory structure
Set up the directory structure for backup scripts, logs, and temporary files with proper permissions.
sudo mkdir -p /opt/backup-system/{scripts,logs,temp}
sudo chmod 755 /opt/backup-system
sudo chmod 750 /opt/backup-system/{scripts,logs,temp}
sudo chown root:root /opt/backup-system -R
Create GPG encryption wrapper script
Create a wrapper script that handles GPG encryption with proper error handling and logging.
#!/bin/bash
GPG Backup Encryption Wrapper
Usage: gpg-backup-wrapper.sh
set -euo pipefail
Configuration
GPG_RECIPIENT="backups@example.com"
LOG_FILE="/opt/backup-system/logs/backup-$(date +%Y%m%d).log"
TEMP_DIR="/opt/backup-system/temp"
DATE=$(date +"%Y%m%d_%H%M%S")
Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
Check arguments
if [ $# -ne 3 ]; then
log "ERROR: Usage: $0 "
exit 1
fi
SOURCE_DIR="$1"
BACKUP_NAME="$2"
DESTINATION="$3"
ARCHIVE_NAME="${BACKUP_NAME}_${DATE}.tar.gz"
ENCRYPTED_NAME="${ARCHIVE_NAME}.gpg"
log "Starting backup: $BACKUP_NAME"
log "Source: $SOURCE_DIR"
log "Destination: $DESTINATION"
Verify source exists
if [ ! -d "$SOURCE_DIR" ]; then
log "ERROR: Source directory does not exist: $SOURCE_DIR"
exit 1
fi
Create destination if it doesn't exist
mkdir -p "$DESTINATION"
Create compressed archive
log "Creating compressed archive..."
cd "$(dirname "$SOURCE_DIR")"
tar czf "$TEMP_DIR/$ARCHIVE_NAME" "$(basename "$SOURCE_DIR")" 2>> "$LOG_FILE"
if [ $? -eq 0 ]; then
log "Archive created successfully: $ARCHIVE_NAME"
else
log "ERROR: Failed to create archive"
exit 1
fi
Encrypt the archive
log "Encrypting archive..."
gpg --trust-model always --cipher-algo AES256 --compress-algo 2 \
--recipient "$GPG_RECIPIENT" --encrypt \
"$TEMP_DIR/$ARCHIVE_NAME"
if [ $? -eq 0 ]; then
log "Archive encrypted successfully"
else
log "ERROR: Failed to encrypt archive"
rm -f "$TEMP_DIR/$ARCHIVE_NAME"
exit 1
fi
Move encrypted file to destination
log "Moving encrypted backup to destination..."
mv "$TEMP_DIR/${ARCHIVE_NAME}.gpg" "$DESTINATION/$ENCRYPTED_NAME"
Clean up temporary files
rm -f "$TEMP_DIR/$ARCHIVE_NAME"
Calculate and log file sizes
ORIGINAL_SIZE=$(du -sh "$SOURCE_DIR" | cut -f1)
ENCRYPTED_SIZE=$(du -sh "$DESTINATION/$ENCRYPTED_NAME" | cut -f1)
log "Backup completed successfully"
log "Original size: $ORIGINAL_SIZE, Encrypted size: $ENCRYPTED_SIZE"
log "Backup file: $DESTINATION/$ENCRYPTED_NAME"
exit 0
Make the wrapper script executable
Set proper permissions on the backup wrapper script.
sudo chmod 750 /opt/backup-system/scripts/gpg-backup-wrapper.sh
sudo chown root:root /opt/backup-system/scripts/gpg-backup-wrapper.sh
Create rsync encryption script
Create a script that combines encrypted local backups with secure rsync transfer to remote destinations.
#!/bin/bash
Encrypted Rsync Backup Script
Encrypts files locally, then syncs to remote destination
set -euo pipefail
Configuration
SOURCE_DIRS=(
"/etc"
"/home"
"/var/log"
"/opt/important-data"
)
LOCAL_BACKUP_DIR="/backup/encrypted"
REMOTE_USER="backup"
REMOTE_HOST="backup.example.com"
REMOTE_DIR="/backups/$(hostname)"
SSH_KEY="/root/.ssh/backup_rsa"
LOG_FILE="/opt/backup-system/logs/rsync-backup-$(date +%Y%m%d).log"
RETENTION_DAYS=30
Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting encrypted rsync backup process"
Create local backup directory
mkdir -p "$LOCAL_BACKUP_DIR"
Encrypt each source directory
for SOURCE in "${SOURCE_DIRS[@]}"; do
if [ -d "$SOURCE" ]; then
BACKUP_NAME=$(echo "$SOURCE" | sed 's/\//_/g' | sed 's/^_//')
log "Processing: $SOURCE -> $BACKUP_NAME"
/opt/backup-system/scripts/gpg-backup-wrapper.sh \
"$SOURCE" "$BACKUP_NAME" "$LOCAL_BACKUP_DIR"
if [ $? -ne 0 ]; then
log "ERROR: Failed to backup $SOURCE"
continue
fi
else
log "WARNING: Source directory does not exist: $SOURCE"
fi
done
Sync encrypted backups to remote server
log "Syncing encrypted backups to remote server..."
rsync -avz --delete-after --progress \
-e "ssh -i $SSH_KEY -o StrictHostKeyChecking=no" \
"$LOCAL_BACKUP_DIR/" \
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" \
2>> "$LOG_FILE"
if [ $? -eq 0 ]; then
log "Remote sync completed successfully"
else
log "ERROR: Remote sync failed"
exit 1
fi
Clean up old local backups
log "Cleaning up backups older than $RETENTION_DAYS days..."
find "$LOCAL_BACKUP_DIR" -name "*.gpg" -mtime +$RETENTION_DAYS -delete
log "Encrypted rsync backup process completed"
Generate backup report
BACKUP_COUNT=$(find "$LOCAL_BACKUP_DIR" -name "*.gpg" -mtime -1 | wc -l)
TOTAL_SIZE=$(du -sh "$LOCAL_BACKUP_DIR" | cut -f1)
log "Backup summary: $BACKUP_COUNT files created, total size: $TOTAL_SIZE"
Make rsync script executable
Set proper permissions on the rsync backup script.
sudo chmod 750 /opt/backup-system/scripts/encrypted-rsync-backup.sh
sudo chown root:root /opt/backup-system/scripts/encrypted-rsync-backup.sh
Create SSH key for backup authentication
Generate an SSH key pair for secure authentication to the remote backup server.
sudo ssh-keygen -t rsa -b 4096 -f /root/.ssh/backup_rsa -N ""
sudo chmod 600 /root/.ssh/backup_rsa
sudo chmod 644 /root/.ssh/backup_rsa.pub
Create systemd service and timer
Set up systemd service and timer files for automated backup execution.
[Unit]
Description=Encrypted Backup Service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=root
ExecStart=/opt/backup-system/scripts/encrypted-rsync-backup.sh
StandardOutput=journal
StandardError=journal
Create systemd timer
Configure the timer to run encrypted backups daily at 2 AM.
[Unit]
Description=Run encrypted backup daily
Requires=encrypted-backup.service
[Timer]
OnCalendar=daily
Persistent=true
AccuracySec=1min
[Install]
WantedBy=timers.target
Enable and start the backup timer
Enable the systemd timer to run automatically and start it immediately.
sudo systemctl daemon-reload
sudo systemctl enable encrypted-backup.timer
sudo systemctl start encrypted-backup.timer
sudo systemctl status encrypted-backup.timer
Create backup verification script
Create a script to verify that encrypted backups can be successfully decrypted and restored.
#!/bin/bash
Backup Verification Script
Tests decryption and extraction of encrypted backups
set -euo pipefail
Configuration
BACKUP_DIR="/backup/encrypted"
TEST_DIR="/tmp/backup-verification"
LOG_FILE="/opt/backup-system/logs/verification-$(date +%Y%m%d).log"
Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting backup verification process"
Create test directory
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"
Find most recent backup files
BACKUP_FILES=$(find "$BACKUP_DIR" -name "*.gpg" -mtime -1 -type f)
if [ -z "$BACKUP_FILES" ]; then
log "ERROR: No recent backup files found"
exit 1
fi
VERIFIED=0
FAILED=0
for BACKUP_FILE in $BACKUP_FILES; do
FILENAME=$(basename "$BACKUP_FILE")
log "Verifying: $FILENAME"
# Attempt decryption
if gpg --quiet --decrypt "$BACKUP_FILE" > "$TEST_DIR/test.tar.gz" 2>/dev/null; then
log " Decryption: SUCCESS"
# Attempt extraction test (first few files only)
if tar -tzf "$TEST_DIR/test.tar.gz" | head -10 > /dev/null 2>&1; then
log " Archive integrity: SUCCESS"
((VERIFIED++))
else
log " Archive integrity: FAILED"
((FAILED++))
fi
rm -f "$TEST_DIR/test.tar.gz"
else
log " Decryption: FAILED"
((FAILED++))
fi
done
Cleanup
rm -rf "$TEST_DIR"
log "Verification completed: $VERIFIED successful, $FAILED failed"
if [ $FAILED -gt 0 ]; then
exit 1
fi
exit 0
Make verification script executable
Set proper permissions on the verification script.
sudo chmod 750 /opt/backup-system/scripts/verify-backup.sh
sudo chown root:root /opt/backup-system/scripts/verify-backup.sh
Create backup restoration script
Create a script to simplify the process of restoring encrypted backups when needed.
#!/bin/bash
Backup Restoration Script
Usage: restore-backup.sh
set -euo pipefail
Check arguments
if [ $# -ne 2 ]; then
echo "Usage: $0 "
echo "Example: $0 /backup/encrypted/etc_20241201_020000.tar.gz.gpg /tmp/restore"
exit 1
fi
ENCRYPTED_FILE="$1"
DEST_DIR="$2"
TEMP_DIR="/tmp/backup-restore-$$"
echo "Starting restoration process..."
echo "Source: $ENCRYPTED_FILE"
echo "Destination: $DEST_DIR"
Verify source file exists
if [ ! -f "$ENCRYPTED_FILE" ]; then
echo "ERROR: Encrypted backup file not found: $ENCRYPTED_FILE"
exit 1
fi
Create temporary and destination directories
mkdir -p "$TEMP_DIR" "$DEST_DIR"
Decrypt the backup
echo "Decrypting backup..."
if ! gpg --quiet --decrypt "$ENCRYPTED_FILE" > "$TEMP_DIR/backup.tar.gz"; then
echo "ERROR: Failed to decrypt backup file"
rm -rf "$TEMP_DIR"
exit 1
fi
echo "Decryption successful"
Extract the archive
echo "Extracting archive..."
if tar -xzf "$TEMP_DIR/backup.tar.gz" -C "$DEST_DIR"; then
echo "Extraction successful"
else
echo "ERROR: Failed to extract archive"
rm -rf "$TEMP_DIR"
exit 1
fi
Cleanup
rm -rf "$TEMP_DIR"
echo "Backup restoration completed successfully"
echo "Restored files are available in: $DEST_DIR"
echo "Please verify the restored data before using it"
Make restoration script executable
Set proper permissions on the restoration script.
sudo chmod 750 /opt/backup-system/scripts/restore-backup.sh
sudo chown root:root /opt/backup-system/scripts/restore-backup.sh
Verify your setup
Test your encrypted backup system to ensure everything is working correctly.
# Check GPG key is available
gpg --list-keys "backups@example.com"
Test manual backup creation
sudo /opt/backup-system/scripts/gpg-backup-wrapper.sh /etc test-backup /tmp
Verify the encrypted file was created
ls -la /tmp/test-backup_*.tar.gz.gpg
Test backup verification
sudo /opt/backup-system/scripts/verify-backup.sh
Check systemd timer status
sudo systemctl list-timers encrypted-backup.timer
Check recent backup logs
sudo tail -20 /opt/backup-system/logs/backup-$(date +%Y%m%d).log
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| GPG encryption fails with "public key not found" | Wrong recipient email or key not imported | gpg --list-keys and verify recipient matches exactly |
| Permission denied on backup scripts | Incorrect file permissions | chmod 750 /opt/backup-system/scripts/*.sh |
| Rsync fails with SSH connection error | SSH key not configured or wrong permissions | Verify SSH key exists and has chmod 600 permissions |
| Systemd timer not running | Timer not enabled or service file error | systemctl enable encrypted-backup.timer && systemctl start encrypted-backup.timer |
| Backup verification fails | Corrupted backup or wrong GPG key | Check log files and verify GPG key matches encryption key |
| Insufficient disk space for backups | Large files or inadequate retention policy | Adjust retention days or implement compression levels |
Next steps
- Set up automated MySQL database backups with compression and rotation
- Configure Linux file encryption with LUKS and cryptsetup for data protection
- Configure backup monitoring with Prometheus and Grafana
- Implement backup rotation policies with automated cleanup
- Setup remote backup storage with S3-compatible encryption
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# GPG Backup System Installation Script
# Configures encrypted backups with GPG and rsync
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
BACKUP_USER=${1:-"backups@$(hostname -f)"}
KEY_EXPIRY=${2:-"2y"}
TOTAL_STEPS=8
print_usage() {
echo "Usage: $0 [backup_email] [key_expiry]"
echo " backup_email: Email for GPG key (default: backups@hostname)"
echo " key_expiry: Key expiration (default: 2y)"
exit 1
}
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
cleanup() {
if [ $? -ne 0 ]; then
error "Installation failed. Cleaning up..."
rm -rf /opt/backup-system 2>/dev/null || true
fi
}
trap cleanup ERR
# Check if running as root
if [ "$EUID" -ne 0 ]; then
error "This script must be run as root"
exit 1
fi
# Detect distribution
if [ ! -f /etc/os-release ]; then
error "/etc/os-release not found. Cannot detect distribution."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update -qq"
PKG_INSTALL="apt install -y"
PKG_UPGRADE="apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf upgrade -y"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PKG_UPGRADE="dnf upgrade -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PKG_UPGRADE="yum upgrade -y"
;;
*)
error "Unsupported distribution: $ID"
exit 1
;;
esac
log "[$1/$TOTAL_STEPS] Updating system packages..."
$PKG_UPDATE
$PKG_UPGRADE
log "[$2/$TOTAL_STEPS] Installing required packages..."
$PKG_INSTALL gnupg2 rsync coreutils tar gzip
log "[$3/$TOTAL_STEPS] Verifying GPG installation..."
GPG_VERSION=$(gpg --version | head -1)
log "Installed: $GPG_VERSION"
log "[$4/$TOTAL_STEPS] Creating backup directory structure..."
mkdir -p /opt/backup-system/{scripts,logs,temp,keys}
chmod 755 /opt/backup-system
chmod 750 /opt/backup-system/{scripts,logs,temp,keys}
chown root:root /opt/backup-system -R
log "[$5/$TOTAL_STEPS] Creating GPG encryption wrapper script..."
cat > /opt/backup-system/scripts/gpg-backup-wrapper.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Configuration
GPG_RECIPIENT="backups@example.com"
LOG_FILE="/opt/backup-system/logs/backup-$(date +%Y%m%d).log"
TEMP_DIR="/opt/backup-system/temp"
DATE=$(date +"%Y%m%d_%H%M%S")
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
if [ $# -ne 3 ]; then
log "ERROR: Usage: $0 <source_dir> <backup_name> <destination>"
exit 1
fi
SOURCE_DIR="$1"
BACKUP_NAME="$2"
DESTINATION="$3"
ARCHIVE_NAME="${BACKUP_NAME}_${DATE}.tar.gz"
ENCRYPTED_NAME="${ARCHIVE_NAME}.gpg"
log "Starting backup: $BACKUP_NAME"
if [ ! -d "$SOURCE_DIR" ]; then
log "ERROR: Source directory does not exist: $SOURCE_DIR"
exit 1
fi
mkdir -p "$DESTINATION"
log "Creating compressed archive..."
cd "$(dirname "$SOURCE_DIR")"
tar czf "$TEMP_DIR/$ARCHIVE_NAME" "$(basename "$SOURCE_DIR")" 2>> "$LOG_FILE"
log "Encrypting archive..."
gpg --trust-model always --cipher-algo AES256 --compress-algo 2 \
--recipient "$GPG_RECIPIENT" --encrypt \
"$TEMP_DIR/$ARCHIVE_NAME"
log "Moving encrypted backup to destination..."
mv "$TEMP_DIR/${ARCHIVE_NAME}.gpg" "$DESTINATION/$ENCRYPTED_NAME"
rm -f "$TEMP_DIR/$ARCHIVE_NAME"
log "Backup completed: $ENCRYPTED_NAME"
# Verification
gpg --list-packets "$DESTINATION/$ENCRYPTED_NAME" > /dev/null 2>&1
log "Backup verification successful"
EOF
chmod 750 /opt/backup-system/scripts/gpg-backup-wrapper.sh
log "[$6/$TOTAL_STEPS] Creating backup restore script..."
cat > /opt/backup-system/scripts/restore-backup.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [ $# -ne 2 ]; then
echo "Usage: $0 <encrypted_backup_file> <restore_destination>"
exit 1
fi
ENCRYPTED_FILE="$1"
RESTORE_DEST="$2"
TEMP_DIR="/opt/backup-system/temp"
if [ ! -f "$ENCRYPTED_FILE" ]; then
echo "ERROR: Encrypted file not found: $ENCRYPTED_FILE"
exit 1
fi
mkdir -p "$RESTORE_DEST" "$TEMP_DIR"
echo "Decrypting backup..."
gpg --output "$TEMP_DIR/decrypted.tar.gz" --decrypt "$ENCRYPTED_FILE"
echo "Extracting to $RESTORE_DEST..."
cd "$RESTORE_DEST"
tar xzf "$TEMP_DIR/decrypted.tar.gz"
rm -f "$TEMP_DIR/decrypted.tar.gz"
echo "Restore completed successfully"
EOF
chmod 750 /opt/backup-system/scripts/restore-backup.sh
log "[$7/$TOTAL_STEPS] Creating systemd timer for automated backups..."
cat > /etc/systemd/system/backup-system.service << EOF
[Unit]
Description=Encrypted Backup Service
After=network.target
[Service]
Type=oneshot
User=root
Group=root
ExecStart=/opt/backup-system/scripts/gpg-backup-wrapper.sh /etc system-config /opt/backup-system/backups
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/backup-system.timer << 'EOF'
[Unit]
Description=Run encrypted backups daily
Requires=backup-system.service
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable backup-system.timer
log "[$8/$TOTAL_STEPS] Performing final verification..."
# Update GPG recipient in wrapper script
sed -i "s/backups@example.com/$BACKUP_USER/g" /opt/backup-system/scripts/gpg-backup-wrapper.sh
# Test GPG functionality
gpg --list-keys > /dev/null 2>&1 || warn "GPG keyring is empty. Generate keys with: gpg --full-generate-key"
# Verify permissions
find /opt/backup-system -type d -exec test -r {} \; -exec test -x {} \; || error "Directory permissions incorrect"
find /opt/backup-system/scripts -name "*.sh" -exec test -x {} \; || error "Script permissions incorrect"
# Check systemd files
systemctl is-enabled backup-system.timer > /dev/null || warn "Backup timer not enabled"
log "Installation completed successfully!"
echo
echo -e "${GREEN}Next steps:${NC}"
echo "1. Generate GPG key: gpg --full-generate-key"
echo "2. Export keys: gpg --list-secret-keys --keyid-format=long"
echo "3. Update recipient in /opt/backup-system/scripts/gpg-backup-wrapper.sh"
echo "4. Test backup: /opt/backup-system/scripts/gpg-backup-wrapper.sh /etc test /tmp"
echo "5. Start timer: systemctl start backup-system.timer"
echo
echo -e "${YELLOW}Important:${NC} Store your private GPG key backup securely!"
Review the script before running. Execute with: bash install.sh