Frontend Labs
⋯

Day 55 — SNI Routing

Learning Objectives

  • Route TLS connections by hostname before decrypting them (SNI routing)
  • Serve multiple domains from one IP/port with different backends
  • Understand how CDNs and multi-tenant proxies work

Key Concepts

SNI (Server Name Indication):
  Extension to TLS ClientHello — client announces which hostname it's connecting to
  Server uses this to select the right certificate BEFORE the TLS handshake completes
  Without SNI: one IP can only serve one TLS certificate

How CDNs use SNI:
  1. All traffic hits the same IP (anycast)
  2. TLS ClientHello contains SNI (e.g., "acme.corp.com")
  3. CDN routes to the right customer's origin based on SNI
  4. CDN terminates TLS with the customer's cert

Exercise: SNI Router

package main

import (
    "crypto/tls"
    "fmt"
    "net"
    "strings"
)

type SNIRouter struct {
    // map from hostname → backend address
    routes   map[string]string
    reloader *CertReloader // from Day 54
}

func NewSNIRouter() *SNIRouter {
    return &SNIRouter{routes: make(map[string]string)}
}

func (r *SNIRouter) AddRoute(hostname, backendAddr string) {
    r.routes[hostname] = backendAddr
}

// GetCertificate picks the right cert based on SNI hostname
func (r *SNIRouter) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
    // In production: per-hostname cert store
    // Simplified: one cert that covers all hostnames via SANs
    return r.reloader.GetCertificate(hello)
}

// GetConfigForClient is called during TLS handshake — access to SNI before conn is established
func (r *SNIRouter) GetConfigForClient(hello *tls.ClientHelloInfo) (*tls.Config, error) {
    fmt.Printf("[SNI] hostname=%q from %s\n", hello.ServerName, hello.Conn.RemoteAddr())

    // Return per-hostname config (cert, min TLS version, etc.)
    return &tls.Config{
        GetCertificate: r.GetCertificate,
        MinVersion:     tls.VersionTLS12,
    }, nil
}

// Route returns the backend address for a given hostname
func (r *SNIRouter) Route(hostname string) (string, bool) {
    // Exact match
    if backend, ok := r.routes[hostname]; ok {
        return backend, true
    }
    // Wildcard match (*.example.com)
    parts := strings.SplitN(hostname, ".", 2)
    if len(parts) == 2 {
        wildcard := "*." + parts[1]
        if backend, ok := r.routes[wildcard]; ok {
            return backend, true
        }
    }
    return "", false
}

func handleSNIConn(conn net.Conn, router *SNIRouter) {
    defer conn.Close()

    // At this point, TLS handshake has completed
    // We know the SNI hostname from GetConfigForClient
    tlsConn, ok := conn.(*tls.Conn)
    if !ok { return }

    sni := tlsConn.ConnectionState().ServerName
    backend, found := router.Route(sni)
    if !found {
        fmt.Printf("[SNI] no route for %q\n", sni)
        return
    }

    // Connect to the appropriate backend and proxy
    upstream, err := net.Dial("tcp", backend)
    if err != nil {
        fmt.Printf("[SNI] backend %s unavailable: %v\n", backend, err)
        return
    }
    defer upstream.Close()

    fmt.Printf("[SNI] routing %q → %s\n", sni, backend)
    var wg sync.WaitGroup
    wg.Add(2)
    go func() { defer wg.Done(); io.Copy(upstream, conn) }()
    go func() { defer wg.Done(); io.Copy(conn, upstream) }()
    wg.Wait()
}

func main() {
    router := NewSNIRouter()
    router.AddRoute("api.example.com", "127.0.0.1:8081")
    router.AddRoute("admin.example.com", "127.0.0.1:8082")
    router.AddRoute("*.staging.example.com", "127.0.0.1:8083")

    // Reloader from Day 54
    reloader, _ := NewCertReloader("server.crt", "server.key")
    router.reloader = reloader

    tlsConfig := &tls.Config{
        GetConfigForClient: router.GetConfigForClient,
    }

    ln, err := tls.Listen("tcp", ":443", tlsConfig)
    if err != nil { fmt.Println(err); return }
    defer ln.Close()

    fmt.Println("SNI router on :443")
    for {
        conn, err := ln.Accept()
        if err != nil { return }
        go handleSNIConn(conn, router)
    }
}

Test:

# Connect with specific hostname (SNI)
curl --cacert ca.crt --resolve api.example.com:443:127.0.0.1 https://api.example.com/
curl --cacert ca.crt --resolve admin.example.com:443:127.0.0.1 https://admin.example.com/

Real-World Context

Cloudflare, Fastly, and every multi-tenant CDN uses SNI routing. When your company hosts multiple products on the same IP, SNI routing lets each one have its own TLS cert and backend. This is also how nginx ssl_server_name and AWS ALB host-based routing work.


Generated from week8.md · 8 Oct 2026 16:36