Safeguard
Supply Chain Security•

QUIC and HTTP/3 Explained: How RFC 9000 and RFC 9114 Work

QUIC (RFC 9000) is an encrypted transport over UDP; HTTP/3 (RFC 9114) runs HTTP on it. How they work, where they are weak and how to secure them.

Safeguard Security Team
37 min read

QUIC is an encrypted, multiplexed transport protocol that runs over UDP, defined in RFC 9000 (May 2021) with TLS 1.3 built in through RFC 9001. HTTP/3, defined in RFC 9114 (June 2022), is the version of HTTP that runs on QUIC. Together they replace the TCP plus TLS plus HTTP/2 stack for a growing share of web traffic.

That is the direct answer. The longer one matters if you run web servers, proxies or firewalls, because QUIC moves the transport out of the kernel and into libraries inside your applications, encrypts nearly everything a network device used to read, and uses UDP port 443, which many security policies were never written for. Every protocol fact here comes from the RFCs on rfc-editor.org, and every command from the official nginx, curl, BIND and Linux documentation.

Key takeaways

  • QUIC version 1 is four RFCs from May 2021: RFC 8999 (version independent properties), RFC 9000 (transport), RFC 9001 (TLS) and RFC 9002 (loss detection and congestion control). HTTP/3 (RFC 9114) and QPACK (RFC 9204) followed in June 2022, and QUIC version 2 (RFC 9369) in May 2023.
  • QUIC fixes three TCP plus TLS problems: a layered, slow handshake, head of line blocking across multiplexed requests, and connections that break when a client changes network.
  • After the handshake almost everything is encrypted and authenticated, including acknowledgments and packet numbers. Observers still see IP addresses, ports, connection IDs, packet sizes, timing and, without Encrypted Client Hello, the server name.
  • The RFCs name their own weakness classes: amplification before address validation (capped at three times the bytes received), handshake denial of service, 0-RTT replay, request forgery, resource exhaustion, header compression abuse and version downgrade.
  • RFC 9001 says disabling 0-RTT "is the most effective defense against replay attack". nginx ships with ssl_early_data off and quic_retry off, and labels its HTTP/3 module experimental.
  • Always keep an HTTP/2 or HTTP/1.1 over TLS fallback: RFC 9308 cites measurements showing 3% to 5% of networks block all UDP.

What QUIC and HTTP/3 are for: the layer and the standards

QUIC is a transport protocol. It sits where TCP sits, but runs on top of UDP and carries its own encryption instead of relying on a separate TLS layer. RFC 9000 describes it as providing flow controlled streams, low latency connection setup and the ability to move a connection to a new network path. HTTP/3 is an application protocol that maps HTTP semantics (methods, status codes and fields, defined in RFC 9110) onto QUIC streams.

Running over UDP was deliberate. RFC 9308 explains that building on UDP lets QUIC be implemented in user space and cross NATs and other middleboxes without changing existing network equipment. In practice QUIC lives in a library your application links, not in the kernel, so the transport you depend on is now a dependency you ship and patch.

DocumentTitlePublished
RFC 8999Version-Independent Properties of QUIC (updated by RFC 9368)May 2021
RFC 9000QUIC: A UDP-Based Multiplexed and Secure TransportMay 2021
RFC 9001Using TLS to Secure QUICMay 2021
RFC 9002QUIC Loss Detection and Congestion ControlMay 2021
RFC 9221An Unreliable Datagram Extension to QUICMarch 2022
RFC 9114HTTP/3June 2022
RFC 9204QPACK: Field Compression for HTTP/3June 2022
RFC 9308 and RFC 9312Applicability and Manageability of QUIC (Informational)September 2022
RFC 9368 and RFC 9369Compatible Version Negotiation; QUIC Version 2May 2023
RFC 9460SVCB and HTTPS DNS recordsNovember 2023

None of the core QUIC or HTTP/3 RFCs obsoletes an earlier document, because QUIC is a new protocol, not a TCP revision. HTTP/3 sits beside HTTP/1.1 (RFC 9112) and HTTP/2 (RFC 9113), all published in June 2022 with RFC 9110, which obsoleted the RFC 7230 series. The rfc-editor.org records list no updates to RFC 9000, 9001, 9002, 9114 or 9204 as of today.

What problem QUIC solves versus TCP plus TLS

Handshake latency. TCP plus TLS 1.3 needs the TCP handshake and then the TLS handshake. QUIC folds both into one, so a new connection carries application data after one round trip, and a resumed one can send data in its first flight with 0-RTT.

