A practical SSL certificate deployment tool for automating the secure distribution of certificates across multiple hosts. Supports multiple certificate formats, service management, and comprehensive logging.
- Multi-format certificate generation: PKCS#12, PEM, DER, JKS, and custom formats
- Intelligent service management: Automatic reload/restart with fallback handling
- Security-focused: Configurable permissions, SSH key authentication, audit logging
- Dual implementation: Identical functionality in both Bash and Python
- Reliable operations: Idempotent operations, comprehensive error handling, dry-run validation
- Quick Start
- Installation
- Configuration
- Usage
- Security
- Certificate Formats
- Monitoring & Troubleshooting
- Testing
- Contributing
- Linux/Unix environment with SSH access to target hosts
- Bash 4.0+ or Python 3.9+
- Valid SSL certificates (Let's Encrypt recommended)
- Standard tools:
rsync,ssh,openssl
pip install requests# 1. Clone and configure
git clone <repository-url>
cd cert-spreader
cp config.example.conf config.conf
# 2. Edit configuration (see Configuration section)
nano config.conf
# 3. Set up SSH keys
ssh-keygen -t ed25519 -f ~/.ssh/cert_spreader_key
ssh-copy-id -i ~/.ssh/cert_spreader_key user@target-host
# 4. Validate configuration
./cert-spreader.sh --dry-run # or ./cert-spreader.py --dry-run
# 5. Deploy certificates
./cert-spreader.sh # or ./cert-spreader.py# Create dedicated system user (recommended)
sudo useradd -r -s /bin/bash -d /opt/cert-spreader cert-spreader
# Install to system location
sudo mkdir -p /opt/cert-spreader
sudo cp cert-spreader.{sh,py} config.example.conf /opt/cert-spreader/
sudo chown -R cert-spreader:cert-spreader /opt/cert-spreader
sudo chmod +x /opt/cert-spreader/cert-spreader.{sh,py}
# Configure logging
sudo mkdir -p /var/log/cert-spreader
sudo chown cert-spreader:cert-spreader /var/log/cert-spreader- No additional dependencies required
- Uses standard Unix tools:
rsync,ssh,openssl,curl,sha256sum
# Required Python packages
pip install requests
# System packages (Ubuntu/Debian)
sudo apt update && sudo apt install python3-requests
# System packages (RHEL/CentOS/Fedora)
sudo dnf install python3-requests# Add post-renewal hook
# Add to Let's Encrypt renewal configuration if using certbot
echo 'post_hook = /opt/cert-spreader/cert-spreader.sh' >> /etc/letsencrypt/renewal/yourdomain.com.conf
# Or run manually after renewal
certbot renew && /opt/cert-spreader/cert-spreader.shEdit config.conf with your environment details:
# Basic settings
DOMAIN="yourdomain.com"
CERT_DIR="/opt/ssl-certs/yourdomain.com"
LOG_FILE="/var/log/cert-spreader/cert-spreader.log"
# SSH configuration
SSH_OPTS="-o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new"
# Target hosts
HOSTS="web-01 web-02 app-01 db-01 legacy-host alpine-host"
# Default service manager for remote hosts
SERVICE_MANAGER="systemctl" # Global default (systemctl, service, rc-service, etc.)
# Service configuration per host
# Legacy format: "hostname:port:service1,service2,service3" (uses SERVICE_MANAGER default)
# Enhanced format: "hostname:port:service_manager:service1,service2,service3"
HOST_SERVICES=(
# Modern systemd hosts (use global SERVICE_MANAGER default)
"web-01:22:nginx,apache2"
"web-02:22:nginx"
"app-01:2222:myapp,redis"
"db-01:22:mysql"
# Mixed environments with explicit service managers
"legacy-host:22:service:nginx,apache2" # SysV init (older RHEL/CentOS)
"alpine-host:22:rc-service:nginx,php-fpm82" # OpenRC (Alpine Linux)
)CUSTOM_CERTIFICATES=(
# Windows/IIS certificates
"pkcs12:SecurePassword123:windows-iis.pfx"
# Web server certificates with DH parameters
"concatenated:/etc/ssl/dhparam.pem:nginx-combined.pem"
# Java application certificates
"jks:KeystorePassword:tomcat-app.jks"
# Mobile/embedded certificates
"der::mobile-app.der"
)# File permissions and ownership
FILE_PERMISSIONS=644 # Certificate files
PRIVKEY_PERMISSIONS=600 # Private keys (more restrictive)
DIRECTORY_PERMISSIONS=755 # Directories
FILE_OWNER=root # File owner
FILE_GROUP=ssl-cert # File group# Configure local service management (replaces hardcoded nginx)
LOCAL_SERVICE="nginx" # Service to reload/restart (empty = skip)
LOCAL_SERVICE_MANAGER="systemctl" # Service manager (systemctl, service, rc-service, etc.)
# Examples for different service managers:
# LOCAL_SERVICE="apache2" LOCAL_SERVICE_MANAGER="systemctl" # Ubuntu/Debian systemd
# LOCAL_SERVICE="httpd" LOCAL_SERVICE_MANAGER="service" # RHEL/CentOS SysV init
# LOCAL_SERVICE="nginx" LOCAL_SERVICE_MANAGER="rc-service" # Alpine/OpenRC
# LOCAL_SERVICE="" # Disable local service management# Global default service manager for remote hosts
SERVICE_MANAGER="systemctl" # Default for all remote hosts
# Enhanced HOST_SERVICES format supporting mixed environments:
HOST_SERVICES=(
# Legacy format (uses SERVICE_MANAGER default)
"web-server:22:nginx,apache2"
"database:22:mysql,redis"
# Enhanced format with explicit service managers
"legacy-rhel6:22:service:httpd,postfix" # SysV init
"alpine-server:22:rc-service:nginx,php-fpm82" # OpenRC
"freebsd-host:22:service:nginx,mysql-server" # BSD service
"custom-host:22:/usr/local/bin/svc:myapp" # Custom service manager
)
# Service manager compatibility matrix:
# systemctl - systemd (Ubuntu 16+, RHEL 7+, SUSE 12+)
# service - SysV init (older RHEL/CentOS, Debian, FreeBSD)
# rc-service - OpenRC (Alpine, Gentoo)
# custom path - Custom service management scriptsPROXMOX_USER="automation@pve!cert-deployer"
PROXMOX_TOKEN="your-api-token-here"
PROXMOX_NODES=("proxmox-01" "proxmox-02")# Validate configuration before deployment
./cert-spreader.sh --dry-run
./cert-spreader.py --dry-run
# Test SSH connectivity
ssh -i ~/.ssh/cert_spreader_key user@target-host 'echo "Connection OK"'Both implementations provide identical command-line interfaces:
# Full deployment (certificates + services + Proxmox)
./cert-spreader.sh
./cert-spreader.py
# Deployment modes
./cert-spreader.sh --cert-only # Deploy certificates only
./cert-spreader.sh --services-only # Restart services only
./cert-spreader.sh --proxmox-only # Update Proxmox only
./cert-spreader.sh --permissions-fix # Fix permissions only
# Validation and debugging
./cert-spreader.sh --dry-run # Preview actions without changes
./cert-spreader.sh --help # Display usage information
# Custom configuration
./cert-spreader.sh /path/to/custom.conf --dry-run- Validate:
./cert-spreader.sh --dry-run - Deploy:
./cert-spreader.sh - Monitor: Check logs at
/var/log/cert-spreader/cert-spreader.log - Verify: Test services and certificate validity
# Fix certificate permissions across all hosts
./cert-spreader.sh --permissions-fix
# Restart services after manual certificate updates
./cert-spreader.sh --services-only
# Update only Proxmox certificates
./cert-spreader.sh --proxmox-only# Add to crontab for automated renewals
0 3 * * 1 /opt/cert-spreader/cert-spreader.sh >> /var/log/cert-spreader/cron.log 2>&1# Create systemd service and timer files
sudo systemctl enable cert-spreader.timer
sudo systemctl start cert-spreader.timer# Generate dedicated SSH keys
ssh-keygen -t ed25519 -f ~/.ssh/cert_spreader_key
# Secure key permissions
chmod 600 ~/.ssh/cert_spreader_key
chown root:root ~/.ssh/cert_spreader_key
# Deploy keys to target hosts
for host in web-01 web-02 app-01; do
ssh-copy-id -i ~/.ssh/cert_spreader_key user@${host}.yourdomain.com
done# Secure configuration files
chmod 600 config.conf
chown root:root config.conf
# Use environment variables for sensitive data
export PROXMOX_TOKEN="your-token-here"
./cert-spreader.sh- Certificate directories:
755(drwxr-xr-x) - Private keys:
600(-rw-------) - Certificate files:
644(-rw-r--r--) - Custom ownership and group assignment supported
- Never commit
config.confto version control - Use dedicated service accounts for deployment
- Implement proper SSH key rotation
- Monitor certificate deployment logs
- Validate certificate chains and expiration dates
| Format | Extension | Primary Use Cases | Platform Support |
|---|---|---|---|
| PKCS#12 | .pfx, .p12 |
Windows IIS, Exchange, client certs | Windows, Cross-platform |
| Concatenated | .pem |
Nginx, Apache, HAProxy, ZNC | Linux, Unix |
| DER | .der, .crt |
Java applications, Android, embedded | Java, Mobile, IoT |
| PKCS#7 | .p7b, .p7c |
Windows cert stores, Java trust chains | Windows, Java |
| JKS | .jks |
Java applications, Tomcat, Kafka | Java ecosystem |
| PEM | .pem |
Custom applications, OpenSSL | Linux, Unix |
| CRT | .crt |
Individual certificates, web servers | Cross-platform |
CUSTOM_CERTIFICATES=(
# Load balancer with DH parameters
"concatenated:/etc/ssl/dhparam.pem:haproxy-frontend.pem"
# Application servers
"pkcs12:AppServerPassword:app-server.pfx"
# Java middleware
"jks:MiddlewareKeystore:tomcat-cluster.jks"
)CUSTOM_CERTIFICATES=(
# Windows infrastructure
"pkcs12:WindowsPassword:exchange-server.pfx"
"pkcs12:IISPassword:web-server.pfx"
# Linux web services
"concatenated:/etc/nginx/ssl/dhparam.pem:nginx-production.pem"
"concatenated::apache-staging.pem"
# Mobile applications
"der::android-app.der"
"der::ios-app.crt"
# Java applications
"jks:ProductionKeystore:spring-boot-app.jks"
)- Main log:
/var/log/cert-spreader/cert-spreader.log - Cron log:
/var/log/cert-spreader/cron.log - System log:
journalctl -u cert-spreader
# Monitor real-time deployment
tail -f /var/log/cert-spreader/cert-spreader.log
# Check recent deployments
grep "Certificate deployment" /var/log/cert-spreader/cert-spreader.log | tail -10
# Analyze errors
grep -i error /var/log/cert-spreader/cert-spreader.log# Test SSH connectivity
ssh -i ~/.ssh/cert_spreader_key user@target-host 'echo "Connection test"'
# Verify SSH agent
ssh-add -l
# Check DNS resolution
nslookup target-host.yourdomain.com# Verify certificate validity and chain
openssl x509 -in /opt/ssl-certs/domain/cert.pem -text -noout | grep -A2 Validity
openssl verify -CApath /etc/ssl/certs /opt/ssl-certs/domain/fullchain.pem
# Check certificate hash for change detection
sha256sum /opt/ssl-certs/domain/fullchain.pem | head -c 64# Test service reload manually
ssh target-host 'systemctl reload nginx || systemctl restart nginx'
# Check service status
ssh target-host 'systemctl status nginx'
# Verify certificate loading
ssh target-host 'openssl s_client -connect localhost:443 -servername yourdomain.com'# Fix permissions manually
./cert-spreader.sh --permissions-fix --dry-run
./cert-spreader.sh --permissions-fix
# Verify ownership and permissions
ls -la /opt/ssl-certs/domain/# Comprehensive pre-flight check
./cert-spreader.sh --dry-run 2>&1 | tee pre-deployment-check.log
# Validate configuration syntax
bash -n cert-spreader.sh
python3 -m py_compile cert-spreader.py# Verify certificate deployment
for host in $(echo $HOSTS); do
ssh ${host}.${DOMAIN} "ls -la /opt/ssl-certs/${DOMAIN}/"
done
# Test HTTPS connectivity
for host in $(echo $HOSTS); do
curl -I https://${host}.${DOMAIN}
doneComprehensive test suites are provided for both implementations. See TESTING.md for detailed information.
# Run all tests
./test-cert-spreader.sh
./test-cert-spreader.py
# Run with verbose output
./test-cert-spreader.py -v
# Test specific functionality
python3 -m unittest test-cert-spreader.TestCustomCertificates -v| Criteria | Bash Implementation | Python Implementation |
|---|---|---|
| Dependencies | Standard Unix tools only | Python 3.9+ + requests |
| Performance | Excellent (native shell) | Very good |
| Error Handling | Good with proper exit codes | Excellent with exceptions |
| Maintainability | Good for shell expertise | Excellent for development teams |
| Debugging | Shell debugging tools | Rich debugging and logging |
| Portability | Unix/Linux only | Cross-platform |
| Extensibility | Moderate | High |
- Bash: Choose for minimal dependencies, maximum performance, pure Unix environments
- Python: Choose for better error handling, easier maintenance, team development
- Maintain dual compatibility: Changes must work in both Bash and Python implementations
- Security first: Follow security best practices, never expose secrets
- Test thoroughly: All changes must include tests and pass existing test suites
- Document changes: Update README.md, TESTING.md, and inline documentation
# Before submitting changes
./test-cert-spreader.sh
./test-cert-spreader.py
# Validate with real configuration
./cert-spreader.sh --dry-run
./cert-spreader.py --dry-run- Bash: Follow shell scripting best practices, use
set -euo pipefail - Python: Follow PEP 8, use type hints, maintain Python 3.9+ compatibility
- Documentation: Clear comments, comprehensive docstrings, updated examples
Licensed under the Apache License, Version 2.0.
See LICENSE for full terms.
Attribution Requirement: If you publicly use or modify this project, credit the original author in your documentation.
- Issues: Report bugs and feature requests via GitHub Issues
- Documentation: See TESTING.md for testing details
- Security: Never commit real credentials; use
.gitignoreand example files - Best Practices: Always run
--dry-runbefore production deployments
Practical SSL certificate management for distributed infrastructure.