|$ curl https://forge-ai.dev/api/markdown?path=docs/security/tls
$cat docs/security-—-ssl/tls-&-https.md
updated Recently·14 min read·published

Security — SSL/TLS & HTTPS

SecurityIntermediate
Introduction

TLS (Transport Layer Security) and its predecessor SSL (Secure Sockets Layer) are cryptographic protocols that provide secure communication over a network. HTTPS is HTTP over TLS — it encrypts all data between the client and server, ensuring confidentiality, integrity, and authentication.

Without TLS, all data is transmitted in plain text — passwords, API keys, personal information, and session tokens can be intercepted by anyone on the network. Modern web applications should use TLS everywhere, not just on login pages.

How TLS Works

The TLS handshake establishes a secure connection between client and server. It involves cipher suite negotiation, certificate verification, and key exchange.

StepDescriptionDetails
1Client HelloClient sends supported TLS versions, cipher suites, and a random number
2Server HelloServer selects TLS version, cipher suite, sends its certificate and random number
3Certificate VerificationClient verifies server certificate against trusted CAs
4Key ExchangeClient and server derive shared session key (ECDHE, DHE)
5FinishedEncrypted handshake messages exchanged, secure channel established

info

TLS 1.3 reduces the handshake to a single round trip (1-RTT) compared to TLS 1.2's 2-RTT. It also removes insecure cipher suites and provides forward secrecy by default. Always prefer TLS 1.3 when possible.
Certificate Authorities & Chain of Trust

A Certificate Authority (CA) issues digital certificates that verify a domain's identity. The browser trusts the CA, and the CA vouches for the website. This creates a chain of trust: Root CA > Intermediate CA > Your Certificate.

certificate-inspection.sh
Bash
1# Inspect a certificate chain
2openssl s_client -connect example.com:443 -showcerts
3
4# Output shows the full chain:
5# 0 s:/CN=example.com (leaf certificate)
6# 1 s:/CN=R3 (intermediate CA)
7# 2 s:/CN=ISRG Root X1 (root CA — trusted by browser)
8
9# Download certificate chain
10openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > chain.pem
11
12# Verify a certificate against a CA bundle
13openssl verify -CAfile ca-bundle.pem certificate.pem
14
15# Generate CSR (Certificate Signing Request)
16openssl req -new -newkey rsa:2048 -nodes -keyout example.com.key -out example.com.csr -subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
17
18# Check certificate details
19openssl x509 -in certificate.pem -text -noout | grep -E "Subject:|Issuer:|Not Before|Not After|DNS:"

warning

Root CA certificates are pre-installed in browsers and operating systems. If your certificate is signed by a CA not in the browser's trust store, users will see a security warning. Use a publicly trusted CA like Let's Encrypt, DigiCert, or Sectigo for production sites.
Let's Encrypt & ACME Protocol

Let's Encrypt is a free, automated, and open CA that provides TLS certificates via the ACME (Automated Certificate Management Environment) protocol. Certificates are valid for 90 days and must be renewed automatically.

letsencrypt-setup.sh
Bash
1# Install Certbot (Let's Encrypt client)
2brew install certbot # macOS
3apt install certbot # Ubuntu/Debian
4
5# Obtain certificate (standalone mode — temporary web server)
6certbot certonly --standalone -d example.com -d www.example.com --email admin@example.com --agree-tos
7
8# Obtain certificate (webroot mode — existing web server)
9certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
10
11# Certificate location:
12# /etc/letsencrypt/live/example.com/fullchain.pem
13# /etc/letsencrypt/live/example.com/privkey.pem
14
15# Automatic renewal (certbot adds systemd timer)
16certbot renew
17
18# Test renewal process
19certbot renew --dry-run
20
21# DNS-01 challenge (for wildcard certificates)
22certbot certonly --manual --preferred-challenges dns -d *.example.com -d example.com
23
24# ACME client alternatives:
25# acme.sh, lego, Caddy automatic HTTPS, Traefik ACME

info

Wildcard certificates (*.example.com) require DNS-01 challenge, not HTTP-01. Use acme.sh or lego for more flexible ACME clients. Caddy and Traefik handle ACME automatically — no manual certbot configuration needed.
TLS Termination

TLS termination is the process of decrypting HTTPS traffic at a proxy or load balancer before forwarding it to the backend server. This offloads the CPU-intensive encryption work from application servers.

