AI Tools Compared

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:

  1. Generating Mozilla-recommended cipher suites for your server software and version
  2. Auditing existing configs against current TLS best practices
  3. Building automated certificate renewal pipelines
  4. 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:

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.”


Built by theluckystrike — More at zovo.one