QUIC: The Protocol That Breaks Your Site Without Warning

👁6views
QUIC: The Protocol That Breaks Your Site Without Warning

QUIC breaks sites silently because it runs over UDP, bypassing firewalls, proxies, and middleboxes that only inspect TCP traffic. When those network devices drop or mishandle UDP packets, connections fail without clear error messages. Diagnosing it requires checking whether UDP port 443 is blocked and testing with QUIC explicitly disabled in your server or CDN configuration.

CloudScale AI SEO - Article Summary
  • 1.
    What it is
    QUIC protocol breaks sites silently by replacing TCP entirely — learn exactly how head-of-line blocking, handshake latency, and connection immobility cause hidden performance failures, and how to diagnose and fix them.
  • 2.
    Why it matters
    Understanding QUIC's failure modes prevents unexpected downtime and throughput collapse — at just 0.5% packet loss, TCP head-of-line blocking creates a 575x latency penalty that HTTP/3 deployments can inherit if misconfigured.
  • 3.
    Key takeaway
    Deploy QUIC correctly or it will silently degrade performance worse than HTTP/1.1 — diagnose connection immobility, 0-RTT misconfigurations, and packet-loss sensitivity before going live.

Why It Exists, What It Gets Wrong, and How to Diagnose and Fix It

Andrew Baker · andrewbaker.ninja · 2 May 2026

1. Why TCP Was a Problem

QUIC is a complete replacement for the TCP transport layer. To understand why that replacement was necessary, you need to understand what TCP does internally and where its design assumptions broke down under the load patterns of the modern web. TCP is a reliable, ordered byte stream protocol implemented almost entirely inside the operating system kernel. Its designers in the early 1970s optimised for a world of relatively few, long-lived connections between known hosts on a trusted academic network. The web of 2010 onwards, with its billions of connections, short-lived sessions, lossy mobile networks, multiplexed requests, and TLS everywhere, strained TCP in three fundamental ways.

1.1 Head-of-Line Blocking: The Technical Mechanism

TCP delivers data as a single ordered byte stream. Every byte has a sequence number, and the receiver maintains a receive buffer that passes data to the application only in order: the application cannot read byte 500 until bytes 0 through 499 have all arrived. This guarantee is fundamental to how TCP works.

When a packet is lost, the receiver has a gap in its sequence space. It sends acknowledgements (ACKs) for everything it has received but marks the gap implicitly by not acknowledging the missing sequence range. The sender detects this through either a timeout or three duplicate ACKs indicating out-of-order arrival, and retransmits the missing segment. Only after the retransmission arrives and fills the gap can the receiver advance its ACK pointer and let the application consume the data.

This was acceptable for HTTP/1.1, which used one request per TCP connection. Each connection had its own loss-recovery state, and a dropped packet on one connection only stalled that one request. HTTP/2 changed this architecture fundamentally: it multiplexes multiple logical streams over a single TCP connection, so a file download, an API call, a CSS fetch, and a font load can all be in flight simultaneously over one connection. But that single TCP connection still enforces strict byte ordering. A dropped packet on any of those streams causes the TCP receive buffer to stall for all of them, regardless of which stream the dropped packet belonged to.

The numbers quantify exactly how bad this is. At 0.5% packet loss with a 100ms round-trip time, message blocking time is 230ms with ordered TCP delivery versus 0.4ms without head-of-line blocking, a 575x difference. [1] The blocking is not academic: a single lost packet can freeze 127 in-flight video chunks simultaneously on a typical TCP connection, and head-of-line blocking alone constrains maximum effective throughput to approximately 58.6% of physical link capacity under uniform random traffic loads. [1] [3]

The cruel irony of HTTP/2 deployment is that its multiplexing feature, marketed as a key performance improvement over HTTP/1.1, made things worse on lossy networks. Under 2% packet loss conditions, HTTP/1.1 users with multiple parallel TCP connections can actually outperform HTTP/2 users because distributing requests across several connections distributes the packet-loss risk, so no single dropped packet blocks everything. [2]

1.2 Handshake Latency: Counted in Round-Trips

Every TCP connection begins with a three-way handshake where the client sends SYN, the server responds with SYN-ACK, and the client sends ACK. This costs one round-trip of dead time before a single byte of application data can flow, which on a 200ms intercontinental connection means 200ms of nothing happening.

TLS 1.2 added two further round-trips on top. The TLS ClientHello and ServerHello exchange cipher suites and certificates over two message pairs before the encrypted channel exists, meaning a fresh HTTPS connection under TLS 1.2 cost 1 RTT for the TCP handshake, 2 RTTs for TLS 1.2 negotiation, and 3 RTTs total, or 600ms on a 200ms intercontinental link, before the first byte of HTML arrived.

