Pricing - Pricing & Costs
Cloudflare Pricing Calculator 2026: Forecast Your Real Traffic Costs
Cloudflare Pricing Calculator 2026: Real Cost Model The single largest line item on most Cloudflare bills is not ...
A WebSocket upgrade turns a CDN edge into a bidirectional tunnel. The moment your origin answers the RFC 6455 handshake with 101 Switching Protocols, caching, compression, image transforms, range requests and request collapsing stop applying to that connection for its entire life. WebSocket CDN support is therefore judged almost entirely by handshake behavior and timeout policy: idle timeouts documented in the 60–100 second range as of 2026, per-connection memory of roughly 300 KB once permessage-deflate negotiates context takeover, and a 40 ms delayed-ACK stall waiting for anyone who forgot to disable Nagle.
What does survive is still worth having: TLS termination close to the user, anycast path selection, a warm pooled leg to origin, header rewriting and authentication at handshake time, and rate limiting on the handshake request. The mistake is assuming the rest of the feature set follows the connection through the upgrade. It does not, and the failure modes show up at 10,000 concurrent connections, not at 100.

The handshake is an ordinary HTTP/1.1 request, so every edge feature that operates on requests gets exactly one shot at it. After the 101, the edge is copying opaque frames in both directions and has no protocol-level idea what is inside them.
| CDN capability | Survives the upgrade? | Practical consequence |
|---|---|---|
| TLS termination at the edge | Yes | Handshake RTT savings still apply; this is the main real win |
| Anycast routing and backbone transit | Yes | Fewer lossy hops on the long leg, steadier frame jitter |
| Cache (any tier, including shield) | No | Hit ratio on this hostname is structurally 0%; egress is 100% billable |
| Edge compression (gzip, Brotli) | No | Frame compression must be negotiated per RFC 7692 instead |
| Request collapsing, retries, failover on error | Handshake only | Mid-stream origin failure becomes a client-visible disconnect |
| Access rules, token auth, header rewrite | Handshake only | Authorization is evaluated once, then the tunnel is trusted for hours |
| Per-request access logs | No | One log line per connection, not per message; p99 latency is invisible |
The single most important conclusion: a CDN in front of WebSockets buys you network path and TLS, and nothing else, so price and evaluate that hostname purely as transit.
RFC 8441 defines WebSockets over HTTP/2 using extended CONNECT, and RFC 9220 extends the same mechanism to HTTP/3. Browser and edge support exists but is uneven, and most edges still downgrade the upgrade to HTTP/1.1 on the origin leg. That means one TCP connection per WebSocket to origin, no stream multiplexing, and no head-of-line-blocking benefit from QUIC on the part of the path where you would most want it.
There is a second-order effect worth noting. Under HTTP/1.1 tunnelling, each client connection consumes an origin-side socket for its full lifetime, so a 250,000-connection fleet needs the ephemeral port and file-descriptor math done per origin IP tuple, not per request rate.
Two independent buffers hurt. The first is proxy buffering: if any layer accumulates bytes before forwarding, small frames get coalesced and delivery becomes bursty. The second is TCP itself. Nagle's algorithm plus Linux delayed ACK, whose minimum timer is 40 ms, will hold a small write until an ACK arrives, and that penalty applies at both the edge-to-origin and edge-to-client hop.
For a 20 Hz game state stream with 50 ms between frames, a 40 ms stall is close to a full tick. Measure it as the gap between application-level ping RTT and raw TCP RTT; if application RTT p99 exceeds TCP RTT p99 by 30–45 ms with no queueing on the origin, you are looking at a buffering problem, not a capacity problem.
Per RFC 7692, permessage-deflate with context takeover keeps a live zlib deflate context per connection per direction, which at the default 15 window bits costs roughly 300 KB of memory for the compressor and about 44 KB for the decompressor. At 100,000 concurrent WebSocket connections that is on the order of 30 GB of RAM before a single message is queued, which is why many real-time platforms in 2026 either negotiate no_context_takeover, cap window bits at 10–12, or disable the extension entirely for small-payload traffic.
The trade-off is honest: context takeover typically compresses repetitive JSON payloads 60–85%, and switching it off raises egress on exactly the traffic that gets no cache offload. Small frames under about 200 bytes rarely compress usefully anyway, so a per-message size threshold usually beats a global on/off decision.
Every edge enforces an idle timeout on a tunnelled connection. Vendor-published values commonly sit in the 60–100 second band as of 2026, which means application-level ping frames every 20–30 seconds are mandatory, not optional. Relying on TCP keepalive alone fails because the default first probe on Linux fires after 7,200 seconds.
The bigger risk is correlated disconnection. An edge configuration deploy, a node drain, or a routing change drops every connection it holds at once. If 100,000 clients reconnect with a fixed 1-second retry, the origin sees 100,000 handshakes per second, each requiring authentication and state rehydration. Full jitter over a 5–30 second window turns that into roughly 4,000 handshakes per second, and a token bucket on the handshake route caps the worst case.
Observability is the gap nobody plans for. CDN logs give you connection open, close, duration and bytes; they cannot give you message latency, frame counts, or which shard was slow. That instrumentation has to live in your application, emitted as histograms keyed by edge node identifier so you can attribute regressions to a region.
Split the planes. Put cacheable assets, API reads and manifests on the hostname where cache, compression and collapsing earn their keep, and put the real-time tunnel on a dedicated hostname with caching off, edge compression off, buffering off and long read timeouts. Separate hostnames let you tune each one honestly instead of compromising both.
upstream ws_upstream {
server 10.0.0.11:8080 max_fails=2 fail_timeout=5s;
server 10.0.0.12:8080 max_fails=2 fail_timeout=5s;
keepalive 64;
}
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}
server {
listen 443 ssl;
server_name REALTIME_HOSTNAME;
location /ws/ {
proxy_pass http://ws_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Edge-Node EDGE_NODE_ID;
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
tcp_nodelay on;
}
}
Substitute REALTIME_HOSTNAME with the dedicated real-time hostname and EDGE_NODE_ID with whatever node identifier your edge injects, so application metrics can be grouped by node. If a provider cannot disable buffering and compression per hostname, that is a hard limit on how well real-time traffic behaves through it; flexible per-property configuration is one of the few things worth checking against the CDN feature set for real-time and WebSocket traffic before you commit a hostname to it.
It fits when clients are globally distributed and the long haul is lossy: TLS at the edge plus managed transit measurably steadies frame jitter for chat, collaboration, dashboards and presence systems. It also fits when you want handshake-time authentication and rate limiting enforced off your origin.
It fits poorly when connections are regional and already low-latency, when you need mid-stream failover, or when message volume is high and compression choices are dictated by the edge rather than by you. Sub-20 ms interactive workloads and anything needing unreliable datagrams are better served by a direct path or WebTransport over HTTP/3.
No. After the 101 Switching Protocols response, the edge treats the connection as an opaque tunnel, so caching, edge compression and request collapsing no longer apply. Cache hit ratio on a WebSocket hostname is structurally 0%. Frame-level compression must be negotiated between client and origin using permessage-deflate as defined in RFC 7692.
Most edges do not cap total duration but do enforce an idle timeout, commonly in the 60–100 second range as of 2026, plus occasional forced closes during configuration deploys or node drains. Send application-level ping frames every 20–30 seconds and treat any disconnect as expected, with jittered reconnect logic on the client.
Bypass makes sense when clients are concentrated in one or two regions and baseline RTT is already under 30 ms, since the edge adds a hop without offering cache offload. Keep the CDN path when users are global, packet loss on transit is material, or you want handshake authentication and rate limiting applied before origin.
It helps on the client leg by removing one TCP and TLS handshake per connection, using extended CONNECT per RFC 8441 and RFC 9220. The benefit is limited because most edges still downgrade to HTTP/1.1 toward origin, so origin socket counts and head-of-line behavior on that leg stay unchanged.
Pick your busiest real-time hostname and emit two histograms for seven days: application ping RTT and TCP RTT from the same process, both tagged with the edge node identifier and the client region. Then plot idle-close events against your ping interval. Most teams discover one of three things — a 40 ms buffering tax, a ping interval sitting uncomfortably close to the edge idle timeout, or reconnect bursts clustered within a two-second window after a deploy. All three are cheap to fix once you can see them, and none of them are visible in CDN logs alone.
Pricing - Pricing & Costs
Cloudflare Pricing Calculator 2026: Real Cost Model The single largest line item on most Cloudflare bills is not ...
Pricing - Pricing & Costs
10 Best CloudFront Alternatives in 2026 (Cheaper, Faster) One 40 GB patch, 500,000 players in the first 24 hours, 20 PB ...