nginx-tls-termination.conf
TEXT
1# nginx — TLS termination configuration
2server {
3 listen 443 ssl http2;
4 server_name example.com www.example.com;
5
6 # Certificate paths
7 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
8 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
9
10 # TLS protocols — disable obsolete versions
11 ssl_protocols TLSv1.2 TLSv1.3;
12
13 # Secure cipher suites (Mozilla intermediate)
14 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
15 ssl_prefer_server_ciphers on;
16
17 # OCSP stapling
18 ssl_stapling on;
19 ssl_stapling_verify on;
20 resolver 8.8.8.8 8.8.4.4 valid=300s;
21 resolver_timeout 5s;
22
23 # HSTS
24 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
25
26 # Proxy to backend
27 location / {
28 proxy_pass http://backend:3000;
29 proxy_set_header Host $host;
30 proxy_set_header X-Real-IP $remote_addr;
31 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
32 proxy_set_header X-Forwarded-Proto $scheme;
33 }
34}
35
36# Redirect HTTP to HTTPS
37server {
38 listen 80;
39 server_name example.com www.example.com;
40 return 301 https://$server_name$request_uri;
41}
42
43# Cloudflare — Full (Strict) TLS mode
44# Cloudflare terminates TLS at their edge, re-encrypts to origin
45# Origin certificate can be self-signed (Cloudflare Origin CA)
46
47# AWS ELB — TLS termination at load balancer
48# Upload certificate to ACM (AWS Certificate Manager)
49# Listener: HTTPS:443 -> Target Group: HTTP:3000
50# Target group health check: HTTP

best practice

Terminate TLS at the edge (load balancer, CDN, or reverse proxy). This centralizes certificate management and reduces the computational load on application servers. Ensure the connection between your proxy and backend is also encrypted (TLS or internal network) if it traverses a shared network.
HSTS (HTTP Strict-Transport-Security)

HSTS is a response header that tells browsers to always connect via HTTPS, even if the user types http:// or clicks an HTTP link. This prevents SSL stripping and downgrade attacks.

hsts-config.txt
TEXT
1# HSTS header formats
2
3# Basic — 1 year, current domain only
4Strict-Transport-Security: max-age=31536000
5
6# Recommended — 2 years, all subdomains, preload eligibility
7Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
8
9# Nginx
10add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
11
12# Apache
13Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
14
15# Express/Node.js (via helmet)
16app.use(helmet.hsts({
17 maxAge: 63072000,
18 includeSubDomains: true,
19 preload: true,
20}));
21
22# HSTS Preload — submit your domain to https://hstspreload.org/
23# Requirements:
24# 1. Valid HTTPS certificate on the root domain
25# 2. Redirect HTTP to HTTPS on all subdomains
26# 3. HSTS header with max-age >= 31536000 and includeSubDomains
27# 4. HSTS header on the main domain for all requests
28
29# Once accepted, browsers hardcode your domain as HTTPS-only
30# Removal takes months (browser updates are slow)

danger

HSTS preload is a one-way decision. Once your domain is in the preload list, it can take months for browsers to remove it. Ensure your entire site (including all subdomains) can serve HTTPS before enabling preload. Test with a short max-age first, then increase to 2 years.
Certificate Pinning vs CA Verification

Certificate pinning associates a host with its expected certificate or public key. While CA verification checks against a list of trusted CAs, pinning checks against a specific certificate or key. HTTP Public Key Pinning (HPKP) is deprecated — modern approaches use Expect-CT or certificate transparency.

certificate-pinning.ts
TypeScript
1// Certificate pinning in Node.js (for API clients)
2import https from 'https';
3import crypto from 'crypto';
4import fs from 'fs';
5
6// Option 1: Pin by certificate fingerprint
7const EXPECTED_PIN = 'sha256/abc123def456...';
8
9const options = {
10 hostname: 'api.example.com',
11 port: 443,
12 path: '/',
13 checkServerIdentity: (hostname, cert) => {
14 const fingerprint = crypto
15 .createHash('sha256')
16 .update(cert.raw)
17 .digest('base64');
18
19 const pin = `sha256/${fingerprint}`;
20
21 if (pin !== EXPECTED_PIN) {
22 return new Error(`Certificate pin mismatch: expected ${EXPECTED_PIN}, got ${pin}`);
23 }
24
25 return undefined; // No error — certificate matches
26 },
27};
28
29https.get(options, (res) => {
30 console.log('Connection established with pinned certificate');
31});
32
33// Option 2: Pin by public key
34const EXPECTED_PUBLIC_KEY = '...';
35
36const checkPublicKey = (cert) => {
37 const publicKey = crypto
38 .createHash('sha256')
39 .update(cert.pubkey)
40 .digest('base64');
41
42 return publicKey === EXPECTED_PUBLIC_KEY;
43};
44
45// Certificate Transparency — alternative to pinning
46// Use Expect-CT header to enforce CT log inclusion
47// Expect-CT: max-age=86400, enforce, report-uri="https://example.com/report"
48
49// Modern approach: use Certificate Transparency (CT) logs
50// All publicly trusted CAs must log certificates to CT logs
51// Browsers require CT for certificates issued after April 2018