TLS 1.3 reduced this to one TLS round-trip by redesigning the handshake to send the client’s key material speculatively in the first message, allowing the server to derive the session keys and start encrypting in its first reply. This is a genuine improvement, but the TCP handshake RTT remains mandatory, so TLS 1.3 over TCP still costs 2 RTTs for new connections and 1 RTT for resumed sessions.

QUIC collapses the TCP and TLS handshakes into one protocol. A fresh QUIC connection completes in 1 RTT, and a resumed QUIC connection to a known server can send application data in the very first packet (0-RTT), eliminating handshake latency entirely for returning visitors. On a 150ms link, 0-RTT represents 150ms of latency simply removed.

1.3 Connection Immobility: The Four-Tuple Problem

TCP identifies a connection by a four-tuple of source IP, source port, destination IP, and destination port. This tuple is the connection: every packet in a TCP session carries all four values and the OS kernel uses them to route incoming packets to the right socket in the receive buffer.

Mobile networks break this assumption repeatedly. When a phone moves from WiFi to LTE, its source IP address changes, the four-tuple changes, and the TCP kernel state on the server is keyed to the old tuple which is now dead. Every in-flight request aborts and must restart from zero, producing a reload or an error for the user. On a train journey this happens multiple times per minute in urban areas with overlapping cell coverage. NAT rebinding, where a carrier-grade NAT box reassigns the port mapping for an idle connection, produces the same failure silently because the server cannot distinguish a rebind from a new connection and the socket times out.

2. What QUIC Is

QUIC was built by Google in 2012, standardised by the IETF as RFC 9000 in May 2021, and forms the transport layer for HTTP/3 (RFC 9114). It addresses every TCP weakness above by running over UDP and reimplementing reliability, flow control, and encryption from scratch.

2.1 UDP as the Foundation: Why It Is Not a Step Backwards

UDP (User Datagram Protocol) is the other major transport protocol in the IP stack alongside TCP. Where TCP is a reliable ordered stream, UDP is a connectionless datagram protocol that adds only source port, destination port, length, and checksum to IP packets. There is no connection handshake, no sequence numbering, no acknowledgement, no retransmission, no flow control, and no congestion control: UDP simply sends a packet and moves on.

This sounds like a regression, but in practice it gives QUIC several critical advantages. The most important is a blank canvas: QUIC can implement exactly the semantics HTTP needs without inheriting TCP’s legacy behaviour, whose reliability semantics are so deeply ingrained that they cannot be changed without breaking the kernel ABI and every piece of networking equipment on the planet. UDP has also benefited from decades of kernel optimisation, and the UDP receive path on a Linux kernel is extremely efficient, so QUIC piggybacks on this efficiency for its datagram delivery. Finally, TCP’s behaviour is so well-known that countless routers, firewalls, and transparent proxies have hardcoded assumptions about it, and attempts to extend TCP have repeatedly failed because middleboxes break when TCP looks unfamiliar; UDP is treated as opaque payload, giving QUIC room to evolve.

The cost is that QUIC must implement its own reliability, ordering, and congestion control in userspace, inside the browser or server process rather than in the OS kernel. This is where QUIC’s CPU overhead comes from, as detailed in section 4.3.

2.2 Connection IDs: Solving the Four-Tuple Problem

Instead of identifying a connection by its network four-tuple, QUIC assigns each connection a Connection ID (CID), a random opaque token negotiated during the handshake. Both endpoints store the CID and use it to look up connection state regardless of the underlying network addresses.

When a client’s IP address changes due to mobile handoff, VPN reconnect, or NAT rebind, it sends a NEW_CONNECTION_ID frame to the server on the new network path. The server matches the CID, migrates the connection, and continues without interruption, with in-flight streams simply continuing. A user loading a slow page while walking between WiFi and cellular coverage will complete the load instead of seeing a reload, and a user streaming video on a train will not experience rebuffer at cell handoffs.

2.3 Integrated TLS 1.3: No Unencrypted QUIC Exists

TLS is not layered on top of QUIC the way it sits on top of TCP. TLS 1.3 key material and certificates are exchanged inside the QUIC handshake itself, and there is no QUIC cleartext mode. RFC 9000 is explicit: the protocol was designed with encryption as a fundamental property, not an add-on.

This integration serves two purposes. The first is security, as every QUIC connection is authenticated and encrypted from the first byte. The second is protocol ossification prevention: TCP has been unable to deploy meaningful extensions for decades because middleboxes assume fixed TCP semantics, so QUIC encrypts its headers to ensure that routers and firewalls see opaque encrypted data and cannot make structural assumptions about it. RFC 9000 explicitly cites this as a design goal. [10]

2.4 Stream-Level Independence: Fixing HTTP/2’s Core Problem