Head of line blocking. HTTP/2 multiplexes requests over one TCP byte stream, so one lost packet stalls every request on the connection. QUIC streams are independent, so a loss only delays the streams whose data it carried. QPACK exists for the same reason: RFC 9204 explains that HPACK assumes a total ordering across streams, which would bring the blocking back.

Fragile, ossified connections. TCP connections are tied to the address and port 4-tuple, so moving from Wi-Fi to mobile data kills them. QUIC identifies connections by connection IDs, which survive address changes. And because middleboxes came to depend on readable TCP headers, TCP became hard to change; QUIC encrypts and authenticates almost all of its header to prevent that happening again.

For a field by field view of what an eavesdropper sees in TCP, UDP, TLS and QUIC, see what an eavesdropper can see and change on TCP and UDP. For UDP itself, see what UDP is and why it matters.

How QUIC works on the wire

QUIC packets travel inside UDP datagrams, and one datagram can carry several packets (coalescing), which lets the handshake send an Initial and a Handshake packet together. Each packet carries frames, and frames do the work: stream data, acknowledgments, flow control and connection ID management. There are two header forms: the long header during the handshake and the short header afterwards.

Long header (RFC 9000 section 17.2)

FieldSizeMeaning
Header Form1 bit1 for long headers
Fixed Bit1 bit1; packets with 0 are discarded
Long Packet Type2 bitsInitial 0x00, 0-RTT 0x01, Handshake 0x02, Retry 0x03 (version 1)
Type-Specific Bits4 bitsIncludes the protected packet number length
Version32 bits0x00000001 for version 1; 0x00000000 marks Version Negotiation
DCID Length, DCID8 bits, 0 to 160 bitsDestination Connection ID, at most 20 bytes in version 1
SCID Length, SCID8 bits, 0 to 160 bitsSource Connection ID, at most 20 bytes in version 1
Type-Specific PayloadvariableToken, length, packet number and encrypted frames

Short header, the 1-RTT packet (section 17.3.1)

FieldSizeMeaning
Header Form1 bit0 for short headers
Fixed Bit1 bit1
Spin Bit1 bitOptional; lets on-path observers estimate round trip time
Reserved Bits, Key Phase, Packet Number Length2, 1, 2 bitsAll masked by header protection
Destination Connection ID0 to 160 bitsNo length field
Packet Number8 to 32 bitsMasked by header protection
Packet PayloadvariableEncrypted, authenticated frames

Header protection (RFC 9001 section 5.4) uses a separate key, derived with the "quic hp" label, to mask the packet number and the low four bits of a long header's first byte or five bits of a short header's. In a short header, only the header form, fixed bit, spin bit and destination connection ID stay readable.

Connection IDs and streams

A connection ID is an opaque value each endpoint chooses for packets sent to it. Endpoints route by connection ID, so a connection survives a client address change. They issue spares with NEW_CONNECTION_ID and retire them with RETIRE_CONNECTION_ID, and RFC 9000 section 9.5 forbids reusing a connection ID from more than one local address, so an observer cannot link two paths.

Streams carry the data. The low two bits of a stream ID give its type: 0x00 client initiated bidirectional, 0x01 server initiated bidirectional, 0x02 client initiated unidirectional, 0x03 server initiated unidirectional. Peers cap stream counts with the initial_max_streams_bidi and initial_max_streams_uni transport parameters and raise them with MAX_STREAMS, and flow control works per stream (MAX_STREAM_DATA) and per connection (MAX_DATA).

Frames

TypeFramePurpose
0x00, 0x01PADDING, PINGFill space (client Initial datagrams must reach 1,200 bytes); elicit an acknowledgment
0x02 to 0x03ACKAcknowledge packet ranges, optionally with ECN counts
0x04, 0x05RESET_STREAM, STOP_SENDINGEnd or refuse a stream
0x06, 0x07CRYPTO, NEW_TOKENCarry TLS handshake messages; give a token for future address validation
0x08 to 0x0fSTREAMStream data
0x10 to 0x17MAX_DATA through STREAMS_BLOCKEDFlow control and stream limits
0x18, 0x19NEW_CONNECTION_ID, RETIRE_CONNECTION_IDConnection ID management
0x1a, 0x1bPATH_CHALLENGE, PATH_RESPONSEPath validation with 8 bytes of random data
0x1c, 0x1dCONNECTION_CLOSETransport or application error close
0x1eHANDSHAKE_DONEServer confirms the handshake

