SSL/TLS Management
SkillCloud & infraManage SSL/TLS certificates with Let's Encrypt and internal PKI. Configure secure HTTPS, certificate renewal, and cipher suites. Use when implementing secure communications.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the SSL/TLS Management skill
What this skill tells your AI
The instructions your AI receives, as published by bagelhole/devops-security-agent-skills in security/network/ssl-tls-management/SKILL.md and read by ahel’s review.
Manage certificates and secure communications across web servers, Kubernetes clusters, and internal services.
When to Use This Skill
Use this skill when:
- Setting up HTTPS for a new web application
- Automating certificate renewal with Let's Encrypt
- Deploying cert-manager in Kubernetes
- Configuring TLS for internal service-to-service communication
- Auditing cipher suites and TLS versions for compliance
- Responding to an expiring or compromised certificate
Prerequisites
- Domain name with DNS control for public certificates
- Root/sudo access on web servers
certbotinstalled for Let's EncryptopensslCLI available (installed by default on most Linux distros)- Kubernetes cluster with Helm for cert-manager deployment
- Understanding of X.509 certificate chain of trust
Let's Encrypt with Certbot
Installation and Certificate Issuance
# Install certbot (Ubuntu/Debian)
apt update && apt install -y certbot python3-certbot-nginx
# Obtain certificate for nginx (interactive)
certbot --nginx -d example.com -d www.example.com
# Non-interactive mode for automation
certbot certonly --nginx \
-d example.com \
-d www.example.com \
--non-interactive \
--agree-tos \
--email admin@example.com
# Standalone mode (when no web server is running)
certbot certonly --standalone \
-d example.com \
--preferred-challenges http
# DNS challenge (for wildcard certs)
certbot certonly --manual \
--preferred-challenges dns \
-d "*.example.com" \
-d example.com
# Using DNS plugin for automation (Cloudflare example)
pip install certbot-dns-cloudflare
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.example.com" \
-d example.com
Renewal Automation
# Test renewal
certbot renew --dry-run
# Systemd timer (preferred over cron)
cat > /etc/systemd/system/certbot-renewal.service << 'EOF'
[Unit]
Description=Certbot certificate renewal
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
EOF
cat > /etc/systemd/system/certbot-renewal.timer << 'EOF'
[Unit]
Description=Run certbot renewal twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl enable --now certbot-renewal.timer
# Verify timer is active
systemctl list-timers certbot-renewal.timer
# Renewal hooks for post-renewal actions
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh << 'HOOK'
#!/bin/bash
systemctl reload nginx
# Also reload other services using the cert
systemctl reload haproxy 2>/dev/null || true
HOOK
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh
cert-manager for Kubernetes
Installation
# Install with Helm
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.0 \
--set installCRDs=true \
--set prometheus.enabled=true
# Verify installation
kubectl get pods -n cert-manager
kubectl get crds | grep cert-manager
ClusterIssuer Configurations
# letsencrypt-staging (use for testing first)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx
---
# letsencrypt-prod
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
---
# DNS challenge solver (for wildcard certs with Cloudflare)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-dns
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
---
# Self-signed CA issuer for internal services
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-ca
spec:
selfSigned: {}
Certificate Resources
# Public-facing certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-cert
namespace: default
spec:
secretName: example-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
duration: 2160h # 90 days
renewBefore: 720h # 30 days before expiry
---
# Wildcard certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-cert
namespace: default
spec:
secretName: wildcard-tls
issuerRef:
name: letsencrypt-prod-dns
kind: ClusterIssuer
dnsNames:
- "*.example.com"
- example.com
---
# Ingress with automatic TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
OpenSSL Commands Reference
# Generate a private key
openssl genrsa -out server.key 4096
# Generate an ECDSA key (preferred for performance)
openssl ecparam -genkey -name prime256v1 -out server-ec.key
# Generate a CSR (Certificate Signing Request)
openssl req -new -key server.key -out server.csr \
-subj "/C=US/ST=California/L=San Francisco/O=Acme Corp/CN=example.com"
# Generate CSR with SAN (Subject Alternative Names)
openssl req -new -key server.key -out server.csr -config <(cat <<EOF
[req]
default_bits = 4096
distinguished_name = dn
req_extensions = san
prompt = no
[dn]
CN = example.com
O = Acme Corp
C = US
[san]
subjectAltName = DNS:example.com,DNS:www.example.com,DNS:api.example.com
EOF
)
# Generate self-signed certificate (development/testing)
openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
-keyout selfsigned.key -out selfsigned.crt \
-subj "/CN=localhost"
# View certificate details
openssl x509 -in cert.pem -noout -text
# Check certificate expiration date
openssl x509 -in cert.pem -noout -dates
# Check remote certificate
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates -subject -issuer
# Verify certificate chain
openssl verify -CAfile ca-bundle.crt server.crt
# Check certificate chain from remote server
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
openssl x509 -noout -text
# Convert PEM to PKCS12
openssl pkcs12 -export -out cert.pfx -inkey server.key -in server.crt -certfile ca.crt
# Convert PKCS12 to PEM
openssl pkcs12 -in cert.pfx -out cert.pem -nodes
# Test TLS connection and cipher negotiation
openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'
Strong TLS Configuration
Nginx
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Protocol versions
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites (TLS 1.2)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
# HTTP to HTTPS redirect (in separate server block)
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Apache
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLUseStapling on
SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</VirtualHost>
Certificate Monitoring
#!/bin/bash
# cert-monitor.sh - Monitor certificate expiration across hosts
WARN_DAYS=30
CRIT_DAYS=7
HOSTS=(
"example.com:443"
"api.example.com:443"
"admin.example.com:443"
)
for host in "${HOSTS[@]}"; do
expiry=$(echo | openssl s_client -connect "$host" -servername "${host%%:*}" 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$expiry" ]; then
echo "ERROR: Cannot connect to $host"
continue
fi
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_left" -le "$CRIT_DAYS" ]; then
echo "CRITICAL: $host expires in $days_left days ($expiry)"
elif [ "$days_left" -le "$WARN_DAYS" ]; then
echo "WARNING: $host expires in $days_left days ($expiry)"
else
echo "OK: $host expires in $days_left days ($expiry)"
fi
done
Prometheus cert-manager Metrics
# Alert on expiring certificates in Kubernetes
groups:
- name: cert-manager
rules:
- alert: CertificateExpiringSoon
expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7 * 24 * 3600
for: 1h
labels:
severity: critical
annotations:
summary: "Certificate {{ $labels.name }} expires in less than 7 days"
- alert: CertificateNotReady
expr: certmanager_certificate_ready_status{condition="True"} == 0
for: 15m
labels:
severity: warning
annotations:
summary: "Certificate {{ $labels.name }} is not ready"
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Certbot fails with "connection refused" | Port 80 blocked by firewall | Open port 80 for ACME HTTP-01 challenge |
| "Too many certificates already issued" | Let's Encrypt rate limit hit | Use staging endpoint for testing; wait for rate limit reset |
| cert-manager challenge stuck pending | Ingress or DNS misconfigured | Check kubectl describe challenge; verify DNS records |
| Mixed content warnings | HTTP resources on HTTPS page | Update all asset URLs to HTTPS; use CSP headers |
| OCSP stapling not working | Resolver not configured | Add resolver directive in nginx; verify outbound DNS |
| Intermediate cert missing | Incomplete chain served | Use fullchain.pem not cert.pem; verify with openssl s_client -showcerts |
| TLS handshake failure | Client doesn't support offered ciphers | Add TLS 1.2 support; check cipher suite compatibility |
Best Practices
- Automate renewal with systemd timers or cert-manager
- Monitor expiration dates with alerting (30-day and 7-day warnings)
- Use only TLS 1.2 and TLS 1.3
- Enable HSTS with long max-age and includeSubDomains
- Enable OCSP stapling to improve handshake performance
- Use ECDSA keys for better performance where possible
- Test configuration with SSL Labs (ssllabs.com/ssltest)
- Keep private keys secure with proper file permissions (0600)
- Rotate certificates before expiry, not after
- Maintain a certificate inventory across all services
Related Skills
- hashicorp-vault - PKI management
- waf-setup - Web protection
- zero-trust - mTLS and identity-based access
Signals
- GitHub stars
- 1k
- Forks
- 157
- Last commit
- May 2026
Advanced
- Catalog kind
- skill
- Gateway key
ssl-tls-management- Source
- github.com/bagelhole/devops-security-agent-skills