HTTP/2 multiplexes streams over one TCP connection and immediately encounters TCP head-of-line blocking. QUIC’s answer is to track loss recovery state per stream rather than per connection, so a dropped packet on stream 3 triggers retransmission for stream 3 only while streams 1, 2, and 4 continue receiving data without interruption.

Technically, QUIC still uses an ordered byte stream within each individual stream, but loss on one stream does not block the connection-level receive buffer. The two concerns, intra-stream ordering and cross-stream independence, are kept strictly separate at the protocol level.

2.5 The alt-svc Upgrade Mechanism

Servers cannot force a client to use QUIC on the first connection because the client does not know QUIC is available until it receives a response. The upgrade path uses the alt-svc (Alternative Services) HTTP header: after an HTTP/2 connection succeeds, the server includes:

alt-svc: h3=":443"; ma=86400

This advertisement tells the browser that it supports HTTP/3 on port 443 and that the browser should cache this fact for 86,400 seconds. On the next request to that domain, the browser races a QUIC/UDP connection against a TCP/TLS connection in a variant of the Happy Eyeballs algorithm and uses whichever connects first. Once QUIC is known to work, it wins this race consistently because its 1-RTT or 0-RTT handshake is faster than TCP + TLS.

The ma (max-age) value is the duration for which the browser caches the QUIC preference, and the default of 86,400 seconds (24 hours) is the source of the most damaging failure mode described in section 4.2.

3. The Real Benefits, With Numbers

3.1 Google Scale: The Landmark SIGCOMM Paper

Google’s original QUIC deployment, documented in the ACM SIGCOMM 2017 paper by Langley et al., provided the first rigorous production data on QUIC performance. At the time, Google was routing approximately 7% of all internet traffic through QUIC internally across Search, YouTube, and Gmail. The measured results were an 8% reduction in search page latency for desktop users, 3.5% for mobile users, 18% reduction in video rebuffer time for desktop YouTube users, and 15.3% for mobile YouTube users. [4] These percentage improvements are statistically meaningful at Google’s query volume: an 8% latency improvement across billions of daily searches translates to conversion rate and engagement improvements that justify the engineering cost independently.

3.2 CDN Data: Akamai and Cloudflare

Akamai’s production deployment data skews toward high-latency and high-loss networks, which are exactly where QUIC’s advantages are largest, producing a 30% average reduction in mobile latency with HTTP/3 versus HTTP/2, a 55% improvement in high packet-loss scenarios consistent with the theoretical head-of-line blocking analysis, and 25% faster downloads on intercontinental connections between the US East Coast and Germany. [5]

Cloudflare’s measurements at the 95th percentile show 116ms faster connection establishment with HTTP/3 enabled: the median improvement is smaller while the tail improvement is large, which matters because the slowest users are typically on mobile, on congested networks, or at geographic distance, and those are the users you most want to help. [6]

3.3 Connection Establishment: 0-RTT Quantified

The headline latency claim is 50% faster connection setup for new connections: 1-RTT for QUIC versus 2-RTT for TCP + TLS 1.3. On a 150ms RTT intercontinental connection, this is 150ms removed from every new connection.

For resumed connections, 0-RTT removes the handshake entirely. The client sends application data in the first packet using a session ticket from the previous visit, and the server can process it immediately, subject to replay-attack mitigations: non-idempotent operations such as POST or payment flows should not rely on 0-RTT data because an attacker could theoretically replay the first packet, while read-only GET requests are generally safe.

The HTTP/3 Extensible Priorities extension, specified in RFC 9218, allows browsers to tell servers which resources to deliver first and adds up to 37% faster webpage loading on top of the base QUIC latency gains.

3.4 Adoption: Where HTTP/3 Stands in 2025

34% of the top 10 million websites now support HTTP/3 as of September 2024, 21% of all requests to Cloudflare in 2025 used HTTP/3, 95% of major browsers support it, and Georgia led geographic adoption at 38% with 15 countries exceeding 33%. [6] [9] HTTP/3 is no longer experimental: it is the default on many CDNs and represents a significant fraction of global web traffic.

4. The Real Problems

4.1 UDP Is Not TCP on the Network

Twenty years of enterprise networking assumes TCP. Corporate firewalls, hotel captive portals, airline WiFi gateways, and mobile carrier proxies are built around TCP inspection, and UDP on port 443 triggers different handling in almost every piece of network infrastructure built before 2020.

