Claude
Skills
Sign in
Back

ssl-tls

Included with Lifetime
$97 forever

SSL/TLS certificate management skill: Let's Encrypt, Certbot, certificate types, TLS version/cipher configuration, HSTS, OCSP stapling, renewal automation, and local development certificates. USE WHEN: - Obtaining and renewing Let's Encrypt certificates (standalone, webroot, DNS challenge) - Configuring TLS on Nginx or Apache (protocols, cipher suites, session parameters) - Setting up HSTS with preload, OCSP stapling, or DHParam generation - Automating certificate renewal via systemd timer and deploy hooks - Managing multi-domain (SAN) or wildcard certificates - Debugging TLS handshake failures, mixed content, or ACME challenge errors - Setting up mkcert for trusted local HTTPS development DO NOT USE FOR: - mTLS / client certificate authentication (use service-mesh or api-gateway skill) - Application-level JWT or session token management - SSH key management - Internal CA for Kubernetes clusters (use cert-manager)

Backend & APIs

What this skill does


# SSL/TLS Certificate Management

## Certificate Types

| Type | Issued to | Validation | Browser trust | Wildcard support |
|---|---|---|---|---|
| **DV** (Domain Validated) | Domain owner | Automated DNS/HTTP | Yes | Via wildcard DV |
| **OV** (Organization Validated) | Legal entity | Manual org check | Yes | No |
| **EV** (Extended Validation) | Legal entity + strict checks | Extensive manual | Yes (green bar deprecated) | No |
| **Wildcard** (e.g. `*.example.com`) | Domain owner | DNS challenge only | Yes | Yes — one level only |
| **Self-signed** | Anyone | None | No — browser warning | Yes |
| **mkcert** (local CA) | Localhost, IPs | Local trust store | Yes (after trust-store install) | Yes |

Let's Encrypt issues **DV** certs only. Wildcard certs require the **DNS-01** challenge.

---

## ACME Protocol Basics

ACME (Automatic Certificate Management Environment, RFC 8555) works as follows:

