Set up automated ClickHouse backups with compression, S3 storage, and systemd timers. Includes monitoring, encryption, and recovery procedures for production environments.
Prerequisites
- ClickHouse server installed and running
- S3-compatible storage bucket
- Root or sudo access
What this solves
This tutorial sets up automated ClickHouse backups with compression and S3 integration. You'll configure systemd timers to run regular backups, compress data to reduce storage costs, and store backups securely in S3-compatible storage with monitoring and alerting.
Step-by-step configuration
Install backup dependencies
Install required packages for backup operations, compression, and S3 integration.
sudo apt update
sudo apt install -y awscli gzip lz4 pigz clickhouse-backup
Configure S3 credentials
Set up AWS credentials and S3 bucket access for backup storage. Replace with your actual S3 credentials and bucket details.
sudo mkdir -p /etc/clickhouse-backup
sudo aws configure set aws_access_key_id YOUR_ACCESS_KEY_ID
sudo aws configure set aws_secret_access_key YOUR_SECRET_ACCESS_KEY
sudo aws configure set default.region us-east-1
Create backup configuration
Configure clickhouse-backup with S3 settings, compression options, and retention policies.
general:
remote_storage: s3
max_file_size: 1073741824
disable_progress_bar: true
backups_to_keep_local: 3
backups_to_keep_remote: 30
log_level: info
clickhouse:
username: default
password: ""
host: localhost
port: 9000
data_path: /var/lib/clickhouse
skip_tables:
- system.*
- INFORMATION_SCHEMA.*
timeout: 5m
s3:
access_key: YOUR_ACCESS_KEY_ID
secret_key: YOUR_SECRET_ACCESS_KEY
bucket: your-clickhouse-backups
endpoint: s3.amazonaws.com
region: us-east-1
acl: private
force_path_style: false
path: clickhouse-backups/
disable_ssl: false
part_size: 104857600
storage_class: STANDARD_IA
compression:
format: lz4
level: 1
encryption:
type: AES256
key: ""
key_id: ""
Set secure file permissions
Protect the backup configuration file containing sensitive credentials.
sudo chown clickhouse:clickhouse /etc/clickhouse-backup/config.yml
sudo chmod 600 /etc/clickhouse-backup/config.yml
Create backup script
Create a comprehensive backup script that handles full and incremental backups with error handling and logging.
#!/bin/bash
ClickHouse Backup Script with S3 Integration
set -euo pipefail
Configuration
BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
LOG_FILE="/var/log/clickhouse-backup.log"
CONFIG_FILE="/etc/clickhouse-backup/config.yml"
RETENTION_DAYS=30
TIMEOUT=3600
Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
Error handling
error_exit() {
log "ERROR: $1"
exit 1
}
Check if ClickHouse is running
if ! systemctl is-active --quiet clickhouse-server; then
error_exit "ClickHouse server is not running"
fi
log "Starting ClickHouse backup: $BACKUP_NAME"
Create local backup
log "Creating local backup..."
timeout $TIMEOUT clickhouse-backup -c "$CONFIG_FILE" create "$BACKUP_NAME" || error_exit "Failed to create local backup"
Upload to S3
log "Uploading backup to S3..."
timeout $TIMEOUT clickhouse-backup -c "$CONFIG_FILE" upload "$BACKUP_NAME" || error_exit "Failed to upload backup to S3"
Verify backup integrity
log "Verifying backup integrity..."
clickhous-backup -c "$CONFIG_FILE" list remote | grep "$BACKUP_NAME" || error_exit "Backup verification failed"
Clean up old backups
log "Cleaning up old backups..."
clickhouse-backup -c "$CONFIG_FILE" delete local --age "${RETENTION_DAYS}d" || log "Warning: Failed to clean local backups"
clickhouse-backup -c "$CONFIG_FILE" delete remote --age "${RETENTION_DAYS}d" || log "Warning: Failed to clean remote backups"
Get backup size information
BACKUP_SIZE=$(clickhouse-backup -c "$CONFIG_FILE" list local | grep "$BACKUP_NAME" | awk '{print $3}' || echo "Unknown")
log "Backup completed successfully. Size: $BACKUP_SIZE"
Send metrics to monitoring (optional)
if command -v curl >/dev/null 2>&1 && [ -n "${WEBHOOK_URL:-}" ]; then
curl -X POST "$WEBHOOK_URL" -d "backup_completed=1&backup_name=$BACKUP_NAME&backup_size=$BACKUP_SIZE" || log "Warning: Failed to send metrics"
fi
log "Backup process finished successfully"
Make backup script executable
Set appropriate permissions for the backup script and create log directory.
sudo chmod +x /usr/local/bin/clickhouse-backup.sh
sudo mkdir -p /var/log
sudo touch /var/log/clickhouse-backup.log
sudo chown clickhouse:clickhouse /var/log/clickhouse-backup.log
sudo chmod 644 /var/log/clickhouse-backup.log
Create systemd service
Create a systemd service unit for running backups with proper isolation and resource limits.
[Unit]
Description=ClickHouse Backup Service
After=clickhouse-server.service
Requires=clickhouse-server.service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=clickhouse
Group=clickhouse
ExecStart=/usr/local/bin/clickhouse-backup.sh
TimeoutStartSec=7200
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
ReadWritePaths=/var/lib/clickhouse /var/log /tmp
Environment=HOME=/var/lib/clickhouse
WorkingDirectory=/var/lib/clickhouse
[Install]
WantedBy=multi-user.target
Create systemd timer
Configure systemd timer for automated daily backups with randomized execution to avoid peak hours.
[Unit]
Description=Run ClickHouse Backup Daily
Requires=clickhouse-backup.service
[Timer]
OnCalendar=daily
RandomizedDelaySec=3600
Persistent=true
AccuracySec=1h
[Install]
WantedBy=timers.target
Enable and start the backup timer
Enable the systemd timer to start automatic backups and check the schedule.
sudo systemctl daemon-reload
sudo systemctl enable clickhouse-backup.timer
sudo systemctl start clickhouse-backup.timer
sudo systemctl list-timers clickhouse-backup.timer
Create recovery script
Create a script for easy backup recovery and restoration procedures.
#!/bin/bash
ClickHouse Restore Script
set -euo pipefail
BACKUP_NAME="$1"
CONFIG_FILE="/etc/clickhouse-backup/config.yml"
LOG_FILE="/var/log/clickhouse-restore.log"
if [ $# -eq 0 ]; then
echo "Usage: $0 "
echo "Available backups:"
clickhouse-backup -c "$CONFIG_FILE" list remote
exit 1
fi
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log "Starting restore from backup: $BACKUP_NAME"
Download backup from S3
log "Downloading backup from S3..."
clickhouse-backup -c "$CONFIG_FILE" download "$BACKUP_NAME"
Stop ClickHouse for restoration
log "Stopping ClickHouse server..."
sudo systemctl stop clickhouse-server
Restore backup
log "Restoring backup..."
clickhouse-backup -c "$CONFIG_FILE" restore "$BACKUP_NAME"
Start ClickHouse
log "Starting ClickHouse server..."
sudo systemctl start clickhouse-server
Wait for server to be ready
log "Waiting for ClickHouse to be ready..."
for i in {1..30}; do
if clickhouse-client --query "SELECT 1" >/dev/null 2>&1; then
log "ClickHouse server is ready"
break
fi
sleep 5
done
log "Restore completed successfully"
Make recovery script executable
Set permissions for the recovery script and create its log file.
sudo chmod +x /usr/local/bin/clickhouse-restore.sh
sudo touch /var/log/clickhouse-restore.log
sudo chown clickhouse:clickhouse /var/log/clickhouse-restore.log
sudo chmod 644 /var/log/clickhouse-restore.log
Configure backup monitoring
Set up monitoring script to check backup status and send alerts for failures.
#!/bin/bash
ClickHouse Backup Monitoring Script
set -euo pipefail
CONFIG_FILE="/etc/clickhouse-backup/config.yml"
LOG_FILE="/var/log/clickhouse-backup.log"
ALERT_EMAIL="admin@example.com"
MAX_BACKUP_AGE_HOURS=25
Check if backup ran successfully in the last 25 hours
LAST_BACKUP=$(clickhouse-backup -c "$CONFIG_FILE" list local | head -n1 | awk '{print $1}' || echo "")
if [ -z "$LAST_BACKUP" ]; then
echo "ERROR: No backups found" | mail -s "ClickHouse Backup Alert: No backups found" "$ALERT_EMAIL"
exit 1
fi
Check backup age
BACKUP_TIME=$(echo "$LAST_BACKUP" | grep -o '[0-9]\{8\}-[0-9]\{6\}' || echo "")
if [ -n "$BACKUP_TIME" ]; then
BACKUP_TIMESTAMP=$(date -d "${BACKUP_TIME:0:8} ${BACKUP_TIME:9:2}:${BACKUP_TIME:11:2}:${BACKUP_TIME:13:2}" +%s)
CURRENT_TIMESTAMP=$(date +%s)
AGE_HOURS=$(( (CURRENT_TIMESTAMP - BACKUP_TIMESTAMP) / 3600 ))
if [ $AGE_HOURS -gt $MAX_BACKUP_AGE_HOURS ]; then
echo "ERROR: Last backup is $AGE_HOURS hours old (max allowed: $MAX_BACKUP_AGE_HOURS)" | \
mail -s "ClickHouse Backup Alert: Backup too old" "$ALERT_EMAIL"
exit 1
fi
fi
Check for recent errors in log
if tail -n 100 "$LOG_FILE" | grep -q "ERROR"; then
echo "Recent errors found in backup log:" > /tmp/backup_errors.txt
tail -n 100 "$LOG_FILE" | grep "ERROR" >> /tmp/backup_errors.txt
mail -s "ClickHouse Backup Alert: Errors detected" "$ALERT_EMAIL" < /tmp/backup_errors.txt
rm -f /tmp/backup_errors.txt
fi
echo "Backup monitoring check completed successfully"
Install mail utility and configure monitoring cron
Install mail utilities for alerting and set up monitoring checks.
sudo apt install -y mailutils postfix
sudo chmod +x /usr/local/bin/clickhouse-backup-monitor.sh
sudo systemctl enable --now postfix
Add monitoring to crontab (runs every 4 hours)
echo "0 /4 /usr/local/bin/clickhouse-backup-monitor.sh" | sudo crontab -u clickhouse -
Verify your setup
Test the backup system and verify all components are working correctly.
# Test backup configuration
sudo clickhouse-backup -c /etc/clickhouse-backup/config.yml list local
Run a manual backup test
sudo systemctl start clickhouse-backup.service
sudo systemctl status clickhouse-backup.service
Check backup logs
sudo tail -f /var/log/clickhouse-backup.log
Verify S3 upload
aws s3 ls s3://your-clickhouse-backups/clickhouse-backups/ --recursive
Check timer status
sudo systemctl status clickhouse-backup.timer
sudo systemctl list-timers clickhouse-backup.timer
Test monitoring script
sudo -u clickhouse /usr/local/bin/clickhouse-backup-monitor.sh
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Permission denied errors | Incorrect file ownership | sudo chown -R clickhouse:clickhouse /etc/clickhouse-backup /var/log/clickhouse-*.log |
| S3 upload fails | Invalid credentials or bucket policy | Test with aws s3 ls s3://your-bucket and check IAM permissions |
| Backup timeout | Large database or slow storage | Increase timeout in service file and backup script |
| ClickHouse connection fails | Server not running or wrong credentials | Check service status and verify connection settings in config |
| Timer not running | Timer not enabled | sudo systemctl enable clickhouse-backup.timer && sudo systemctl start clickhouse-backup.timer |
| Compression fails | Missing compression utilities | Install lz4 or gzip packages for your distribution |
Next steps
- Set up ClickHouse monitoring with Prometheus and Grafana for comprehensive observability
- Configure ClickHouse users and RBAC for production environments with authentication and access control
- Configure backup monitoring with Prometheus and Grafana for automated infrastructure oversight
- Implement ClickHouse disaster recovery with multi-region replication
- Set up ClickHouse backup encryption with GPG for enhanced security
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# ClickHouse Backup Automation Install Script
# Production-ready installation with S3 integration
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
CLICKHOUSE_BACKUP_VERSION="2.4.24"
CONFIG_DIR="/etc/clickhouse-backup"
LOG_DIR="/var/log/clickhouse-backup"
BACKUP_SCRIPT_PATH="/usr/local/bin/clickhouse-backup-script.sh"
SERVICE_NAME="clickhouse-backup"
# Usage
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -b BUCKET S3 bucket name (required)"
echo " -r REGION AWS region (default: us-east-1)"
echo " -h Show this help message"
exit 1
}
# Parse arguments
S3_BUCKET=""
AWS_REGION="us-east-1"
while getopts "b:r:h" opt; do
case $opt in
b) S3_BUCKET="$OPTARG" ;;
r) AWS_REGION="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
if [[ -z "$S3_BUCKET" ]]; then
echo -e "${RED}Error: S3 bucket name is required${NC}"
usage
fi
# Logging
log() {
echo -e "${GREEN}$(date '+%Y-%m-%d %H:%M:%S') - $1${NC}"
}
warn() {
echo -e "${YELLOW}$(date '+%Y-%m-%d %H:%M:%S') - WARNING: $1${NC}"
}
error() {
echo -e "${RED}$(date '+%Y-%m-%d %H:%M:%S') - ERROR: $1${NC}"
exit 1
}
# Cleanup function
cleanup() {
if [[ $? -ne 0 ]]; then
warn "Installation failed. Cleaning up..."
systemctl stop clickhouse-backup.timer 2>/dev/null || true
systemctl disable clickhouse-backup.timer 2>/dev/null || true
rm -f /etc/systemd/system/clickhouse-backup.* 2>/dev/null || true
systemctl daemon-reload
fi
}
trap cleanup ERR
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
fi
# Detect distro
if [[ ! -f /etc/os-release ]]; then
error "Cannot detect operating system"
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
CLICKHOUSE_USER="clickhouse"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
CLICKHOUSE_USER="clickhouse"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
CLICKHOUSE_USER="clickhouse"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
CLICKHOUSE_USER="clickhouse"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
log "[1/8] Updating package manager..."
$PKG_UPDATE
log "[2/8] Installing backup dependencies..."
case "$PKG_MGR" in
"apt")
$PKG_INSTALL awscli gzip lz4 pigz wget curl
;;
"dnf"|"yum")
$PKG_INSTALL awscli gzip lz4 pigz wget curl
;;
esac
log "[3/8] Installing clickhouse-backup..."
BACKUP_URL="https://github.com/Altinity/clickhouse-backup/releases/download/v${CLICKHOUSE_BACKUP_VERSION}/clickhouse-backup-linux-amd64.tar.gz"
cd /tmp
wget -q "$BACKUP_URL" -O clickhouse-backup.tar.gz
tar -xzf clickhouse-backup.tar.gz
chmod 755 clickhouse-backup/clickhouse-backup
mv clickhouse-backup/clickhouse-backup /usr/local/bin/
rm -rf clickhouse-backup clickhouse-backup.tar.gz
log "[4/8] Creating configuration directories..."
mkdir -p "$CONFIG_DIR" "$LOG_DIR"
chown root:root "$CONFIG_DIR"
chmod 755 "$CONFIG_DIR"
chown "$CLICKHOUSE_USER:$CLICKHOUSE_USER" "$LOG_DIR"
chmod 755 "$LOG_DIR"
log "[5/8] Creating backup configuration..."
cat > "$CONFIG_DIR/config.yml" << EOF
general:
remote_storage: s3
max_file_size: 1073741824
disable_progress_bar: true
backups_to_keep_local: 3
backups_to_keep_remote: 30
log_level: info
clickhouse:
username: default
password: ""
host: localhost
port: 9000
data_path: /var/lib/clickhouse
skip_tables:
- system.*
- INFORMATION_SCHEMA.*
timeout: 5m
s3:
bucket: ${S3_BUCKET}
endpoint: s3.amazonaws.com
region: ${AWS_REGION}
acl: private
force_path_style: false
path: clickhouse-backups/
disable_ssl: false
part_size: 104857600
storage_class: STANDARD_IA
compression:
format: lz4
level: 1
encryption:
type: AES256
EOF
chown "$CLICKHOUSE_USER:$CLICKHOUSE_USER" "$CONFIG_DIR/config.yml"
chmod 640 "$CONFIG_DIR/config.yml"
log "[6/8] Creating backup script..."
cat > "$BACKUP_SCRIPT_PATH" << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
BACKUP_NAME="backup-$(date +%Y%m%d-%H%M%S)"
LOG_FILE="/var/log/clickhouse-backup/clickhouse-backup.log"
CONFIG_FILE="/etc/clickhouse-backup/config.yml"
TIMEOUT=3600
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
error_exit() {
log "ERROR: $1"
exit 1
}
if ! systemctl is-active --quiet clickhouse-server; then
error_exit "ClickHouse server is not running"
fi
log "Starting ClickHouse backup: $BACKUP_NAME"
log "Creating local backup..."
timeout $TIMEOUT clickhouse-backup -c "$CONFIG_FILE" create "$BACKUP_NAME" || error_exit "Failed to create local backup"
log "Uploading backup to S3..."
timeout $TIMEOUT clickhouse-backup -c "$CONFIG_FILE" upload "$BACKUP_NAME" || error_exit "Failed to upload backup to S3"
log "Verifying backup integrity..."
clickhouse-backup -c "$CONFIG_FILE" list remote | grep "$BACKUP_NAME" || error_exit "Backup verification failed"
log "Cleaning up old backups..."
clickhouse-backup -c "$CONFIG_FILE" delete local --age 7d || log "Warning: Failed to clean local backups"
clickhouse-backup -c "$CONFIG_FILE" delete remote --age 30d || log "Warning: Failed to clean remote backups"
log "Backup completed successfully: $BACKUP_NAME"
EOF
chmod 755 "$BACKUP_SCRIPT_PATH"
log "[7/8] Creating systemd service and timer..."
cat > "/etc/systemd/system/clickhouse-backup.service" << EOF
[Unit]
Description=ClickHouse Backup Service
After=clickhouse-server.service
Requires=clickhouse-server.service
[Service]
Type=oneshot
User=$CLICKHOUSE_USER
Group=$CLICKHOUSE_USER
ExecStart=$BACKUP_SCRIPT_PATH
StandardOutput=journal
StandardError=journal
EOF
cat > "/etc/systemd/system/clickhouse-backup.timer" << EOF
[Unit]
Description=ClickHouse Backup Timer
Requires=clickhouse-backup.service
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable clickhouse-backup.timer
systemctl start clickhouse-backup.timer
log "[8/8] Running verification checks..."
# Check if clickhouse-backup is installed
if ! command -v clickhouse-backup &> /dev/null; then
error "clickhouse-backup command not found"
fi
# Check configuration file
if [[ ! -f "$CONFIG_DIR/config.yml" ]]; then
error "Configuration file not found"
fi
# Check backup script
if [[ ! -x "$BACKUP_SCRIPT_PATH" ]]; then
error "Backup script not executable"
fi
# Check systemd timer status
if ! systemctl is-enabled clickhouse-backup.timer &> /dev/null; then
error "Systemd timer not enabled"
fi
if ! systemctl is-active clickhouse-backup.timer &> /dev/null; then
error "Systemd timer not active"
fi
log "Installation completed successfully!"
log ""
log "Next steps:"
log "1. Configure AWS credentials: aws configure"
log "2. Ensure S3 bucket '$S3_BUCKET' exists and is accessible"
log "3. Test backup manually: sudo -u $CLICKHOUSE_USER $BACKUP_SCRIPT_PATH"
log "4. Check timer status: systemctl status clickhouse-backup.timer"
log ""
log "Configuration files:"
log "- Config: $CONFIG_DIR/config.yml"
log "- Script: $BACKUP_SCRIPT_PATH"
log "- Logs: $LOG_DIR/"
Review the script before running. Execute with: bash install.sh