The failure modes are specific and worth understanding individually. Complete UDP/443 blocks affect an estimated 3-5% of public networks, with corporate and enterprise rates substantially higher and largely unmeasured; enterprise VPNs frequently block QUIC deliberately to force traffic through HTTPS inspection gateways as a policy choice rather than a misconfiguration. Some ISPs throttle non-TCP traffic or treat high-rate UDP flows as potential DDoS activity. The worst failure mode is silent drops, where UDP packets are discarded with no RST, no ICMP unreachable, and no response of any kind, leaving QUIC waiting for an acknowledgement that will never arrive. [11]

The critical asymmetry versus TCP failure is that when TCP is blocked, the connection is refused with a RST or times out in a way browsers handle predictably, falling back to HTTP/2 or displaying a clear error. When UDP is silently dropped, QUIC hangs waiting and the browser’s Happy Eyeballs fallback logic is imperfect. On some mobile browser versions a QUIC timeout produces ERR_CONNECTION_FAILED rather than a transparent TCP fallback, and the user sees a broken page.

4.2 The alt-svc Cache Trap

This is the failure mode that catches operators off guard, and it is the one that affected andrewbaker.ninja directly.

When a QUIC-capable server sends alt-svc: h3=":443"; ma=86400, the browser caches this preference for 24 hours. During those 86,400 seconds, the browser prefers QUIC for every request to that domain. If QUIC then starts failing, because the CDN reconfigures or the user moves to a UDP-blocking network or an ISP begins rate-limiting, the browser does not immediately fall back. It tries QUIC, waits, times out, and depending on browser version either falls back gracefully or shows ERR_CONNECTION_FAILED.

Clearing the browser HTTP cache does not clear the QUIC state. The alt-svc cache is stored in a separate transport-layer data store, and on desktop Chrome the only way to clear it is to open chrome://net-internals/#quic and click “Clear QUIC state”. On mobile Chrome there is no direct UI for this; you must clear all browsing data including cookies and cached images, and even then the cache repopulates on the very next successful request if the server still serves the alt-svc header.

4.3 CPU and Kernel Call Overhead: The Technical Detail

This is the most technically significant problem for self-hosted or low-resource deployments, and it deserves a detailed explanation because the surface-level summary that QUIC uses more CPU dramatically understates the difference.

TCP is implemented almost entirely inside the Linux kernel. When a TCP packet arrives, the kernel’s network stack receives the Ethernet frame in the NIC driver, runs IP processing including checksum and routing, delivers to the TCP socket layer which handles sequence numbers, ACKs, flow control, and congestion control entirely in kernelspace, copies data to the socket receive buffer, and then wakes the application via a single epoll/select event. TLS decryption happens once the application reads from the socket, typically via OpenSSL using AES-NI hardware acceleration. The kernel handles all retransmission, ordering, and flow control without any application involvement.

QUIC runs in userspace, and every QUIC datagram requires a substantially larger set of operations. Each arriving packet demands a UDP recvmsg() system call, which is a kernel context switch per datagram rather than per batch. The application must then parse packet headers to demultiplex the datagram to the correct stream, generate and send ACKs via sendmsg() system calls, manage PTO timers and congestion window state in userspace for loss detection and retransmission, perform TLS 1.3 encrypt/decrypt operations per record (with QUIC using smaller records than TLS-over-TCP, increasing the total number of crypto operations), and copy data from application buffers to kernel socket buffers via UDP sendmsg() for every datagram sent.

A 2023 arxiv study measured this directly and found that QUIC requires 231,000 kernel packet processing calls per download compared to only 15,000 for HTTP/2, a 15x difference. The study also found up to 45.2% data rate reduction compared to TCP+TLS+HTTP/2 when the server CPU becomes the constraint, with the performance gap widening as bandwidth increases, so that at 1 Gbps QUIC can underperform HTTP/2 on the same hardware. [12] Mobile devices are hit harder still: APNIC research shows mobile processors spend 58% of time in an “Application Limited” state waiting for receiver-side QUIC processing, versus 7% on desktop, because mobile CPUs are significantly slower at the encryption and ACK processing that TCP handles in hardware-optimised kernelspace but QUIC must perform in userspace. [8]

The practical implication is that for a Raspberry Pi, a t3.micro EC2 instance, or any low-spec VPS, enabling HTTP/3 under concurrent load is a measurable and non-trivial resource cost. HTTP/2 is the correct choice for resource-constrained servers.

4.4 Debugging Difficulty

TCP traffic captured with Wireshark is readable without keys: the sequence numbers, ACKs, flags, and window sizes are all in plaintext. QUIC encrypts most of its packet headers, exposing only a tiny version field and connection ID prefix. To decrypt QUIC traffic in Wireshark you need the TLS session keys exported via SSLKEYLOGFILE, which requires setting SSLKEYLOGFILE=/path/to/keys.log in the environment of the browser or server process, capturing traffic simultaneously with Wireshark, and loading the keylog file in Wireshark’s TLS preferences. In production this is impractical because you cannot set SSLKEYLOGFILE on every user’s browser, so production debugging of QUIC issues must rely on server-side logs, browser diagnostics via chrome://net-internals, and indirect signals like error rates and latency distributions.

