Manage SSL/TLS certificates with Let's Encrypt and internal PKI. Configure secure HTTPS, certificate renewal, and cipher suites. Use when implementing secure communications.
npx skills add https://github.com/BagelHole/DevOps-Security-Agent-Skills --skill ssl-tls-management
Manage certificates and secure communications across web servers, Kubernetes clusters, and internal services.
Use this skill when:
certbot installed for Let's Encryptopenssl CLI available (installed by default on most Linux distros)# 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 [email protected]
# 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
# 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
# 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
# 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: [email protected]
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: [email protected]
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: [email protected]
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: {}
# 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
# 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'
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;
}
<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>
#!/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
# 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"
| 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 |
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take bagelhole/ssl-tls-management from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip.
Without those the skill loads but fails at the first command.