warning

HPKP (HTTP Public Key Pinning) is deprecated and should not be used. If you pin incorrectly, your site becomes inaccessible until the pin expires. Prefer Certificate Transparency (CT) monitoring and Expect-CT headers instead. For mobile apps, pinning is still valuable but requires a backup pin strategy.
mTLS (Mutual TLS)

Mutual TLS extends the TLS handshake so that the client also presents a certificate to the server. Both sides verify each other's identity. It is commonly used in microservice communication, IoT, and API security.

mtls-setup.sh
Bash
1# Generate client certificate (mTLS)
2# 1. Create a CA for internal use
3openssl genrsa -out ca.key 4096
4openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=Internal CA"
5
6# 2. Generate client certificate
7openssl genrsa -out client.key 2048
8openssl req -new -key client.key -out client.csr -subj "/CN=api-client"
9
10# 3. Sign with CA
11openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256
12
13# nginx — require client certificate
14server {
15 listen 443 ssl http2;
16 server_name api.example.com;
17
18 ssl_certificate /etc/ssl/server.crt;
19 ssl_certificate_key /etc/ssl/server.key;
20
21 # mTLS configuration
22 ssl_client_certificate /etc/ssl/ca.crt; # Trusted CA
23 ssl_verify_client on; # Require client cert
24 ssl_verify_depth 2; # Max chain depth
25
26 # Pass client certificate info to backend
27 location / {
28 proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
29 proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
30 proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
31 proxy_pass http://backend:3000;
32 }
33}
34
35# Envoy proxy mTLS configuration
36# static_resources:
37# listeners:
38# - filters:
39# - typed_config:
40# "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
41# require_client_certificate: true
42# common_tls_context:
43# tls_certificates:
44# certificate_chain: { filename: "/etc/envoy/server.crt" }
45# private_key: { filename: "/etc/envoy/server.key" }
46# validation_context:
47# trusted_ca: { filename: "/etc/envoy/ca.crt" }

best practice

Use mTLS for service-to-service communication in production (service mesh). Istio, Linkerd, and Consul Connect automate mTLS between microservices. For API-to-API communication, mTLS provides stronger authentication than API keys or JWTs alone because it operates at the transport layer.
TLS Versions: 1.2 vs 1.3

TLS 1.3 is the latest version of the protocol, offering significant improvements in security, performance, and privacy over TLS 1.2.

FeatureTLS 1.2TLS 1.3
Handshake rounds2 round trips (2-RTT)1 round trip (1-RTT) or 0-RTT
Cipher suitesMany options (some insecure)5 secure AEAD ciphers only
Forward secrecyOptionalMandatory for all ciphers
DeprecatedStill supports RC4, 3DES, CBCRemoves all legacy algorithms
0-RTTNot supportedSupported (with anti-replay)
Browser supportUniversal97%+ (2024)
check-tls-versions.sh
Bash
1# Check TLS version support for a domain
2openssl s_client -connect example.com:443 -tls1_2 2>/dev/null | grep "Protocol"
3openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | grep "Protocol"
4
5# Test cipher suite support
6openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES128-GCM-SHA256 2>/dev/null
7
8# Nginx — disable TLS 1.0 and 1.1
9ssl_protocols TLSv1.2 TLSv1.3;
10
11# Cloudflare — TLS settings in dashboard
12# SSL/TLS > Edge TLS Settings > Minimum TLS Version: 1.2
13
14# AWS CloudFront — security policy
15# Security Policy: TLSv1.2_2021 or TLSv1.2_2023 (includes TLS 1.3)

info