RFC 9221 adds DATAGRAM frames (0x30 and 0x31), negotiated with the max_datagram_frame_size transport parameter (0x20), for unreliable data inside an encrypted connection. They are not retransmitted but still obey congestion control.

The handshake with TLS 1.3 (RFC 9001)

QUIC does not use TLS records. TLS hands its handshake messages to QUIC, which carries them in CRYPTO frames, and TLS returns keys for four encryption levels: Initial, 0-RTT, Handshake and 1-RTT. RFC 9001 section 4.2 says clients must not offer anything older than TLS 1.3, and an endpoint must close the connection if an older version is negotiated. The application protocol is chosen with ALPN (RFC 7301); HTTP/3 uses "h3".

Client                                              Server
  |-- Initial[CRYPTO: ClientHello + transport params] ->|  padded to at least 1200 bytes
  |<- Initial[CRYPTO: ServerHello, ACK] ----------------|  server capped at 3x bytes received
  |<- Handshake[CRYPTO: EncryptedExtensions,            |  until the client address is validated
  |    Certificate, CertificateVerify, Finished] -------|
  |-- Handshake[CRYPTO: Finished, ACK] ---------------->|
  |-- 1-RTT[STREAM: HTTP/3 request] ------------------->|
  |<- 1-RTT[HANDSHAKE_DONE, STREAM: response] ----------|  handshake confirmed
  |== 1-RTT short header packets both ways =============|
  |-- 1-RTT[CONNECTION_CLOSE] ------------------------->|  or idle timeout, or stateless reset

A server that wants proof of the client's address before doing expensive cryptography answers the first Initial with a Retry packet holding a token, which the client echoes. RFC 9312 calls Retry the functional analogue of TCP SYN cookies. Transport parameters (idle timeout, flow and stream limits, disable_active_migration, the 16 byte stateless_reset_token and others) travel inside the TLS handshake, so they get the handshake's integrity protection.

One detail surprises people. Initial packets are encrypted, but with keys derived from the client's Destination Connection ID and a salt published in RFC 9001 section 5.2. Anyone can derive them, and RFC 9369 notes this lets observers inspect Initial contents, including the TLS ClientHello. Hiding the server name needs Encrypted Client Hello (RFC 9849, March 2026). Our TLS 1.3 handshake walkthrough covers the TLS messages in depth.

Loss recovery and teardown

QUIC packet numbers never repeat within a number space, even for retransmissions, which removes TCP's ambiguity about which copy was acknowledged. RFC 9002 declares a packet lost once a packet sent three later (kPacketThreshold) is acknowledged or a time threshold passes, uses a probe timeout, and starts with an initial RTT of 333 milliseconds. Its example congestion controller resembles TCP NewReno; implementations may use others.

A connection ends by CONNECTION_CLOSE (type 0x1c for transport errors, 0x1d for application errors, which must be downgraded to 0x1c before the handshake is protected), by idle timeout, or by stateless reset. A stateless reset looks random but ends in the 16 byte token the peer received earlier, so an off-path attacker cannot forge one the way a TCP reset can be forged. With no visible FIN or RST, RFC 9312 notes, firewalls and NATs must expire QUIC state on timers.

Ports and IANA

QUIC has no fixed port. RFC 9308 gives UDP 443 as the default for HTTP/3, mirroring HTTPS over TCP, and the IANA registry lists https on UDP 443 referencing RFC 9110. DNS over QUIC (RFC 9250) uses UDP 853 (domain-s). RFC 9114 lets servers use any UDP port, and RFC 9312 warns that nothing guarantees a port carries its registered service.

How HTTP/3 maps onto QUIC (RFC 9114)

Each request and response uses one client initiated bidirectional stream. Unidirectional streams carry connection level traffic: the control stream (type 0x00, carrying SETTINGS and GOAWAY), push streams (0x01), and the QPACK encoder (0x02) and decoder (0x03) streams defined in RFC 9204.

FrameTypePurpose
DATA0x00Message body
HEADERS0x01QPACK encoded field section
CANCEL_PUSH0x03Cancel a server push
SETTINGS0x04Connection settings, first frame on the control stream
PUSH_PROMISE0x05Announce a server push
GOAWAY0x07Graceful shutdown
MAX_PUSH_ID0x0dClient limit on pushes

