Main issue: https://github.com/modelcontextprotocol/registry/issues/1323
Related: registry#1252 (load test / origin scaling), dns#26 (poc/registry-edge-cache)
Repos affected: modelcontextprotocol/registry, modelcontextprotocol/dns
Scope: Cloudflare as the edge/CDN layer, Let's Encrypt (via cert-manager) retained as the origin certificate authority.
Load testing against ~19k server entries found the registry's GET /v0/servers endpoint breaks down around 300 RPS: goroutine count climbs to 8k–12k, GC-driven CPU usage rises sharply, and requests start timing out waiting on Postgres or on CPU availability. Below that threshold the API is fast and DB queries are not the bottleneck — the ceiling is concurrent in-process load on the Go service itself.
/v0/servers traffic is dominated by repeated or near-repeated queries — the default listing, common searches — which makes it a good fit for a caching layer rather than a compute-scaling fix: caching reduces both origin load and client-facing latency, whereas scaling out compute only addresses the former, at ongoing cost that scales with traffic instead of being absorbed at the edge.
Today, Cloudflare is authoritative for DNS but is not in the request path. There is exactly one TLS session, client straight through to the origin.
sequenceDiagram
participant C as Client
participant CF as Cloudflare (DNS only)
participant LB as GCP passthrough NLB
participant N as ingress-nginx
participant P as registry pod
C->>CF: DNS lookup registry.modelcontextprotocol.io
CF-->>C: CNAME chain resolves to real GCP LB IP
C->>LB: TCP + TLS handshake, packets forwarded not terminated
LB->>N: same connection, unmodified
N->>P: plain HTTP, TLS already terminated here
Note over C,N: One TLS session, client all the way to nginx.<br/>Real client IP arrives intact, no forwarded-header logic needed.
Two details matter for what follows:
Service of type: LoadBalancer. It forwards packets by a 5-tuple hash without ever assembling the TCP stream itself, so the client's real handshake reaches all the way to the ingress-nginx pod. Combined with externalTrafficPolicy: Local (avoids an internal SNAT hop), this is why nginx can read the real client IP directly off the TCP connection today, with use-forwarded-headers: false.ClusterIssuer: letsencrypt-prod, ACME HTTP-01 challenge routed through the same ingress). The certificate covers both {env}.registry.modelcontextprotocol.io and, for prod, the root registry.modelcontextprotocol.io as SANs on a single cert.No Cache-Control headers are set anywhere in the application, and no firewall rule restricts who can reach the origin load balancer directly.
Introducing Cloudflare as a proxy changes the single-hop picture above into two independent hops, each with its own TLS session:
sequenceDiagram
participant C as Client
participant CF as Cloudflare edge
participant LB as GCP passthrough NLB
participant N as ingress-nginx
participant P as registry pod
C->>CF: TLS leg 1, Cloudflare's own edge cert
alt cache HIT, GET /v0/servers within 60s
CF-->>C: served directly from edge, origin untouched
else cache MISS
CF->>LB: TLS leg 2, new connection, existing Lets Encrypt cert
LB->>N: forwarded unmodified, same as today
N->>P: plain HTTP
P-->>N: response
N-->>CF: response
CF-->>C: response, now cached for next request
end
The sections below explain each decision behind this shape, and why.
Flip the registry CNAME's proxied flag from false to true (dns#26 also generalizes dns.ts to read this per-record instead of hardcoding it, since nothing was ever proxied before). The underlying prod.registry.modelcontextprotocol.io A record is deliberately left un-proxied.
Why leave prod.registry un-proxied: it preserves a direct path to origin for debugging and health checks without any extra tooling — you can still curl/browse it directly, bypassing the edge entirely, at zero implementation cost. The alternative (proxying everything) would mean every debugging session has to first reason about which of the two TLS legs, or which cache state, might be responsible for what's being observed — a real cost in operational clarity for no corresponding benefit.