Disable TLS 1.0 and 1.1 — they are deprecated by all major browsers and security standards (PCI-DSS, NIST). TLS 1.2 is the minimum acceptable version, TLS 1.3 is preferred. Check your server's TLS configuration with SSL Labs or testssl.sh.
SSL/TLS Configuration Checkers

Regularly test your TLS configuration to ensure it meets current security standards. These tools check for common misconfigurations, weak ciphers, and protocol support.

tls-checkers.sh
Bash
1# SSL Labs — online scanner (https://www.ssllabs.com/ssltest/)
2# Tests: certificate, protocol support, cipher strength, TLS 1.3, HSTS
3# Score: A+ to F
4
5# testssl.sh — local TLS scanner
6git clone https://github.com/drwetter/testssl.sh.git
7cd testssl.sh
8./testssl.sh https://example.com
9
10# Check specific vulnerabilities
11./testssl.sh --heartbleed https://example.com
12./testssl.sh --poodle https://example.com
13./testssl.sh --logjam https://example.com
14./testssl.sh --freak https://example.com
15
16# Comprehensive scan
17./testssl.sh --fast --parallel https://example.com
18./testssl.sh --full https://example.com
19
20# Output formats: HTML, JSON, CSV
21./testssl.sh --htmlfile report.html https://example.com
22
23# Additional checker: Mozilla SSL Configuration Generator
24# https://ssl-config.mozilla.org/
25# Generates secure configs for nginx, Apache, HAProxy, etc.
26
27# Qualys SSL Labs API
28curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com" | jq .

best practice

Run a TLS security scan monthly and after any infrastructure change. Aim for an A+ rating on SSL Labs. The Mozilla SSL Configuration Generator provides secure configurations for most web servers — use it as your starting point rather than writing TLS config from scratch.
HTTPS in Development

Use HTTPS locally during development to detect mixed content issues, test service workers, and work with secure-only APIs. mkcert creates locally trusted certificates easily.

local-https.sh
Bash
1# mkcert — local development certificates
2brew install mkcert # macOS
3# apt install mkcert # Linux
4
5# Install local CA
6mkcert -install
7
8# Create certificate for localhost and dev domains
9mkcert localhost 127.0.0.1 ::1
10# Creates: localhost+2.pem, localhost+2-key.pem
11
12# Create for custom dev domain
13mkcert myapp.local "*.myapp.local"
14
15# Use with Node.js HTTPS server
16// const https = require('https');
17// const fs = require('fs');
18//
19// const options = {
20// key: fs.readFileSync('localhost+2-key.pem'),
21// cert: fs.readFileSync('localhost+2.pem'),
22// };
23//
24// https.createServer(options, app).listen(3000);
25
26# Use with Next.js (next.config.js)
27// module.exports = {
28// devIndicators: {
29// https: true,
30// },
31// };
32
33# Self-signed certificates (alternative to mkcert)
34openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout selfsigned.key -out selfsigned.crt -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
35
36# Note: self-signed certs show browser warnings
37# mkcert is preferred because the CA is trusted locally

info

Use mkcert for local development — it creates a local CA and installs it in your system trust store, so browsers fully trust your local certificates with no warnings. This is much better than self-signed certificates or disabling HTTPS checks in the browser.
Best Practices
  • Use TLS 1.2 or 1.3 — disable SSLv3, TLS 1.0, and TLS 1.1.
  • Use modern, secure cipher suites with forward secrecy (ECDHE).
  • Enable HSTS with includeSubDomains and submit to the preload list.
  • Automate certificate renewal with ACME (Let's Encrypt, ZeroSSL).
  • Use OCSP stapling to improve certificate verification performance and privacy.
  • Terminate TLS at the edge (load balancer, CDN, or reverse proxy).
  • Test your TLS configuration regularly with SSL Labs and testssl.sh.
  • Use HTTPS in all environments, including development — never disable HTTPS verification in production code.
  • Pin certificates only when necessary (mobile apps) and include backup pins.
  • Monitor certificate expiry — set up alerts at 30, 14, and 7 days before expiration.

best practice

TLS configuration is not a set-it-and-forget-it task. Security standards evolve: ciphers get broken, new TLS versions are released, and browsers deprecate old protocols. Review your TLS configuration annually and after any major infrastructure change.
$Blueprint — Engineering Documentation·Section ID: SEC-TLS·Revision: 1.0