Because QUIC already handles flow control, resets, pings and padding, HTTP/3 drops those HTTP/2 frames. HTTP/2's PRIORITY frame has no equivalent; RFC 9114 Appendix A points to the Extensible Prioritization Scheme (RFC 9218) instead. Error codes run from H3_NO_ERROR (0x0100) to H3_VERSION_FALLBACK (0x0110), including H3_EXCESSIVE_LOAD (0x0107) for abusive peers.

How clients discover HTTP/3

The https scheme is tied to TCP, so clients need a hint. RFC 9114 section 3.1 describes the Alt-Svc header (RFC 7838, April 2016), for example Alt-Svc: h3=":443"; ma=86400, and also allows a direct QUIC attempt using the "h3" ALPN token. RFC 9460 adds the HTTPS DNS record (type 65), whose alpn parameter, such as alpn="h2,h3", tells clients before their first connection. RFC 9114 also says clients should try TCP based HTTP when QUIC fails, which is why blocking UDP usually slows sites rather than breaking them.

What QPACK changes from HPACK

HPACK (RFC 7541, May 2015) uses a 61 entry static table plus a dynamic table both sides update in strict order. On QUIC, a lost insert would stall every later header block. QPACK (RFC 9204) keeps the ideas but changes the mechanics:

  • A 99 entry static table, indexed 0 to 98.
  • Dynamic table inserts travel on a dedicated encoder stream and are acknowledged on a decoder stream.
  • Each header block declares a Required Insert Count; if the decoder has not yet seen that many inserts, only that stream is blocked.
  • The decoder sets its risk with SETTINGS_QPACK_MAX_TABLE_CAPACITY (0x01) and SETTINGS_QPACK_BLOCKED_STREAMS (0x07). Both default to zero, so a peer that never opts in gets no dynamic table and never blocks.

The cost is more per connection state in more places. RFC 9204 section 7.3 notes that data waiting on flow control, especially on the decoder stream, is not covered by the dynamic table limit, which is where implementation bugs have appeared. Our post on QPACK amplification in HTTP/3 servers covers a recent case and its fixed versions.

0-RTT, connection migration and QUIC version 2

0-RTT. A returning client can use a saved session ticket to send data in its first flight, saving a round trip. That data can be replayed. RFC 9001 section 9.2 says QUIC's own frames are idempotent and safe to replay, but the application data they carry is not, and managing that risk is the application protocol's job. RFC 9114 section 10.9 requires HTTP/3 to apply the mitigations in RFC 8470 (Using Early Data in HTTP, September 2018). Our post on TLS 1.3 0-RTT replay risk covers the HTTP side, including the 425 Too Early status.

Connection migration. RFC 9000 section 9 lets a client move to a new address without reconnecting. Migration is not allowed before the handshake is confirmed, only clients initiate it (servers can offer a preferred_address), a server can send disable_active_migration, every new address must pass path validation, and each path uses a fresh connection ID. RFC 9114 section 10.10 warns that anything using the client IP for logging or access control must accept that it can change mid connection.

QUIC version 2. RFC 9369 defines a version that works exactly like version 1 but exists to stop networks ossifying on version 1 details. It changes the version number to 0x6b3343cf (chosen at random, not "2"), the long header type codes (Initial 0b01, 0-RTT 0b10, Handshake 0b11, Retry 0b00), the initial salt, the HKDF labels (for example "quicv2 key" instead of "quic key") and the Retry integrity key. It exercises compatible version negotiation from RFC 9368, which switches versions without an extra round trip. RFC 8999 defines what every version must keep: the header form bit, the version field position, the connection ID fields and the Version Negotiation format.

Versions and history

DateMilestone
July 2014ALPN (RFC 7301), later used to select "h3"
May 2015HPACK (RFC 7541) for HTTP/2
April 2016HTTP Alternative Services (RFC 7838)
August 2018TLS 1.3 (RFC 8446); Using Early Data in HTTP (RFC 8470) follows in September
May 2021QUIC version 1: RFC 8999, 9000, 9001, 9002
March 2022DATAGRAM extension (RFC 9221)
May 2022DNS over QUIC (RFC 9250)
June 2022HTTP/3, QPACK, priorities (RFC 9218), WebSockets over HTTP/3 (RFC 9220), with RFC 9110, 9112, 9113
September 2022Applicability (RFC 9308) and manageability (RFC 9312) guidance
May 2023QUIC version 2 (RFC 9369) and compatible version negotiation (RFC 9368)
November 2023HTTPS and SVCB DNS records (RFC 9460)
March 2026TLS Encrypted Client Hello (RFC 9849)