5. TCP versus QUIC: Side-by-Side

AttributeTCP / HTTP/2QUIC / HTTP/3
Transport baseTCP (kernel)UDP (kernel fast-path, QUIC logic in userspace)
ReliabilityKernel-managed, in-order byte streamQUIC-managed per stream; out-of-order OK across streams
EncryptionExternal (TLS above TCP)Integrated TLS 1.3; no unencrypted QUIC
Handshake (new conn)1-RTT TCP + 1-RTT TLS 1.3 = 2 RTTs1-RTT combined QUIC+TLS
Handshake (resumed)1-RTT TCP + 0-RTT TLS = 1 RTT0-RTT (data in first packet, replay-attack caveats)
Head-of-line blockingAll streams stall on single lossOnly affected stream stalls
Connection identity4-tuple (src IP, src port, dst IP, dst port)Opaque Connection ID (survives IP change)
Mobile handoffConnection aborts, must restartConnection migrates via NEW_CONNECTION_ID frame
Kernel calls per download~15,000~231,000 (15x higher)
DebuggingWireshark without keysRequires SSLKEYLOGFILE; most headers encrypted
Firewall compatibilityUniversal, 20+ years of TCP rules3-5% public networks block UDP/443
Low-spec server performanceExcellent (hardware-accelerated in kernel)CPU-intensive; measurable overhead on Pi / VPS

6. A Real Incident: ERR_CONNECTION_FAILED on LTE

The following is a step-by-step account of the QUIC incident on andrewbaker.ninja, which runs on a Raspberry Pi 5 behind a Cloudflare Tunnel.

6.1 Symptoms

The presenting symptoms were users on mobile LTE receiving ERR_CONNECTION_FAILED intermittently, with a consistent pattern where a refresh always succeeded while a fresh navigation to the same page in a new tab would fail again. The problem occurred on both WiFi and LTE simultaneously. The Cloudflare Tunnel reported 4/4 HA connections healthy, the Pi-side probe on curl localhost:8082 returned HTTP 200 every time, an external probe from the Pi via curl https://andrewbaker.ninja passed, and no ntfy alerts fired from the cloudflared watchdog.

6.2 Why the Monitoring Did Not Catch It

The Pi-side watchdog probed localhost:8082 directly, completely bypassing Cloudflare. The external probe from the Pi went through the CF tunnel but the Pi was in Johannesburg, connecting to a JNB point of presence over a low-loss local connection. The Pi’s QUIC connection to the Cloudflare edge was fine while the user’s phone’s QUIC connection to the same edge was not, because the probe path and the user path were different networks measuring different things.

6.3 Root Cause

Cloudflare was serving alt-svc: h3=":443"; ma=86400. The user’s LTE carrier was silently dropping UDP/443 packets. Chrome on the phone had cached the QUIC preference and kept trying it on each new navigation. TCP would have worked, but Chrome never tried it because the alt-svc cache instructed it to use HTTP/3.

6.4 Why Clearing the Cache Did Not Fix It

Clearing browser data removed the cached alt-svc entry, but the site re-served the alt-svc header on the very next successful request, which succeeded because Chrome, having just failed QUIC, tried TCP for the immediate reload. Chrome cached the preference again within milliseconds, so the next new-tab navigation tried QUIC again and failed.

6.5 The Actual Fix

Disabling HTTP/3 at the Cloudflare zone level via the API, rather than the dashboard toggle which was not persisting correctly, caused the alt-svc header to stop appearing in responses. Once that happened, all browser connections used HTTP/2 and there were no further failures.

curl -X PATCH \
  "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/http3" \
  -H "X-Auth-Email: ${CF_EMAIL}" \
  -H "X-Auth-Key: ${CF_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"value":"off"}'

6.6 The “Refresh Always Works” Pattern: Explained

The pattern is nearly diagnostic for a QUIC cache problem and worth understanding precisely. On the first navigation, Chrome checks the alt-svc cache, finds the QUIC preference, and tries UDP/443; the LTE carrier silently drops it and after roughly 300ms Chrome gives up and returns ERR_CONNECTION_FAILED rather than falling back. When the user hits Reload, Chrome has just experienced a QUIC failure on this domain and temporarily suppresses QUIC, trying TCP instead, which succeeds instantly. When the user then opens a new tab, the QUIC failure flag is not a permanent block and the 24-hour alt-svc cache is still valid, so Chrome tries QUIC again, the LTE carrier drops it again, and ERR_CONNECTION_FAILED recurs.

7. Diagnosing QUIC Issues: Scripts and Commands

