AlgoInsight

Free lesson · System Design Fundamentals

SSL/TLS

Hard11 min readsystem-designhlddistributed-systems

30.1 SSL/TLS

The Encryption Layer for the Web

Every time you open a website, check your bank balance, or send a message, your data travels across the internet - through your home router, through your ISP's network, through undersea cables, through data center switches, and finally to the server. At every hop along that path, someone could be listening. Your ISP could read your traffic. The coffee shop WiFi operator could intercept your requests. A compromised router could copy your data. Without encryption, every HTTP request - every password you type, every credit card number you enter, every private message you send - travels as plain text that anyone on the network path can read.

SSL/TLS is the protocol that prevents this. It wraps the communication between your browser and the server in an encrypted tunnel. Anyone intercepting the traffic sees scrambled bytes. They cannot read the content, they cannot modify it without detection, and they cannot impersonate the server you think you are talking to.

TLS (Transport Layer Security) is the current protocol. SSL (Secure Sockets Layer) is its predecessor, now deprecated and insecure. The industry still uses "SSL" colloquially, but every modern system uses TLS. When someone says "SSL certificate," they mean a certificate used with TLS. When someone says "SSL termination," they mean TLS termination.

What TLS Provides

Interactive animation: Three guarantees: eavesdroppers see noise, tampering is detected, and the certificate proves who the server is.

TLS provides three security guarantees, and understanding each is essential for system design:

Confidentiality. The data exchanged between client and server is encrypted. An eavesdropper who intercepts the traffic cannot read it. This protects passwords, personal information, API keys, and every other piece of data in transit.

Integrity. The data cannot be modified in transit without detection. If an attacker modifies a byte of the encrypted traffic, the recipient's TLS implementation detects the tampering and rejects the data. This prevents man-in-the-middle attacks where an attacker modifies requests or responses - imagine someone changing the dollar amount on a wire transfer mid-flight.

Authentication. The client can verify that it is talking to the real server, not an imposter. This is done through certificates - the server presents a certificate signed by a trusted Certificate Authority (CA), proving its identity. Without authentication, an attacker could intercept your connection to your bank and redirect it to their own fake server.

graph TD
    subgraph TLS_Guarantees["TLS Security Guarantees"]
        C["Confidentiality<br/>Data is encrypted<br/>Eavesdroppers see noise"]
        I["Integrity<br/>Data cannot be modified<br/>Tampering is detected"]
        A["Authentication<br/>Server identity is verified<br/>No impersonation"]
    end

    style C fill:#1e293b,stroke:#3b82f6,color:#e2e8f0
    style I fill:#1e293b,stroke:#10b981,color:#e2e8f0
    style A fill:#1e293b,stroke:#f59e0b,color:#e2e8f0
This diagram shows the three security guarantees provided by TLS. Confidentiality means data is encrypted and eavesdroppers cannot read it. Integrity means data cannot be modified in transit without detection. Authentication means the server's identity is verified through certificates, preventing impersonation.

The TLS Handshake: Step by Step

Interactive animation: Client Hello, Server Hello with the certificate, then both sides derive the same key - which never crosses the wire.

Before any encrypted data can be exchanged, the client and server must agree on how to encrypt. This negotiation is called the TLS handshake. It happens before any application data (HTTP requests, API calls) is sent, and it establishes the encryption parameters for the session.

Think of it like two spies meeting for the first time. Before they can exchange secret messages, they need to agree on which code to use, verify each other's identities, and establish a shared secret key. The handshake is that agreement ritual.

The handshake in TLS 1.3 (the current version) is streamlined to a single round-trip:

Step 1: Client Hello. The client sends a message to the server containing: the TLS versions it supports, the cipher suites it supports (which encryption algorithms it can use), and a random number. In TLS 1.3, the client also speculatively sends key-share data - its half of the key exchange - to save a round trip. This is like walking into the meeting already holding up your half of the puzzle.

