Skip to main content
Technology & EngineeringNetworking Infrastructure232 lines

SSL TLS

SSL/TLS certificate management, HTTPS configuration, and transport security best practices

Quick Summary9 lines
You are an expert in SSL/TLS certificates and HTTPS setup for building reliable networked systems.

## Key Points

- **Use TLS 1.3 with TLS 1.2 as fallback** — disable TLS 1.0 and 1.1 entirely, as they have known vulnerabilities and are deprecated by all major browsers.
- **Automate certificate renewal** using certbot timers, acme.sh cron jobs, or cloud-native solutions (ACM, Google-managed certs) — manual renewal is a guaranteed outage waiting to happen.
- **Enable HSTS with a long max-age** and submit to the HSTS preload list so browsers always connect via HTTPS, preventing SSL-stripping attacks.
skilldb get networking-infrastructure-skills/SSL TLSFull skill: 232 lines
Paste into your CLAUDE.md or agent config

SSL/TLS — Networking & Infrastructure

You are an expert in SSL/TLS certificates and HTTPS setup for building reliable networked systems.

Core Philosophy

Overview

TLS (Transport Layer Security) encrypts communication between clients and servers, ensuring confidentiality, integrity, and authentication. Proper TLS configuration is non-negotiable for production services — it protects user data, enables HTTP/2 and HTTP/3, satisfies compliance requirements, and is a ranking factor for search engines. This skill covers certificate types, acquisition and renewal, server configuration, and modern TLS best practices.

Core Concepts

TLS Handshake (TLS 1.3)

Client                                  Server
  │                                       │
  ├──ClientHello (supported ciphers)─────→│
  │                                       │
  │←──ServerHello + Certificate + Finish──┤
  │                                       │
  ├──Client Finish (encrypted)───────────→│
  │                                       │
  │←═══════ Encrypted Data ══════════════→│

TLS 1.3: 1-RTT handshake (vs 2-RTT in TLS 1.2)
TLS 1.3: 0-RTT resumption for returning clients

Certificate Types

TypeValidationUse Case
DV (Domain Validated)Domain ownership onlyMost websites, APIs
OV (Organization Validated)Domain + org verificationBusiness-facing services
EV (Extended Validation)Domain + org + legal verificationFinancial, healthcare
Wildcard (*.example.com)Covers all subdomainsMulti-subdomain setups
SAN (Subject Alternative Name)Multiple specific domainsMultiple domains on one cert

Certificate Chain

Root CA (trusted by browsers/OS)
  └── Intermediate CA
        └── Leaf Certificate (your domain)

Server must send: Leaf + Intermediate(s)
Root CA is already in the client's trust store

Implementation Patterns

Let's Encrypt with Certbot

# Install certbot
sudo apt install certbot python3-certbot-nginx

# Obtain certificate (Nginx plugin auto-configures)
sudo certbot --nginx -d example.com -d www.example.com

# Obtain certificate (standalone mode, for non-Nginx setups)
sudo certbot certonly --standalone -d example.com

# Obtain wildcard certificate (requires DNS challenge)
sudo certbot certonly --manual --preferred-challenges dns \
  -d "*.example.com" -d "example.com"

# Auto-renewal (certbot installs a systemd timer by default)
sudo certbot renew --dry-run

# Check certificate expiry
sudo certbot certificates

Let's Encrypt with acme.sh (Lightweight Alternative)

# Install
curl https://get.acme.sh | sh

# Issue with DNS API (Cloudflare example)
export CF_Token="your-api-token"
export CF_Zone_ID="your-zone-id"

acme.sh --issue --dns dns_cf -d example.com -d "*.example.com"

# Install certificate to Nginx
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.pem \
  --reloadcmd      "systemctl reload nginx"

Nginx TLS Configuration

server {
    listen 443 ssl http2;
    server_name example.com;

    # Certificate files
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # TLS protocol versions
    ssl_protocols TLSv1.2 TLSv1.3;

    # Cipher suites (TLS 1.2 — TLS 1.3 ciphers are fixed)
    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;  # Let client choose (modern best practice)

    # OCSP Stapling
    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;

    # Session resumption
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;  # Rotate keys if enabling

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" 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;
}

Terraform ACM Certificate (AWS)

resource "aws_acm_certificate" "cert" {
  domain_name               = "example.com"
  subject_alternative_names = ["*.example.com"]
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id = aws_route53_zone.primary.zone_id
  name    = each.value.name
  type    = each.value.type
  records = [each.value.record]
  ttl     = 60
}

resource "aws_acm_certificate_validation" "cert" {
  certificate_arn         = aws_acm_certificate.cert.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

Self-Signed Certificates (Development Only)

# Generate self-signed cert for local development
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 \
  -nodes -keyout dev.key -out dev.crt \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

# Using mkcert for trusted local development certs
mkcert -install  # Install local CA
mkcert localhost 127.0.0.1 ::1  # Generate trusted cert

Diagnostic Commands

# Check certificate details
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -text -noout

# Check expiration date
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

# Verify certificate chain
openssl verify -CAfile chain.pem cert.pem

# Test TLS configuration
nmap --script ssl-enum-ciphers -p 443 example.com

# Check with SSL Labs (web)
# https://www.ssllabs.com/ssltest/analyze.html?d=example.com

Best Practices

  • Use TLS 1.3 with TLS 1.2 as fallback — disable TLS 1.0 and 1.1 entirely, as they have known vulnerabilities and are deprecated by all major browsers.
  • Automate certificate renewal using certbot timers, acme.sh cron jobs, or cloud-native solutions (ACM, Google-managed certs) — manual renewal is a guaranteed outage waiting to happen.
  • Enable HSTS with a long max-age and submit to the HSTS preload list so browsers always connect via HTTPS, preventing SSL-stripping attacks.

Common Pitfalls

  • Incomplete certificate chain: Sending only the leaf certificate without intermediates causes trust failures on some clients (especially mobile and older systems). Always serve the full chain (leaf + intermediates).
  • Letting certificates expire: Without automated renewal and monitoring, certificates silently expire and cause hard outages. Set up alerting for certificates expiring within 14 days, even if renewal is automated.

Anti-Patterns

Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.

Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.

Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add networking-infrastructure-skills

Get CLI access →