7.1 Check if a Site Is Advertising HTTP/3

#!/usr/bin/env bash
# check-quic-headers.sh
URL="${1:-https://example.com}"
HEADERS=$(curl -sI --max-time 10 "${URL}" 2>/dev/null)
ALT_SVC=$(echo "${HEADERS}" | grep -i 'alt-svc' || true)
HTTP_VER=$(echo "${HEADERS}" | head -1)
echo "Protocol: ${HTTP_VER}"
if [[ -n "${ALT_SVC}" ]]; then
    echo "HTTP/3 advertised: YES"
    echo "  ${ALT_SVC}"
    MA=$(echo "${ALT_SVC}" | grep -oP 'ma=\K[0-9]+' || echo "unknown")
    echo "  Cache duration: ${MA}s ($((MA / 3600)) hours)"
    echo "RISK: browsers will prefer QUIC for ${MA} seconds."
else
    echo "HTTP/3 advertised: NO (safe)"
fi

7.2 Test if QUIC Actually Works End-to-End

Requires curl built with HTTP/3 support (brew install curl on Mac, or curl >= 7.88 with quiche/ngtcp2 backend on Linux).

#!/usr/bin/env bash
# test-quic-connectivity.sh
HOST="${1:-example.com}"

# Test HTTP/3 (QUIC)
echo "--- HTTP/3 (QUIC/UDP) ---"
curl --http3-only -so /dev/null \
    -w "Status: %{http_code}  Time: %{time_total}s\n" \
    --max-time 10 "https://${HOST}/" 2>/dev/null \
    && echo "QUIC: WORKING" || echo "QUIC: FAILED"

# Test HTTP/2 (TCP fallback)
echo "--- HTTP/2 (TCP/TLS) ---"
curl --http2 -so /dev/null \
    -w "Status: %{http_code}  Time: %{time_total}s\n" \
    --max-time 10 "https://${HOST}/" 2>/dev/null \
    && echo "HTTP/2: WORKING" || echo "HTTP/2: FAILED"

# Test UDP port reachability
echo "--- UDP/443 port probe ---"
nc -u -z -w3 "${HOST}" 443 2>/dev/null \
    && echo "UDP/443: reachable" \
    || echo "UDP/443: BLOCKED or FILTERED"

7.3 Diagnose Chrome QUIC State (Desktop)

On desktop, visit chrome://net-internals/#quic and look for your domain in the active sessions list; clicking “Clear QUIC state” forces HTTP/2 fallback for subsequent requests. Visiting chrome://net-internals/#events and filtering by QUIC shows live protocol events. On mobile Chrome there is no net-internals UI, so you must clear all browsing data including cookies and cached images to clear the alt-svc state. Because Safari and Firefox maintain separate alt-svc caches, testing in a different browser is also a fast diagnostic step.

7.4 Cloudflare Zone HTTP/3 Setting Check

#!/usr/bin/env bash
SETTING=$(curl -s \
    "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/http3" \
    -H "X-Auth-Email: ${CF_EMAIL}" \
    -H "X-Auth-Key: ${CF_KEY}" 2>/dev/null \
    | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result']['value'])" 2>/dev/null)
echo "Cloudflare HTTP/3 setting: ${SETTING}"
[[ "${SETTING}" == "off" ]] \
    && echo "OK: QUIC disabled at zone level" \
    || echo "WARNING: QUIC is enabled, verify alt-svc header"

7.5 Monitor and Alert on alt-svc Re-enabling

#!/usr/bin/env bash
# watch-alt-svc.sh
DOMAIN="${1:-andrewbaker.ninja}"
CHECKS="${2:-10}"
INTERVAL="${3:-30}"
NTFY_URL="${NTFY_URL:-}"

for i in $(seq 1 "${CHECKS}"); do
    ALT=$(curl -sI --max-time 10 "https://${DOMAIN}/" 2>/dev/null \
        | grep -i 'alt-svc' || true)
    TS=$(date '+%H:%M:%S')
    if [[ -n "${ALT}" ]]; then
        echo "[${TS}] CHECK ${i}/${CHECKS}: PROBLEM, alt-svc present: ${ALT}"
        if [[ -n "${NTFY_URL}" ]]; then
            curl -s -d "${DOMAIN}: alt-svc header re-appeared. HTTP/3 is back on." \
                -H "Title: QUIC re-enabled on ${DOMAIN}" \
                -H "Priority: high" "${NTFY_URL}" > /dev/null
        fi
    else
        echo "[${TS}] CHECK ${i}/${CHECKS}: OK, no alt-svc"
    fi
    [[ $i -lt "${CHECKS}" ]] && sleep "${INTERVAL}"
done

8. How to Disable QUIC: Platform by Platform

8.1 Cloudflare (API, Reliable)

The dashboard toggle for HTTP/3 does not always persist, so the API is the only reliable mechanism:

curl -s -X PATCH \
    "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/http3" \
    -H "X-Auth-Email: ${CF_EMAIL}" \
    -H "X-Auth-Key: ${CF_KEY}" \
    -H "Content-Type: application/json" \
    -d '{"value":"off"}' \
    | python3 -c "
import sys, json
d = json.load(sys.stdin)
if d['success']: print('Disabled. Value:', d['result']['value'])
else: print('FAILED:', d.get('errors')); sys.exit(1)
"

# Verify propagation
sleep 5
curl -sI "https://your-domain.com/" | grep -i alt-svc || echo "OK: no alt-svc"

8.2 Cloudflare Workers (Belt-and-Suspenders)

If you have a Worker proxying requests you can strip the alt-svc header from responses, though Cloudflare re-adds alt-svc at the edge if HTTP/3 is enabled at the zone level, so this alone is insufficient and must be combined with the API disable above.

async function handleRequest(request) {
  const response = await fetch(request);
  const newHeaders = new Headers(response.headers);
  newHeaders.delete('alt-svc');
  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: newHeaders,
  });
}