Step 2: Server Hello. The server responds with: the TLS version it chose, the cipher suite it selected from the client's list, its own random number, and its key-share data. The server also sends its certificate (to prove its identity) and a "Finished" message.

Step 3: Key Derivation. Both sides now have enough information to compute the shared secret - the encryption key that will protect all subsequent communication. Neither side ever sends the key over the network. Instead, they use a key exchange algorithm (like Elliptic-Curve Diffie-Hellman, or ECDHE) that allows both sides to independently compute the same shared secret from the exchanged key-share data. This is the mathematical magic of public-key cryptography - both sides arrive at the same number without ever transmitting it.

Step 4: Encrypted Communication. The handshake is complete. All subsequent data is encrypted with the derived key. HTTP requests and responses flow through the encrypted tunnel.

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: Client Hello<br/>(TLS versions, cipher suites,<br/>key share, random)

    Server->>Client: Server Hello<br/>(chosen cipher, key share,<br/>certificate, Finished)

    Note over Client,Server: Both compute shared secret<br/>independently using ECDHE.<br/>The key never crosses the wire.

    Client->>Server: Finished<br/>(encrypted with derived key)

    Note over Client,Server: Handshake complete - 1 round trip

    Client->>Server: GET /api/users (encrypted)
    Server->>Client: 200 OK {"users": [...]} (encrypted)
This sequence diagram shows the TLS 1.3 handshake. The client sends a Client Hello with supported TLS versions, cipher suites, key share data, and a random number. The server responds with its chosen cipher, its key share, its certificate, and a Finished message. Both sides independently compute the shared secret using Elliptic-Curve Diffie-Hellman. The client sends its Finished message encrypted with the derived key. All subsequent communication is encrypted. The entire handshake completes in one round trip.

TLS 1.3 vs. TLS 1.2

Interactive animation: TLS 1.2 against TLS 1.3, message by message: two round trips before any data, then just one.

TLS 1.3, finalized in 2018, is a significant improvement over TLS 1.2:

Faster handshake. TLS 1.3 completes the handshake in one round trip (1-RTT). TLS 1.2 required two round trips (2-RTT). On a connection with 100ms latency, this saves 100ms before any data can be exchanged. For a page that loads 20 resources from different servers, those saved milliseconds add up. TLS 1.3 also supports 0-RTT resumption - reconnecting to a server you have previously connected to without any handshake round trip - though 0-RTT has replay attack risks and should be used carefully.

Stronger security. TLS 1.3 removed support for insecure cipher suites and algorithms that were still allowed in TLS 1.2 (RSA key exchange, CBC mode ciphers, SHA-1). The philosophy changed from "support everything" to "support only what is known to be safe." Fewer choices means fewer opportunities for configuration mistakes.

Forward secrecy by default. TLS 1.3 requires ephemeral key exchange (ECDHE), which means every session uses a unique encryption key. Even if the server's private key is compromised in the future, past recorded traffic cannot be decrypted. This is forward secrecy - the compromise of today's keys does not compromise yesterday's conversations. TLS 1.2 allowed non-forward-secret key exchanges (static RSA), where compromising the server's key would decrypt all past traffic. This is why intelligence agencies were known to record encrypted traffic - if they later obtained the server's private key, they could decrypt everything retroactively. Forward secrecy eliminates this attack.

Important: In 2024+, TLS 1.3 should be the default for all new systems. TLS 1.2 is still acceptable for backward compatibility but should be configured to use only forward-secret cipher suites (ECDHE). TLS 1.0 and 1.1 are deprecated by all major browsers and should never be enabled. SSL v3 has known critical vulnerabilities and must never be used.

Certificates and Certificate Authorities

Interactive animation: The chain of trust: a root already in your OS signs an intermediate, which signs the site certificate.

The authentication part of TLS relies on certificates. A certificate is a digital document that says: "This public key belongs to example.com, and a trusted authority has verified this."