RFC 9000 was published from draft 34 of the IETF working group document, according to its rfc-editor.org record. Draft versions used their own version numbers and do not interoperate with version 1. Nothing in version 1 is deprecated; servers today should speak version 1, with version 2 where their library supports it.

Security properties: what QUIC and HTTP/3 guarantee and what they do not

PropertyProvided?Notes from the RFCs
Payload confidentialityYes, after the handshakeInitial packets are readable by any observer
Integrity of payload and most header bitsYesAEAD plus header protection
Server authenticationYesTLS 1.3 certificates; older TLS must be refused
Client authenticationOptionalTLS client certificates or application level
Replay protectionYes for 1-RTT, no for 0-RTTApplication must handle early data
Off-path injection resistanceStrong after the handshakeStateless reset needs a secret token
On-path handshake disruptionNot preventedInitial packets can be raced or replaced
Server name privacyNo, unless ECH is usedSNI and ALPN sit in the readable ClientHello
Metadata privacyNoAddresses, ports, connection IDs, sizes and timing visible
AvailabilityPartialAddress validation and limits help; floods and exhaustion remain

RFC 9000 section 21.2 explains that once the handshake completes, endpoints discard most unauthenticated packets, leaving little room to interfere with an established connection. It is equally clear that during connection creation QUIC "only provides protection against attacks from off the network path."

Weakness classes, as concepts

The RFCs describe their own threats in detail. These are the shapes of the risk and the design choices behind them, not instructions.

Amplification before address validation. A server's first flight, with a certificate chain, is far larger than a ClientHello, so spoofed Initials could be reflected at a victim. RFC 9001 section 9.3 lists three defences: client Initial datagrams padded to at least 1,200 bytes, a limit of three times the bytes received to any unvalidated address, and authenticated Handshake acknowledgments a blind attacker cannot forge. RFC 9000 section 21.3 adds a variant where an attacker obtains a validation token, gives up the address, then reuses the token while spoofing that address; the fix is short token lifetimes and limited reuse.

Handshake denial of service. Each new handshake costs the server a signature and a key exchange. Retry forces clients to prove their address first, at the price of a round trip, and NEW_TOKEN lets returning clients skip it.

On-path handshake interference. Because Initial keys are public, an attacker who can race or replace Initial packets can make a handshake fail. RFC 9000 section 21.1.1.3 accepts this: the design detects tampering and fails rather than recovering.

0-RTT replay. The server has no fresh input from the client when early data arrives, so it cannot tell an original from a copy. Requests with side effects should not be accepted early unless the application detects duplicates.

Request forgery. RFC 9000 section 21.5 describes how a QUIC endpoint can be steered to send packets at a third party, through spoofed Initials, a preferred_address, spoofed migration or Version Negotiation. If the target trusts traffic by source, such as an unauthenticated loopback or private service, the QUIC server becomes a messenger. Countermeasures include refusing migration to loopback, caution with moves into private address ranges and avoiding known reflection ports before validation; the real fix is strong authentication on the vulnerable service.

Resource exhaustion. RFC 9000 sections 21.6 to 21.9 cover Slowloris style holding of many idle connections, stream fragmentation that makes receivers buffer gaps, stream commitment (opening stream 4000000 implicitly opens 1,000,001 client bidirectional streams if limits allow), and peers sending legitimate frames to no purpose. RFC 9114 section 10.5 adds unknown frames, settings and stream types, server push and field compression, and recommends limits plus H3_EXCESSIVE_LOAD for abuse.

Header compression exhaustion and side channels. QPACK caps the dynamic table and blocked streams, but RFC 9204 section 7.4 requires implementations to bound integers, encoded lengths and string literals too. SETTINGS_MAX_FIELD_SECTION_SIZE (0x06) is only advisory, so servers must enforce a hard limit and reply 431. RFC 9114 section 10.6 also forbids compressing secret and attacker controlled data in the same context, the classic compression length side channel.

Intermediary encapsulation. QPACK can carry carriage return, line feed and null characters that are invalid in HTTP/1.1. RFC 9114 section 10.3 requires such messages be treated as malformed, because a gateway translating them verbatim to an HTTP/1.1 back end could be made to split requests.

Version downgrade and middlebox interference. RFC 9000 section 21.12 notes version 1 Version Negotiation packets have no downgrade protection; RFC 9368 includes a downgrade prevention mechanism, and RFC 9312 advises networks not to interfere with version selection. RFC 9312 section 4.6 adds that dropping a random fraction of UDP packets lets handshakes complete, which prevents TCP fallback and leaves connections on heavy loss.