8.3 nginx

# HTTP/2 only — correct configuration:
server {
    listen 443 ssl;
    http2 on;
    # No quic listen directive
    # No http3 directive
    # No add_header alt-svc
}

# If a previous config added alt-svc and you need to strip it
# (requires ngx_headers_more module):
more_clear_headers "alt-svc";

8.4 Apache

# HTTP/2 only — do not load mod_quic
Protocols h2 http/1.1

# Strip alt-svc if added by a proxy or upstream
<IfModule mod_headers.c>
    Header unset alt-svc
</IfModule>

8.5 Caddy

# Caddy enables HTTP/3 by default — disable at global level
{
    servers {
        protocols h1 h2
    }
}

8.6 HAProxy

frontend https_frontend
    bind *:443 ssl crt /etc/ssl/cert.pem
    # Do NOT add: bind *:443 quic crt /etc/ssl/cert.pem

    # Remove any alt-svc headers added by backends
    http-response del-header alt-svc

8.7 Chrome Desktop

# Via flags UI: chrome://flags/#enable-quic -> set to Disabled -> Relaunch

# macOS:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-quic

# Linux:
google-chrome --disable-quic

# Windows:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --disable-quic

8.8 Firefox

# about:config -> search for: network.http.http3.enabled -> set to false

# Or via user.js in profile folder:
user_pref("network.http.http3.enabled", false);

9. Best Practices

1. Do not enable HTTP/3 for B2B or enterprise-facing traffic without testing on your audience’s networks. Corporate WiFi commonly blocks UDP/443, and a meaningful percentage of enterprise users will see sporadic failures that are extremely difficult to diagnose without understanding the alt-svc cache mechanism.

2. Use a short ma= value during rollout. The default ma=86400 means a 24-hour failure window, so during testing use alt-svc: h3=":443"; ma=60 and ramp to ma=3600, then ma=86400, only after confirming no failures across your user base.

3. Monitor the alt-svc header in your deployment pipeline. After any CDN update, configuration change, or provider upgrade, re-check that HTTP/3 has not been silently re-enabled, and add check-quic-headers.sh to your CI/CD pipeline so this check runs automatically.

4. Verify via API, not dashboard. As demonstrated with Cloudflare, the dashboard UI and the actual zone configuration can diverge, and the API response is the ground truth.

5. Add an external probe on a non-origin network. A Pi-side health check does not detect QUIC failures because the Pi’s probe uses a different network path than mobile users; using a Cloudflare Worker, Lambda, or monitoring service in another region to probe the full user path is the only reliable way to catch these failures in production.

6. Custom error pages do not help for QUIC failures. A Cloudflare Worker intercepting 5xx errors only activates for requests that reach the CDN, but ERR_CONNECTION_FAILED from QUIC happens before the request reaches the edge, so there is nothing to intercept and the fix must be at the protocol level.

7. For resource-constrained servers, prefer HTTP/2. The 15x kernel call overhead of QUIC is measurable on Raspberry Pi hardware and low-spec VPS instances, and HTTP/2 delivers the vast majority of QUIC’s connection-multiplexing benefits without the userspace crypto and ACK processing overhead.

10. Summary Reference

