HTTP/3 connection migration lets a client keep the same TCP connection open even when its IP address or port changes, like when switching from Wi-Fi to cellular.
Let’s see it in action. Imagine a browser on your laptop.
<!DOCTYPE html>
<html>
<head>
<title>HTTP/3 Connection Migration Demo</title>
</head>
<body>
<h1>Watching Connection Migration</h1>
<p>Open your browser's developer tools (usually F12) and go to the Network tab.</p>
<p>Initially, you'll see requests over HTTP/3 (QUIC). Note the local port used for the connection.</p>
<p>Now, disconnect from Wi-Fi and switch to your phone's cellular data. You'll likely see the requests continue without interruption, and the local port might change, but the server still recognizes it as the same logical connection.</p>
</body>
</html>
When you load this page, your browser establishes an HTTP/3 connection to the server. HTTP/3 uses QUIC, which runs over UDP. Unlike TCP, QUIC doesn’t have a built-in concept of a "connection" tied to IP address and port. Instead, QUIC establishes a connection using a Connection ID. This 64-bit identifier is opaque to the network and is unique for each client-server QUIC connection.
When your client’s network interface changes (e.g., Wi-Fi to LTE), your IP address and potentially your UDP port will change. The server, receiving packets from a new IP/port combination, would normally treat this as a new connection. However, QUIC’s connection migration mechanism allows the client to signal this change. The client sends a NEW_CONNECTION_ID frame to the server, indicating its new source IP and port, and the server updates its internal mapping for that Connection ID. The server then acknowledges this change. The client, in turn, can also send a RETIRE_CONNECTION_ID frame for its old 0-RTT or 1-RTT connection IDs, and NEW_CONNECTION_ID for its new ones. This ensures that the server can route incoming packets correctly to the established logical connection, even though the underlying network endpoints have shifted.
The core problem this solves is maintaining application-level state and continuity during transient network disruptions or changes that would typically break a traditional TCP connection. Think of a video call or a large file upload – losing the connection and having to re-establish everything, including authentication and session state, is a poor user experience. HTTP/3’s connection migration, powered by QUIC’s Connection IDs, makes these transitions seamless. The connection is defined by the Connection ID, not by the IP/port tuple.
The most surprising thing is how the Connection ID, a seemingly simple identifier, is the linchpin. The network infrastructure, including firewalls and load balancers, is largely unaware of this Connection ID. They operate at the IP and UDP port level. When a client’s IP changes, a NAT device might assign a new port. The client sends a NEW_CONNECTION_ID frame. If the server is behind a load balancer, that load balancer typically needs to be QUIC-aware or use sticky sessions based on the client’s original IP if it’s not properly handling QUIC connection migration. However, the true magic is that the server application itself, once it receives the NEW_CONNECTION_ID frame, knows to associate the new IP/port with the existing Connection ID. It’s the server’s responsibility to update its internal routing for that Connection ID.
This mechanism requires that both the client and server implement the QUIC protocol correctly, specifically handling the NEW_CONNECTION_ID and RETIRE_CONNECTION_ID frames. The client must be able to detect network changes and proactively inform the server. The server must maintain a mapping between Connection IDs and the current client IP/port.
The next challenge for developers is understanding how connection migration interacts with stateful server-side logic and network middleboxes.