Here is how the trust chain works:

  1. The server operator generates a key pair - a private key (kept secret on the server, never shared with anyone) and a public key (embedded in the certificate, shared with everyone).

  2. The operator requests a certificate from a Certificate Authority (CA). The CA verifies that the operator controls the domain. This verification can be DNS-based (add a specific DNS record), file-based (place a specific file at a URL on the domain), or email-based (respond to an email sent to admin@thedomain.com).

  3. The CA signs the certificate with its own private key. The signed certificate contains: the domain name(s), the server's public key, the CA's digital signature, the validity period, and the issuing CA's identity.

  4. During the TLS handshake, the server presents this certificate. The client's browser checks: Is the certificate signed by a CA I trust? Does the domain name match the URL I navigated to? Has the certificate expired? Has it been revoked? If all checks pass, the browser trusts the connection.

The chain of trust is hierarchical. Root CAs are embedded in your browser and operating system. Root CAs sign intermediate CAs. Intermediate CAs sign server certificates. This creates a chain: server cert → intermediate CA → root CA. The browser validates the entire chain.

graph TD
    Root["Root CA<br/>(embedded in browsers/OS)"] -->|"signs"| Intermediate["Intermediate CA<br/>(issues server certs)"]
    Intermediate -->|"signs"| Leaf["Server Certificate<br/>(example.com)"]

    Browser["Browser"] -->|"trusts root"| Root
    Browser -->|"validates chain to"| Leaf

    style Root fill:#1e293b,stroke:#ef4444,color:#e2e8f0
    style Intermediate fill:#1e293b,stroke:#f59e0b,color:#e2e8f0
    style Leaf fill:#1e293b,stroke:#10b981,color:#e2e8f0
This diagram shows the certificate chain of trust. The Root CA is embedded in browsers and operating systems and is the ultimate trust anchor. The Root CA signs an Intermediate CA's certificate. The Intermediate CA signs the server's leaf certificate for example.com. When a browser connects, it validates the chain from the server cert through the intermediate back to the trusted root. If the chain is intact and all signatures are valid, the connection is trusted.

Let's Encrypt revolutionized TLS adoption by making certificates free and fully automated. Before Let's Encrypt (founded 2014, launched 2016), certificates cost money ($50-$200 per year per domain) and required manual renewal. Today, Let's Encrypt issues certificates that auto-renew every 90 days with zero human intervention. There is no excuse for any public-facing service to not use TLS.

TLS Termination

In a production architecture, TLS is typically terminated at the edge - at the load balancer, reverse proxy, or CDN - rather than at each individual application server. This is called TLS termination, and understanding why it exists is critical for system design.

Why terminate at the edge:

Performance. TLS handshakes and encryption/decryption consume CPU. Offloading this work to dedicated edge infrastructure (which often has hardware acceleration for cryptographic operations) frees application servers to focus on business logic. An application server doing TLS negotiation for every connection wastes CPU cycles that could be serving requests.

Certificate management. Certificates need to be installed, renewed, and rotated. Managing certificates on 200 application servers is an operational nightmare - one missed renewal and the service goes down with certificate errors. Managing them on 2 load balancers is straightforward.

Inspection and routing. The load balancer needs to read HTTP headers for routing decisions, rate limiting, and logging. Those headers are inside the encrypted tunnel. Terminating TLS at the load balancer decrypts the traffic, allowing the load balancer to inspect headers and route intelligently.

The trade-off: Between the load balancer and the application servers, traffic is unencrypted (plain HTTP) unless you add a second layer of encryption. In a trusted internal network (a VPC with strict security groups), this is commonly acceptable. In environments with strict compliance requirements (PCI-DSS, HIPAA), traffic must be encrypted end-to-end. Options include re-encrypting at the load balancer (TLS to backend servers) or using mutual TLS (mTLS) for all internal communication.