AspectDetail
QUIC solvesTCP head-of-line blocking, handshake latency, connection mobility across network changes
Performance gain8% desktop latency, 3.5% mobile latency, 18% YouTube rebuffer reduction (Google SIGCOMM 2017)
Adoption (2024)34% of top 10M sites; 21% of all Cloudflare traffic uses HTTP/3
QUIC breaks onUDP-blocking firewalls, ISP rate limits, silent packet drops, enterprise VPNs
Enterprise risk3-5% of public networks block UDP/443; corporate rates substantially higher
Worst failure modeERR_CONNECTION_FAILED with no fallback; browser QUIC cache lasts 24 hours
CPU overhead15x more kernel processing calls; up to 45.2% throughput reduction under load
Clearing browser cacheDoes NOT clear QUIC/alt-svc state; must use chrome://net-internals/#quic
Cloudflare dashboardHTTP/3 toggle unreliable; always verify and set via API
Best diagnosticcurl -sI https://yourdomain.com | grep alt-svc
Safe ma= for rolloutma=60 during testing; ramp to ma=86400 only when stable

11. References

[1] Robin Marx, HTTP/3 Explained: TCP Head-of-Line Blocking, 2019–2023.
https://http3-explained.haxx.se/en/why-quic/why-tcphol
Analysis of HOL blocking under packet loss, including the 575x blocking time difference and 58.6% throughput cap figures.

[2] Kakhki et al., Taking a Long Look at QUIC, Communications of the ACM, Vol. 62 No. 7, July 2019.
https://m-cacm.acm.org/magazines/2019/7/237717-taking-a-long-look-at-quic/fulltext
Comprehensive measurement study of QUIC performance in production, including degradation at 2% packet loss.

[3] Robin Marx, HOL Blocking Blog Post, GitHub, 2019.
https://github.com/rmarx/holblocking-blogpost
Detailed visualisation of how a single dropped packet blocks 127 in-flight chunks in HTTP/2.

[4] Langley et al., The QUIC Transport Protocol: Design and Internet-Scale Deployment, ACM SIGCOMM 2017.
https://research.google/pubs/the-quic-transport-protocol-design-and-internet-scale-deployment/
Google’s original production deployment paper: 8% desktop search latency reduction, 3.5% mobile, 18%/15.3% YouTube rebuffer reduction.

[5] HTTP/3 is Fast, Request Metrics, 2022.
https://requestmetrics.com/web-performance/http3-is-fast
CDN-level benchmark data including Akamai’s 30% mobile latency reduction and 55% improvement under packet loss.

[6] Cloudflare Radar Year in Review 2025, Cloudflare Blog.
https://blog.cloudflare.com/radar-2025-year-in-review/
HTTP/3 adoption statistics: 21% of Cloudflare traffic, geographic breakdown, 116ms 95th-percentile connection improvement.

[7] RFC 9218: Extensible Prioritization Scheme for HTTP, IETF, June 2022.
https://datatracker.ietf.org/doc/rfc9218/
Specification of the extensible priorities mechanism cited for up to 37% faster page loading.

[8] Geoff Huston, Measuring QUIC vs TCP on Mobile and Desktop, APNIC Blog, January 2018.
https://blog.apnic.net/2018/01/29/measuring-quic-vs-tcp-mobile-desktop/
Mobile vs desktop QUIC performance; 58% application-limited state on mobile vs 7% on desktop.

[9] HTTP/3 Adoption Statistics, W3Techs, September 2024.
https://w3techs.com/technologies/details/ce-http3
34% of top 10M websites support HTTP/3.

[10] Iyengar & Thomson (Eds.), RFC 9000: QUIC, a UDP-Based Multiplexed and Secure Transport, IETF, May 2021.
https://datatracker.ietf.org/doc/html/rfc9000
The core QUIC specification. Related: RFC 9001 (TLS for QUIC), RFC 9002 (Loss Detection), RFC 9114 (HTTP/3).

[11] HTTP/3 Usage One Year On, Cloudflare Blog.
https://blog.cloudflare.com/http3-usage-one-year-on/
3-5% public network UDP blocking; enterprise VPN and firewall policies in context of QUIC deployment.

[12] Performance Comparison of HTTP/3 and HTTP/2: Proxy vs Non-Proxy Environments, arxiv 2409.16267, 2024.
https://arxiv.org/html/2409.16267v2
231K kernel calls for QUIC vs 15K for HTTP/2; 45.2% throughput reduction under CPU-bound conditions. Related: arxiv 2310.09423.

[13] Performance Evaluation of HTTP/2 and HTTP/3(QUIC) using Lighthouse, Carleton University, 2024.
https://doi.org/10.1145/3488663.3493692
Empirical comparison of HTTP/2 and HTTP/3 under controlled conditions.

Written 2 May 2026 · andrewbaker.ninja · Based on a real incident diagnosing ERR_CONNECTION_FAILED caused by Cloudflare’s HTTP/3 alt-svc header not being suppressed after the dashboard toggle was set to off. The API-level disable was required to actually remove the header.