SSL/TLS configuration is a domain where errors have serious consequences: expired certificates cause downtime, misconfigured ciphers expose users to attacks, and incorrect HSTS headers can lock out users for months. AI tools are useful for generating correct initial configurations, auditing existing setups, and automating renewal workflows. This guide covers both.
The Modern TLS Configuration Problem
The “correct” TLS configuration changes as cipher suites age out and vulnerabilities emerge. Most developers configure TLS once and forget it. AI tools help by:
- Generating Mozilla-recommended cipher suites for your server software and version
- Auditing existing configs against current TLS best practices
- Building automated certificate renewal pipelines
- Generating HSTS policies without foot-shooting
The challenge is that TLS configuration involves interacting layers: the server software version, the OpenSSL version it was compiled against, the CA issuing certificates, and the client compatibility target. A setting that’s correct for Nginx 1.25 on Ubuntu 22.04 may be wrong for Apache 2.4 on RHEL 8. AI tools handle this nuance reasonably well when you include version context in your prompts.
Using Claude to Generate TLS Configs
Claude knows the Mozilla SSL Configuration Generator recommendations and can apply them to specific server versions.
Prompt:
Generate a modern TLS configuration for:
- Nginx 1.25 on Ubuntu 22.04
- Let's Encrypt certificate (already obtained at /etc/letsencrypt/live/example.com/)
- Support: browsers from 2019+, no IE11
- Include OCSP stapling, HSTS, and session configuration
Claude’s Nginx TLS block:
# /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# ACME challenge for Let's Encrypt renewal
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on; # Nginx 1.25.1+: http2 directive replaces listen ... http2
server_name example.com www.example.com;
# Certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Mozilla Intermediate configuration — TLS 1.2 + 1.3, modern browsers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off; # Required for TLS 1.3 forward secrecy
# Session configuration
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # ~40k sessions
ssl_session_tickets off; # Disable for perfect forward secrecy
# OCSP Stapling — improves handshake performance
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# HSTS — 2 years, includeSubDomains, preload-ready
# WARNING: only add 'preload' after verifying all subdomains are HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# DH parameters — generate with: openssl dhparam -out /etc/ssl/dhparam.pem 2048
ssl_dhparam /etc/ssl/dhparam.pem;
# Document root and other config below...
root /var/www/example.com;
index index.html;
}
Claude explains the ssl_prefer_server_ciphers off setting: required for TLS 1.3 because TLS 1.3 cipher suites don’t follow the server preference model. GPT-4 sometimes sets this to on, which is technically wrong for TLS 1.3.
Automating Certificate Renewal
The most common automation pattern: Certbot in standalone or webroot mode with a pre/post hook:
#!/bin/bash
# certbot-renew.sh — generated by Claude
set -euo pipefail
DOMAIN="example.com"
WEBROOT="/var/www/certbot"
LOG="/var/log/certbot-renew.log"
# Create webroot if it doesn't exist
mkdir -p "$WEBROOT"
# Attempt renewal
certbot renew \
--webroot \
--webroot-path "$WEBROOT" \
--non-interactive \
--agree-tos \
--deploy-hook "/usr/local/bin/reload-nginx.sh" \
>> "$LOG" 2>&1
echo "$(date): Renewal check complete" >> "$LOG"
#!/bin/bash
# /usr/local/bin/reload-nginx.sh
# Test nginx config before reloading
nginx -t && systemctl reload nginx
echo "$(date): Nginx reloaded after cert renewal" >> /var/log/certbot-renew.log
# Cron entry (run twice daily as recommended by Let's Encrypt)
0 0,12 * * * root /usr/local/bin/certbot-renew.sh
Monitoring Certificate Expiry with AI-Generated Scripts
Automated renewal can fail silently. A certificate expiry monitoring script catches those failures before users do:
#!/usr/bin/env python3
# cert_monitor.py — checks expiry for a list of domains and alerts via email
import ssl
import socket
import smtplib
import datetime
from email.mime.text import MIMEText
from dataclasses import dataclass, field
from typing import Optional
DOMAINS = [
"example.com",
"api.example.com",
"admin.example.com",
]
ALERT_DAYS = 14 # Warn when cert expires within this many days
SMTP_HOST = "localhost"
ALERT_EMAIL = "ops@example.com"
@dataclass
class CertStatus:
domain: str
days_remaining: Optional[int] = None
error: Optional[str] = None
@property
def expiring_soon(self) -> bool:
return self.days_remaining is not None and self.days_remaining < ALERT_DAYS
@property
def ok(self) -> bool:
return self.error is None and not self.expiring_soon
def check_cert_expiry(domain: str, port: int = 443) -> CertStatus:
ctx = ssl.create_default_context()
try:
with ctx.wrap_socket(
socket.create_connection((domain, port), timeout=10),
server_hostname=domain,
) as conn:
cert = conn.getpeercert()
expiry_str = cert["notAfter"]
expiry = datetime.datetime.strptime(expiry_str, "%b %d %H:%M:%S %Y %Z")
days_remaining = (expiry - datetime.datetime.utcnow()).days
return CertStatus(domain=domain, days_remaining=days_remaining)
except Exception as e:
return CertStatus(domain=domain, error=str(e))
def send_alert(statuses: list[CertStatus]) -> None:
problems = [s for s in statuses if not s.ok]
if not problems:
return
lines = []
for s in problems:
if s.error:
lines.append(f" ERROR {s.domain}: {s.error}")
else:
lines.append(f" EXPIRING {s.domain}: {s.days_remaining} days remaining")
body = "SSL certificate alert:\n\n" + "\n".join(lines)
msg = MIMEText(body)
msg["Subject"] = f"SSL Alert: {len(problems)} domain(s) need attention"
msg["From"] = ALERT_EMAIL
msg["To"] = ALERT_EMAIL
with smtplib.SMTP(SMTP_HOST) as smtp:
smtp.sendmail(ALERT_EMAIL, [ALERT_EMAIL], msg.as_string())
if __name__ == "__main__":
statuses = [check_cert_expiry(d) for d in DOMAINS]
for s in statuses:
if s.ok:
print(f"OK {s.domain}: {s.days_remaining}d remaining")
elif s.error:
print(f"ERROR {s.domain}: {s.error}")
else:
print(f"WARN {s.domain}: expires in {s.days_remaining}d")
send_alert(statuses)
Run this as a daily cron job alongside the renewal script. If Certbot fails to renew, this script catches the approaching expiry and sends a warning with 14 days of lead time.
Automated TLS Audit with Claude
Use Claude to audit an existing SSL Labs report:
# tls_auditor.py
import anthropic
import subprocess
import json
def audit_tls_config(domain: str) -> str:
"""Run testssl.sh and have Claude analyze the results."""
client = anthropic.Anthropic()
# Run testssl.sh (must be installed)
result = subprocess.run(
["testssl", "--json", "--quiet", domain],
capture_output=True, text=True, timeout=120
)
if result.returncode not in (0, 1): # testssl exits 1 on warnings
return f"testssl.sh failed: {result.stderr}"
try:
findings = json.loads(result.stdout)
except json.JSONDecodeError:
findings = result.stdout[:5000] # Fall back to raw text
message = client.messages.create(
model="claude-opus-4-6",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Analyze this TLS configuration audit for {domain}:
{json.dumps(findings, indent=2)[:6000]}
Provide:
1. Critical issues that must be fixed immediately
2. Recommended improvements for security hardening
3. Specific config changes (show the nginx/apache directives to add/change)
4. Overall grade and reasoning"""
}]
)
return message.content[0].text
if __name__ == "__main__":
import sys
domain = sys.argv[1] if len(sys.argv) > 1 else "example.com"
print(audit_tls_config(domain))
Docker + Traefik Automated TLS
For containerized applications, Traefik handles TLS automatically:
# docker-compose.yml — Traefik with automatic Let's Encrypt
# Generated by Claude
services:
traefik:
image: traefik:v3.0
command:
- --api.dashboard=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls.certresolver=letsencrypt
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
- --certificatesresolvers.letsencrypt.acme.email=admin@example.com
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
app:
image: my-app:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
volumes:
letsencrypt:
Wildcard Certificates with DNS-01 Challenge
For internal services or wildcard certificates, the DNS-01 challenge is required. Claude generates the correct Certbot incantation plus a Route53 deploy hook:
#!/bin/bash
# Obtain wildcard cert using DNS-01 challenge with Route53
# Requires: certbot, python3-certbot-dns-route53, AWS credentials
DOMAIN="example.com"
EMAIL="admin@example.com"
certbot certonly \
--dns-route53 \
--dns-route53-propagation-seconds 30 \
-d "${DOMAIN}" \
-d "*.${DOMAIN}" \
--email "${EMAIL}" \
--agree-tos \
--non-interactive
# /usr/local/bin/post-renew-wildcard.sh
# Deploy hook: copies renewed cert to services that need it
set -euo pipefail
DOMAIN="example.com"
CERT_SRC="/etc/letsencrypt/live/${DOMAIN}"
# Reload nginx
nginx -t && systemctl reload nginx
# Restart internal service that reads cert files directly
systemctl restart internal-grpc-service
echo "$(date): Wildcard cert deployed for ${DOMAIN}" >> /var/log/certbot-renew.log
GPT-4 generates similar DNS-01 scripts but sometimes omits the --dns-route53-propagation-seconds flag, which causes intermittent failures when Route53 propagation is slow.
Comparing AI Tool Accuracy
| TLS Task | Claude | GPT-4 |
|---|---|---|
| Modern cipher suite generation | Correct — Mozilla Intermediate | Usually correct, check TLS 1.3 settings |
| OCSP stapling config | Includes resolver directive | Sometimes misses resolver |
| HSTS preload warning | Yes — warns about subdomains | Sometimes |
| TLS 1.3 cipher preferences | Correct ssl_prefer_server_ciphers off | Sometimes wrong |
| DH param recommendation | Includes dhparam generation command | Sometimes omits |
| Traefik/Caddy automation | Strong | Good |
| DNS-01 wildcard certs | Includes propagation delay flag | Sometimes omits |
| Certificate expiry monitoring | Full script with alerting | Basic check |
Prompting Tips for Better TLS Configs
When prompting for TLS configuration, include:
- Server software and version —
Nginx 1.25, not justNginx - OS and OpenSSL version —
Ubuntu 22.04 / OpenSSL 3.0 - Client compatibility target —
modern (no IE11),intermediate (2016+), orlegacy - Certificate type — Let’s Encrypt, DigiCert, self-signed, wildcard
- Additional requirements — OCSP stapling, mutual TLS, client certificate auth
The more specific the prompt, the less you need to verify manually. Both Claude and GPT-4 produce better output when given version context upfront rather than being asked to “make some assumptions.”
Related Reading
- Best AI Tools for Writing Apache Configs
- Best AI Tools for Writing Caddy Configs
- How to Use AI for JWT Token Debugging
Built by theluckystrike — More at zovo.one