Mutual TLS (mTLS) extends TLS so that both sides present certificates. In standard TLS, only the server proves its identity. In mTLS, the client also presents a certificate, and the server verifies it. This is used for service-to-service authentication in microservices - every service has its own certificate, and services verify each other on every call. Service meshes like Istio and Linkerd automate mTLS between all services in a Kubernetes cluster, making it transparent to application code.

Interactive animation: Where encryption ends: at the load balancer, re-encrypted to the backends, or mutual TLS between every service.

Common Pitfalls

Mixed content. A page served over HTTPS that loads images, scripts, or stylesheets over HTTP. The browser warns users or blocks the insecure resources entirely. This can break functionality - a payment form that loads a JavaScript file over HTTP will have that script blocked by the browser, potentially making the form non-functional. Ensure all resources are loaded over HTTPS. Most modern frameworks and CDNs make this the default.

Certificate expiry. Certificates have expiration dates. When a certificate expires, browsers show a scary full-page warning and users cannot access the site. This causes immediate, visible outages that affect every user. Automated renewal with Let's Encrypt or AWS ACM eliminates this entirely. If you manage certificates manually, set monitoring alerts that fire 30 days before expiry - not the day of.

Weak cipher suites. Configuring a server to accept old, insecure cipher suites (RC4, DES, export-grade ciphers) for "backward compatibility" creates real vulnerabilities. Attackers can downgrade the connection to use the weakest supported cipher and then break it. Use the Mozilla SSL Configuration Generator to produce a secure configuration for your specific web server (Nginx, Apache, HAProxy).

Not enabling HSTS. HTTP Strict Transport Security (HSTS) tells browsers to always use HTTPS for your domain, even if the user types http://. Without HSTS, the first connection might be over HTTP, allowing an attacker to intercept it before the redirect to HTTPS happens. HSTS closes this window by instructing the browser to never attempt an HTTP connection to your domain.

Ignoring certificate transparency. Certificate Transparency (CT) logs are public, append-only logs of all issued certificates. Monitoring CT logs for your domain allows you to detect unauthorized certificate issuance - if someone fraudulently obtains a certificate for your domain from a compromised CA, the CT log reveals it. Tools like Facebook's Certificate Transparency Monitoring and services from SSLMate can automate this.

Six Takeaways

Tip:

  1. TLS provides confidentiality, integrity, and authentication for data in transit. It prevents eavesdropping, tampering, and impersonation.

  2. TLS 1.3 is the modern standard. One round-trip handshake, mandatory forward secrecy, removal of insecure algorithms. Use it for all new systems.

  3. Certificates prove server identity through a chain of trust. Root CAs are embedded in browsers. Intermediate CAs sign server certificates. Let's Encrypt makes this free and automated.

  4. TLS termination at the load balancer simplifies certificate management and offloads encryption overhead, but leaves internal traffic unencrypted unless you add mTLS or re-encryption.

  5. Mutual TLS (mTLS) authenticates both sides of a connection. Used for service-to-service communication, often automated by service meshes like Istio.

  6. Forward secrecy means past traffic stays safe even if the server's private key is compromised later. TLS 1.3 enforces this by default.

Exercises

Exercise: Warm-up: Explain the difference between TLS 1.2 and TLS 1.3 in terms of handshake latency, cipher suite security, and forward secrecy. Why should new systems default to TLS 1.3?

Design challenge: You are designing a microservices architecture with 12 services on Kubernetes. External traffic enters through an AWS ALB. How do you handle TLS? Where do you terminate? Do you encrypt internal traffic? Design the certificate management strategy, including how certificates are provisioned, renewed, and rotated.

Deep dive: Your organization must comply with PCI-DSS, which requires encryption of cardholder data in transit everywhere, including between internal services. Your current architecture terminates TLS at the load balancer and uses plain HTTP internally. Design the migration to end-to-end encryption. What are the performance implications? How do you handle certificate rotation for 12 services without downtime?

Resources

Back to System Design Fundamentals