Stateless reset oracle. If server instances share the static key for reset tokens, an attacker who steers a packet to the wrong instance can make it send a valid reset for a live connection. RFC 9000 section 21.11 requires that a connection ID always reach the instance holding its state, and that no reset be sent for a connection that could be active elsewhere under the same key.

Middlebox visibility. RFC 9312 lists what operators can still see: version and connection IDs in long headers, the destination connection ID and spin bit in short headers, the readable ClientHello, sizes, timing and 5-tuples. Packet numbers, acknowledgments, stream data, close reasons and an end of flow signal are hidden, so inspection that relied on TCP state or TLS records must move to an endpoint you control, such as a terminating proxy.

Secure configuration, with defender-side checks

The examples use nginx and check with curl, dig and ss. Only run these against systems you own or are authorised to test.

What the nginx documentation says

  • QUIC and HTTP/3 have been available since nginx 1.25.0, and the ngx_http_v3_module page still calls the module experimental. It is not built by default and must be enabled with the http_v3_module configure option.
  • The module needs OpenSSL 1.1.1 or higher; 0-RTT needs OpenSSL 3.5.1 or higher, and before nginx 1.29.1 0-RTT could not be enabled with OpenSSL at all.
  • QUIC requires TLS 1.3, which ssl_protocols enables by default.
  • Defaults include quic_retry off, ssl_early_data off, quic_bpf off, http3_max_concurrent_streams 128 and quic_active_connection_id_limit 2.
  • quic_host_key sets the secret that encrypts stateless reset and address validation tokens. Without it, nginx generates a random key on every reload and rejects tokens issued under the old key.
  • quic_bpf routes QUIC packets with eBPF so migration works across workers; it is Linux 5.7 or later only.

A conservative server block

Adapted from the example in the ngx_http_v3_module documentation:

http {
    log_format quic '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" "$http3"';
    access_log logs/access.log quic;

    server {
        # same port for HTTP/3 and HTTPS, as the nginx docs recommend
        listen 443 quic reuseport;
        listen 443 ssl;

        ssl_certificate     certs/example.com.crt;
        ssl_certificate_key certs/example.com.key;

        quic_retry on;                              # validate client addresses
        quic_host_key /etc/nginx/quic_host.key;     # stable token key; protect it
        ssl_early_data off;                         # no 0-RTT without replay handling
        http3_max_concurrent_streams 128;

        location / {
            add_header Alt-Svc 'h3=":443"; ma=86400';
        }
    }
}
  1. Keep the TCP listener. listen 443 ssl is the fallback for networks that block UDP.
  2. Decide on Retry deliberately. It costs one round trip on first connections but stops spoofed Initials from consuming handshake CPU.
  3. Leave 0-RTT off unless the application handles replay. If you enable it, the $ssl_early_data variable returns "1" for early data before the handshake completes, so you can pass it upstream and refuse non idempotent requests, as RFC 8470 describes.
  4. Set a stable, secret quic_host_key when you run several instances or reload often, and rotate it on a schedule.
  5. Log $http3, which records "h3" for HTTP/3 requests, so you know how much traffic uses QUIC.
  6. Review controls keyed on $remote_addr, since migration can change a client's address.

Check the build, the listener and the advertisement

The nginx QUIC troubleshooting section recommends confirming the TLS library in use with nginx -V. The Linux ss(8) man page documents options for UDP, listening sockets, numeric output and owning processes. The curl man page documents fetching headers only in silent mode.

# which build and TLS library nginx is using
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'http_v3|OpenSSL|built with'

# is anything listening for QUIC on UDP 443
ss -ulnp | grep ':443'

# does the server advertise HTTP/3
curl -sI https://www.example.com/ | grep -i '^alt-svc'

A UDP 443 listener you did not expect is worth chasing; firewall reviews that only list TCP often miss it.

Check that HTTP/3 actually works

curl documents an http3 option (try HTTP/3, fall back if it fails) and an http3-only option (no fallback), plus the http_version write-out variable, "the http version that was effectively used". curl -V lists HTTP3 in its features if the build supports it.

curl -V | grep -o 'HTTP3'
curl --http3-only -sS -o /dev/null -w '%{http_version}\n' https://www.example.com/

