Enterprise-grade, configuration-driven PostgreSQL backup and disaster recovery system
- 🔐 Zero Hardcoded Credentials - All configuration via files
- 📁 Flexible Configuration - Config file OR environment variables
- 🔒 AES-256 Encryption - GPG encrypted backups
- ☁️ Multi-Provider S3 - Backblaze, Tebi, Hetzner, AWS, Custom
- 🔔 Smart Notifications - Telegram, Email, or Both
- ✅ Automated Verification - Weekly restore testing
- 📊 Comprehensive Monitoring - Real-time health checks
- 📖 Complete DR Runbook - Step-by-step recovery procedures
- 🎯 Point-in-Time Recovery - Restore to any moment
- 🚀 One-Command Setup - Interactive configuration wizard
# 1. Configure (interactive wizard)
sudo pg-backup-configure
# 2. Install system
sudo pg-backup-install
# 3. Verify it works
sudo -u postgres pg-backup-testThat's it! Your database is now protected.
/etc/pg_backup/
├── backup.conf # Main configuration (safe to commit)
├── .secrets # Sensitive credentials (NEVER commit)
└── .env -> backup.conf # Symlink for environment loading
/usr/local/bin/pg_backup/
├── lib/
│ └── common.sh # Shared functions library
├── archive_wal.sh # WAL archiving script
├── restore_wal.sh # WAL restore script
├── base_backup.sh # Base backup script
├── backup_monitor.sh # Health monitoring script
└── backup_verify.sh # Automated verification script
/var/log/postgresql/
├── wal_archive.log # WAL archiving logs
├── wal_restore.log # WAL restore logs
├── base_backup.log # Base backup logs
├── backup_monitor.log # Monitoring logs
└── backup_verify.log # Verification logs
sudo pg-backup-configureAsks you questions and creates configuration automatically.
Copy and edit the template:
sudo cp /usr/share/doc/pg_backup/backup.conf.template /etc/pg_backup/backup.conf
sudo nano /etc/pg_backup/backup.confexport S3_PROVIDER="backblaze"
export S3_BUCKET="my-backups"
export AWS_PROFILE="backups"
export GPG_ENCRYPT="yes"
export GPG_RECIPIENT="[email protected]"
# ... etc
pg-backup-install --from-env# Main config (can contain non-sensitive data)
chmod 640 /etc/pg_backup/backup.conf
chown postgres:postgres /etc/pg_backup/backup.conf
# Secrets file (contains credentials)
chmod 600 /etc/pg_backup/.secrets
chown postgres:postgres /etc/pg_backup/.secretsAdd to .gitignore:
# PostgreSQL Backup System
/etc/pg_backup/.secrets
/etc/pg_backup/*.key
/etc/pg_backup/*.pem
*.private.asc# Export public key (safe to share)
sudo -u postgres gpg --export -a [email protected] > backup-public.asc
# Export private key (CRITICAL - keep offline)
sudo -u postgres gpg --export-secret-keys -a [email protected] > backup-private.asc
chmod 600 backup-private.asc
# Store private key in:
# - Hardware security module (HSM)
# - Encrypted USB drive
# - Password manager vault
# - Bank safety deposit box
# Then SECURELY DELETE from server
shred -u backup-private.ascOption A: Use AWS CLI Profile (Recommended)
sudo -u postgres aws configure --profile backups
# Credentials stored in /var/lib/postgresql/.aws/credentials
# File is readable only by postgres userOption B: Use IAM Role (Best for EC2/ECS)
# No credentials needed in config!
# Set S3_PROVIDER and S3_BUCKET only
AWS_PROFILE="" # Leave emptyOption C: Store in .secrets file
# /etc/pg_backup/.secrets
AWS_ACCESS_KEY_ID="your-key-id"
AWS_SECRET_ACCESS_KEY="your-secret-key"# Provider (auto-configures endpoint)
S3_PROVIDER="backblaze" # backblaze, tebi, hetzner, aws, custom
# Region (provider-specific)
S3_REGION="us-west-000"
# Bucket and paths
S3_BUCKET="my-postgres-backups"
S3_BASE_PREFIX="base-backups" # Base backup folder
S3_WAL_PREFIX="wal-archives" # WAL archive folder
# Custom endpoint (for S3_PROVIDER="custom")
S3_ENDPOINT="https://s3.example.com"# Enable/disable encryption
GPG_ENCRYPT="yes" # yes or no
# GPG recipient (email or key ID)
GPG_RECIPIENT="[email protected]"PGHOST="localhost"
PGPORT="5432"
PGUSER="postgres"
PGDATABASE="postgres"
PG_DATA_DIR="/var/lib/postgresql/14/main"
PG_VERSION="14"# Local WAL caching
KEEP_LOCAL_WAL_COPY="no" # yes or no
LOCAL_WAL_DIR="/var/lib/postgresql/wal_archive"
LOCAL_WAL_RETENTION_DAYS="7"
# Base backup retention
BASE_BACKUP_RETENTION_DAYS="30"
BASE_BACKUP_COMPRESS="yes"
BASE_BACKUP_PARALLEL_JOBS="4"
BASE_BACKUP_MAX_RATE_MB="0" # 0 = unlimited# Thresholds
MAX_WAL_LAG_SECONDS="300"
MAX_ARCHIVE_FAILURES="3"
MIN_DISK_SPACE_GB="50"
MAX_BASE_BACKUP_AGE_DAYS="7"
MAX_ARCHIVE_GAP_MINUTES="60"
# Alert settings
ALERT_COOLDOWN_SECONDS="3600" # Don't spam alerts# Enable notifications
ENABLE_NOTIFICATIONS="yes"
# Method
NOTIFICATION_METHOD="telegram" # telegram, email, both, none
# Telegram
TELEGRAM_BOT_TOKEN="123456:ABC..."
TELEGRAM_CHAT_ID="123456789"
# Email
EMAIL_TO="[email protected]"
EMAIL_FROM="[email protected]"
SMTP_SERVER="smtp.gmail.com:587"
SMTP_USER="[email protected]"
SMTP_PASSWORD="your-app-password"# Automated restore testing
ENABLE_BACKUP_VERIFICATION="yes"
VERIFY_TEST_PORT="5433"
VERIFY_TEST_DATA_DIR="/var/lib/postgresql/backup_verify_test"
VERIFY_MAX_DURATION_SECONDS="7200"
VERIFY_CLEANUP_ON_SUCCESS="yes"
VERIFY_CLEANUP_ON_FAILURE="no" # Keep for troubleshooting
# Tests to run
VERIFY_RUN_CHECKSUM_TESTS="yes"
VERIFY_RUN_QUERY_TESTS="yes"
VERIFY_RUN_CONSISTENCY_TESTS="yes"
# Tables to verify (space-separated)
VERIFY_SAMPLE_TABLES="users orders products"Most settings can be changed without restarting PostgreSQL:
# Edit configuration
sudo nano /etc/pg_backup/backup.conf
# Configuration is automatically reloaded on next operation
# No restart needed!Only these PostgreSQL settings require restart:
# In postgresql.conf
archive_mode = on
archive_command = '/usr/local/bin/pg_backup/archive_wal.sh %p %f'
After changing these:
sudo systemctl restart postgresql# Test S3 connection
pg-backup-test --s3
# Test GPG encryption
pg-backup-test --gpg
# Test notifications
pg-backup-test --notify
# Test WAL archiving
pg-backup-test --archive
# Test restore
pg-backup-test --restore
# Run all tests
pg-backup-test --all# 1. Force WAL switch
sudo -u postgres psql -c "SELECT pg_switch_wal();"
# 2. Check archiving status
sudo -u postgres psql -c "SELECT * FROM pg_stat_archiver;"
# 3. Verify file in S3
aws s3 ls s3://your-bucket/wal-archives/$(hostname)/ \
--profile backups \
--endpoint-url https://... \
| tail -5
# 4. Test restore
RECENT_WAL=$(aws s3 ls ... | tail -1 | awk '{print $4}')
sudo -u postgres /usr/local/bin/pg_backup/restore_wal.sh \
"${RECENT_WAL%.gpg}" \
"/tmp/test_restore"# Quick status check
pg-backup-status
# Detailed health report
pg-backup-health
# View recent logs
pg-backup-logs
# Check last verification
pg-backup-verify-statusDefault schedule (automatically configured):
# Every 15 minutes - Health monitoring
*/15 * * * * /usr/local/bin/pg_backup/backup_monitor.sh
# Sunday 2 AM - Full base backup
0 2 * * 0 /usr/local/bin/pg_backup/base_backup.sh
# Monday 3 AM - Verify last week's backup
0 3 * * 1 /usr/local/bin/pg_backup/backup_verify.shExport metrics to Prometheus/Grafana:
# Enable metrics export
echo "ENABLE_METRICS_EXPORT=yes" >> /etc/pg_backup/backup.conf
echo "METRICS_PORT=9187" >> /etc/pg_backup/backup.conf
# Restart metrics exporter
sudo systemctl restart pg-backup-metrics# 1. Stop PostgreSQL
sudo systemctl stop postgresql
# 2. Run recovery wizard (interactive)
sudo pg-backup-recover
# 3. Or use manual procedure
sudo pg-backup-recover --manualFull Recovery (Complete Data Loss):
pg-backup-recover --type=full --backup=latestPoint-in-Time Recovery:
pg-backup-recover --type=pitr --time='2024-01-15 14:30:00'Test Recovery (Non-Destructive):
pg-backup-recover --type=test --port=5433See full DR runbook: /usr/share/doc/pg_backup/disaster-recovery.md
# Check logs
sudo tail -f /var/log/postgresql/wal_archive.log
# Test manually
sudo -u postgres /usr/local/bin/pg_backup/archive_wal.sh \
/path/to/wal/file \
filename
# Check configuration
pg-backup-test --config# Verify credentials
sudo -u postgres aws s3 ls --profile backups
# Check endpoint
curl -I https://your-s3-endpoint.com
# Test with debug
DEBUG_MODE=yes pg-backup-test --s3# List available keys
sudo -u postgres gpg --list-keys
# Re-import public key
sudo -u postgres gpg --import /path/to/public-key.asc
# Verify key trust
sudo -u postgres gpg --edit-key [email protected]
gpg> trust
gpg> 5 (ultimate)
gpg> quit# Test Telegram
curl -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
-d "text=Test"
# Test Email
echo "Test" | mail -s "Test" [email protected]
# Check notification settings
pg-backup-test --notify# Backup old configuration
sudo cp -r /usr/local/bin/pg_backup /usr/local/bin/pg_backup.v1.backup
# Run migration tool
sudo pg-backup-migrate --from=v1
# Test new configuration
pg-backup-test --all# On old server - export configuration
pg-backup-export --output=/tmp/backup-config.tar.gz
# Copy to new server
scp /tmp/backup-config.tar.gz newserver:/tmp/
# On new server - import configuration
sudo pg-backup-import --input=/tmp/backup-config.tar.gz
# Verify and test
pg-backup-test --allCreate custom hooks:
# /etc/pg_backup/hooks/pre-backup.sh
#!/bin/bash
# Runs before each base backup
echo "Pre-backup hook executed"
# /etc/pg_backup/hooks/post-backup.sh
#!/bin/bash
# Runs after successful backup
echo "Backup completed at $(date)"Enable in config:
ENABLE_HOOKS="yes"
HOOK_DIR="/etc/pg_backup/hooks"# Create separate configs for each database
sudo pg-backup-configure --output=/etc/pg_backup/db1.conf
sudo pg-backup-configure --output=/etc/pg_backup/db2.conf
# Use specific config
PG_BACKUP_CONFIG=/etc/pg_backup/db1.conf pg-backup-test --all# Generate new GPG key
sudo -u postgres gpg --gen-key
# Re-encrypt existing backups (use with caution!)
pg-backup-reencrypt [email protected] [email protected]
# Update configuration
sudo nano /etc/pg_backup/backup.conf
# Change GPG_RECIPIENT to new keyFound a bug? Have a feature request?
- Check existing issues
- Create detailed bug report
- Submit pull request with tests
MIT License - See LICENSE file
- Documentation:
/usr/share/doc/pg_backup/ - Community: https://github.com/yourorg/pg-backup-system
- Professional Support: [email protected]
# Increase parallel streams
BASE_BACKUP_PARALLEL_JOBS="8"
# Enable WAL compression
# Add to postgresql.conf:
wal_compression = on
# Adjust checkpoint settings
checkpoint_timeout = 30min
max_wal_size = 5GB# Reduce resources
BASE_BACKUP_PARALLEL_JOBS="2"
archive_timeout = 300 # 5 minutes# Rate limit uploads (MB/s)
BASE_BACKUP_MAX_RATE_MB="50"
# Compress before upload
BASE_BACKUP_COMPRESS="yes"
# Use multipart upload for large files
AWS_CLI_FILE_SIZE_MB="100" # Automatic multipart above thisExposed at :9187/metrics:
pg_backup_last_archive_seconds
pg_backup_failed_archives_total
pg_backup_last_base_backup_seconds
pg_backup_archive_size_bytes
pg_backup_verification_status
Import dashboard: pg-backup-dashboard.json
groups:
- name: pg_backup
rules:
- alert: BackupArchiveFailing
expr: pg_backup_failed_archives_total > 3
for: 15m
- alert: BackupVerificationFailed
expr: pg_backup_verification_status == 0
for: 1hVersion: 2.0.0
Last Updated: 2024-01-15
Tested On: PostgreSQL 12, 13, 14, 15, 16