1. Client (Certbot) contacts the CA (Let's Encrypt) and creates an **Order** for one or more domains.
2. CA issues a **Challenge** — either HTTP-01 (place a file at `/.well-known/acme-challenge/<token>`) or DNS-01 (create a `_acme-challenge.<domain>` TXT record).
3. Client completes the challenge.
4. CA verifies the challenge and issues the certificate.
5. Client stores `fullchain.pem` (cert + intermediates) and `privkey.pem`.

Certificates are valid for **90 days**; automate renewal to run at least every 60 days.

---

## Certbot — All Challenge Types

### Standalone (no existing web server on port 80)

```bash
# Install Certbot (Ubuntu/Debian)
sudo apt-get install -y certbot

# Obtain cert — Certbot spins up a temporary HTTP server on port 80
sudo certbot certonly \
  --standalone \
  --preferred-challenges http \
  -d example.com \
  -d www.example.com \
  --email [email protected] \
  --agree-tos \
  --no-eff-email

# Files created in: /etc/letsencrypt/live/example.com/
#   fullchain.pem  — certificate + intermediates (use this in ssl_certificate)
#   privkey.pem    — private key
#   chain.pem      — intermediates only (use for OCSP stapling)
#   cert.pem       — leaf certificate only (rarely needed)
```

### Webroot (Nginx / Apache keeps running)

```bash
# Ensure the webroot is served by your web server at /.well-known/acme-challenge/
# Nginx: location /.well-known/acme-challenge/ { root /var/www/certbot; }

sudo certbot certonly \
  --webroot \
  --webroot-path /var/www/certbot \
  -d example.com \
  -d www.example.com \
  --email [email protected] \
  --agree-tos \
  --no-eff-email
```

### DNS Challenge — Cloudflare (wildcard support)

```bash
# Install Certbot Cloudflare DNS plugin
sudo apt-get install -y python3-certbot-dns-cloudflare

# Create credentials file (readable only by root)
sudo mkdir -p /etc/letsencrypt/credentials
sudo tee /etc/letsencrypt/credentials/cloudflare.ini > /dev/null <<'EOF'
# Cloudflare API token (Zone:DNS:Edit permission, scoped to zone)
dns_cloudflare_api_token = YOUR_CF_API_TOKEN_HERE
EOF
sudo chmod 600 /etc/letsencrypt/credentials/cloudflare.ini

# Obtain wildcard + apex certificate
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/credentials/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  -d example.com \
  -d "*.example.com" \
  --email [email protected] \
  --agree-tos \
  --no-eff-email
```

---

## Nginx SSL Block (Production-Ready)

```nginx
# Inside server { listen 443 ssl; ... }

ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key     /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;  # OCSP stapling

# Protocol: drop TLS 1.0 and 1.1 (deprecated in RFC 8996)
ssl_protocols TLSv1.2 TLSv1.3;

# Cipher suites: TLS 1.3 ciphers are implicit and non-configurable.
# The list below applies to TLS 1.2 only.
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';

# Let clients (especially TLS 1.3) pick their preferred cipher
ssl_prefer_server_ciphers off;

# Session resumption (reduces handshake overhead for returning clients)
ssl_session_cache   shared:SSL:10m;   # 1MB ≈ 4000 sessions; 10m ≈ 40000 sessions
ssl_session_timeout 1d;
ssl_session_tickets off;              # Disable — tickets use a server-side key that
                                      # if leaked undermines forward secrecy

# DHE key exchange parameters (generated once; 2048-bit minimum)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;

# OCSP Stapling: server fetches and caches the OCSP response;
# eliminates client round-trip to CA's OCSP responder
ssl_stapling        on;
ssl_stapling_verify on;
resolver            1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout    5s;

# HSTS: tell browsers to always use HTTPS for this domain
# preload: submit to browser preload lists (irreversible for ~1 year)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
```

---

## DHParam Generation

```bash
# Generate 2048-bit DH parameters (runs once; takes ~30 seconds)
sudo mkdir -p /etc/nginx/ssl
sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

# 4096-bit for highest security (takes several minutes — overkill for most apps)
sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
```

---

## Renewal Automation — systemd Timer

### /etc/systemd/system/certbot-renewal.service

```ini
[Unit]
Description=Certbot Renewal
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew \
    --quiet \
    --deploy-hook /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
StandardOutput=journal
StandardError=journal
```

### /etc/systemd/system/certbot-renewal.timer

```ini
[Unit]
Description=Run Certbot renewal twice daily

[Timer]
OnCalendar=*-*-* 03,15:00:00   # 3 AM and 3 PM UTC
RandomizedDelaySec=3600         # Spread load on Let's Encrypt's servers
Persistent=true                 # Run immediately if last run was missed (e.g. server was off)

[Install]
WantedBy=timers.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renewal.timer
sudo systemctl list-timers certbot-renewal
```

### Deploy Hook — /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

```bash
#!/bin/bash
# Called by Certbot after a successful renewal.
# RENEWED_LINEAGE is set to the path of the renewed certificate.
set -euo pipefail

echo "[certbot-deploy] Reloading Nginx after renewal of ${RENEWED_LINEAGE}"

# Validate new config before reloading
nginx -t || { echo "[certbot-deploy] ERROR: Nginx config test failed"; exit 1; }

systemctl reload nginx
echo "[certbot-deploy] Nginx reloaded successfully"
```

```bash
sudo chmod 700 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
```

---

## Local Development — mkcert

```bash
# Install mkcert (Linux/macOS)
# macOS: brew install mkcert
# Linux: download binary from https://github.com/FiloSottile/mkcert/releases
sudo apt-get install -y libnss3-tools   # Required for Chrome/Firefox trust store
wget -q "https://dl.filippo.io/mkcert/latest?for=linux/amd64" -O mkcert
chmod +x mkcert && sudo mv mkcert /usr/local/bin/

# Install local CA into system and browser trust stores
mkcert -install

# Generate certificate for local domains and IPs
mkcert localhost 127.0.0.1 ::1 myapp.local "*.myapp.local"

# Output: localhost+5.pem + localhost+5-key.pem in current directory
# Reference these files in your local Nginx / dev server TLS config
```

---

## Certificate Inspection Commands

```bash
# Inspect the live certificate served by a host
openssl s_client -connect example.com:443 -servername example.com \
  </dev/null 2>/dev/null | openssl x509 -noout -text

# Check expiry date only
openssl s_client -connect example.com:443 -servername example.com \
  </dev/null 2>/dev/null | openssl x509 -noout -dates

# Inspect a cert fil

Related in Backend & APIs