A result of 3 means a QUIC handshake and HTTP/3 response succeeded. If this fails while a fallback request works, UDP is blocked or the QUIC listener is broken and clients are silently using TCP. curl's HTTP/3 page notes only its ngtcp2 backend is non experimental and that curl cannot do HTTP/3 through a proxy.

Check the DNS advertisement

BIND's dig takes a query type after the name, and the BIND manual shows HTTPS records with alpn="h2,h3":

dig +short www.example.com HTTPS

If you publish h3 here or in Alt-Svc, make sure UDP 443 is reachable for your users, or every client pays for a failed attempt before falling back.

Which QUIC versions a server accepts

Neither the curl nor the nginx documentation we reviewed exposes a switch for listing accepted QUIC versions. At the protocol level, a server that does not support the offered version answers with a Version Negotiation packet listing those it does. In practice, check your QUIC library's documentation, and use nginx's debug log, where QUIC messages carry the "quic" prefix, to see the version used in a handshake.

Firewall guidance

  • Allow inbound UDP 443 only to hosts meant to serve HTTP/3, in an explicit rule.
  • RFC 9312 recommends a state timeout of at least two minutes for QUIC flows on stateful devices.
  • If you throttle UDP, drop whole flows rather than random packets, so clients fall back cleanly.
  • Server access logs are now the main record of HTTP/3 activity, so make sure your HTTP/3 front end logs what your HTTP/2 one did.

When to use HTTP/1.1, HTTP/2 or HTTP/3

Use caseHTTP/1.1 over TLSHTTP/2 over TLSHTTP/3 over QUIC
Public site or API with mobile usersFallback onlyGood defaultBest on lossy or changing networks; keep a fallback
Internal traffic in one data centreFine for simple servicesCommon, including gRPCRarely worth the extra operational work
Clients on restrictive networksWorks everywhereWorks almost everywhereMay be blocked (3% to 5% of networks per RFC 9308)
Inline inspection requiredAt a terminating proxyAt a terminating proxyOnly at a terminating endpoint
Transport header integrityNone from TCPNone from TCPAuthenticated, mostly encrypted
Stack to patchKernel TCP plus TLS libraryKernel TCP plus TLS libraryUser space QUIC library plus TLS library
Operational costLowestLowHigher: UDP tuning, firewall rules, connection ID aware load balancing

Serve HTTP/2 over TLS as the baseline, add HTTP/3 at the public edge for mobile and long distance users, and keep HTTP/1.1 working for old clients and tools.

Should you block UDP 443?

Inbound: yes, everywhere except hosts that deliberately serve HTTP/3. That is least exposure, and it keeps stray QUIC listeners from developer tools or embedded servers unreachable.

Outbound from a managed network: some organisations block it so browsers fall back to TCP and TLS, where an inspecting proxy can see traffic. RFC 9114's fallback rule means this usually works, at some cost in performance. RFC 9308 notes the trade: falling back to TCP and TLS leaves transport control information open to modification in the network. If you block, block whole flows cleanly, per RFC 9312.

Never block UDP 443 to your own servers while advertising h3 through Alt-Svc or HTTPS records.

Common mistakes

  • Treating Initial packet encryption as confidentiality. The keys are public; SNI and ALPN are readable without ECH.
  • Turning on 0-RTT for speed without passing the early data signal to the application.
  • No TCP fallback. HTTP/3 only services fail on networks that block UDP.
  • Advertising HTTP/3 that is unreachable, so every client wastes a failed attempt.
  • Firewall reviews that list only TCP, missing UDP 443 and DNS over QUIC on UDP 853.
  • Short UDP state timeouts that silently break idle QUIC connections.
  • Random UDP throttling that blocks fallback and leaves users on lossy connections.
  • Trusting client IP for security decisions when migration can change it.
  • No shared token key in a cluster, which breaks Retry tokens and stateless resets across instances.
  • Treating QUIC as "just UDP" outside patch scope, when it is a user space library in your servers and images.
  • Passing invalid header characters from an HTTP/3 front end to an HTTP/1.1 back end.

FAQ

What port does QUIC use?

QUIC has no fixed port. HTTP/3 uses UDP 443 by default, and IANA lists https on UDP 443. Servers can use other ports and advertise them with Alt-Svc or HTTPS records. DNS over QUIC uses UDP 853.

Is QUIC encrypted?

Yes. QUIC always uses TLS 1.3 and encrypts payloads, acknowledgments and packet numbers after the handshake. Initial packets use publicly derivable keys, so the ClientHello and server name are readable unless ECH is used, and addresses, ports, sizes and timing stay visible.

What is the difference between QUIC and HTTP/3?

QUIC (RFC 9000) is a general purpose transport, like TCP. HTTP/3 (RFC 9114) is HTTP mapped onto QUIC streams. Other protocols, such as DNS over QUIC (RFC 9250), also run on QUIC.

HTTP/2 vs HTTP/3: which is faster?

HTTP/3 avoids TCP head of line blocking and can resume with 0-RTT, so it helps most on lossy, long distance and mobile connections. On clean, low latency networks the difference is small. Measure your own traffic.

Is HTTP/3 more secure than HTTP/2?

The cryptography is the same TLS 1.3. HTTP/3 adds authenticated, mostly encrypted transport headers, which makes off-path resets and header tampering much harder. It also brings 0-RTT replay if enabled, amplification limits that depend on correct implementation, more QPACK state and a newer user space stack to patch.

Does QUIC prevent TCP reset attacks?

QUIC does not use TCP, and its stateless reset requires a 16 byte token only the peer received, so an off-path attacker cannot forge one. An on-path attacker can still drop packets or disrupt the handshake.

How do I check if a website supports HTTP/3?

Look for an Alt-Svc header mentioning h3 in a curl header request, look for h3 in the alpn value of the site's HTTPS DNS record, then make a request with curl's http3-only option and print the HTTP version used. The commands are in the configuration section above.

What is QUIC version 2?

RFC 9369 (May 2023) defines a version that works like version 1 but uses a different version number (0x6b3343cf), packet type codes, initial salt and key labels, to keep networks from hard coding version 1 details.

Should I block QUIC on my network?

Block inbound UDP 443 to hosts that should not serve HTTP/3. Blocking outbound QUIC is a policy choice that pushes browsers back to TCP and TLS; if you do it, block whole flows so clients fall back quickly.

Where Safeguard fits

Safeguard does not inspect, filter or block QUIC, HTTP/3 or any network traffic, and it does not configure web servers or firewalls; nothing in the steps above depends on it. The honest connection is the code that implements these protocols. QUIC, HTTP/3, QPACK and TLS live in open source libraries such as ngtcp2, nghttp3 and your TLS library, inside your servers, proxies, frameworks and container images.

Safeguard's software composition analysis, generally available, builds a full transitive dependency inventory of repositories and images, so a QUIC library three levels deep still shows up. SBOM generation and analysis in SPDX and CycloneDX is generally available, and the platform shows which projects contain a given component and version, the workflow in our post on using SBOMs to find affected products fast. Vulnerability management with EPSS and KEV enrichment and reachability analysis, both generally available, help order fixes, and container scanning, part of ESSCM and generally available, covers the base images your web servers come from.

Fix it without waiting for upstream

QUIC moved the transport out of the kernel and into libraries your applications ship, so a transport bug is now a dependency bug: the QUIC, HTTP/3, QPACK and TLS code in your servers, proxies and images is exactly the kind of upstream component this section is about.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show dependency, image and remediation workflows, not network monitoring. You can download Safeguard and follow the CLI docs to try them on your own projects.

This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at the container image for your web server or reverse proxy, the same scan lists the TLS, QUIC and HTTP/3 libraries inside it with their versions and known vulnerabilities.

Click through it at your own pace, or open it full screen.

This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the dependency to bump is an HTTP/3 or QUIC library pinned in your manifest.

Click through it at your own pace, or open it full screen.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which projects or images include a particular QUIC or TLS library version and get the answer from your own scan data.

Click through it at your own pace, or open it full screen.

Quick check

Your QUIC server receives a 1,200 byte Initial from an address it has not validated. How many bytes may it send back before validation?

At most 3,600 bytes. RFC 9000 limits a server to three times the bytes received from an unvalidated address, which caps how much a spoofed packet can be reflected at a victim. With Retry, the server validates the address first.

You block outbound UDP 443 on your office network. Do websites that support HTTP/3 stop working?

Usually not. RFC 9114 tells clients to fall back to TCP based HTTP when QUIC fails, so browsers switch to HTTP/2 or HTTP/1.1 over TLS. Block whole flows rather than random packets, because RFC 9312 warns that partial drops let handshakes succeed and then leave connections on heavy loss.

Sources

Last reviewed: 23 October 2026. nginx labels its HTTP/3 module experimental, and QUIC version 2 support varies by library; check your server and library documentation before relying on a specific feature.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.