How Does Caddy's HTTP/3 Support Work: A Deep Dive into QUIC Implementation
Caddy implements HTTP/3 support using the quic-go library, automatically creating QUIC listeners when TLS is enabled while handling Alt-Svc advertisement, Unix socket limitations, and graceful shutdown integration.
Caddy's HTTP/3 implementation provides zero-configuration support for the QUIC protocol, allowing modern browsers to connect via UDP for improved performance. The server leverages the github.com/quic-go/quic-go library to handle the complexity of QUIC transport while maintaining seamless integration with Caddy's existing TLS automation and HTTP architecture. Understanding the internal mechanics of listener creation, protocol negotiation, and upstream connections reveals how Caddy delivers production-ready HTTP/3 support.
QUIC Foundation and Architecture
Caddy's HTTP/3 stack is built on a clear separation of concerns between transport management and HTTP semantics. The implementation relies on the quic-go library to handle low-level QUIC protocol details, while Caddy's own modules/caddyhttp package manages server lifecycle, TLS configuration, and HTTP handler integration. This architecture ensures that HTTP/3 connections receive the same middleware treatment as HTTP/1.1 and HTTP/2 traffic.
QUIC Listener Creation
When a server configuration includes TLS and the network supports UDP, Caddy initializes HTTP/3 through NetworkAddress.ListenQUIC in modules/caddyhttp/listeners.go (lines 26-45). This function creates a QUIC-specific listener that wraps the quic-go transport layer.
The listener creation follows this flow:
- Protocol Detection: Caddy checks if the
listendirective should include thehttp3protocol alongside standard TCP listeners - Address Binding: The function binds to the same port as the HTTPS listener but uses UDP instead of TCP
- TLS Config Reuse: The QUIC listener shares the same TLS configuration as the existing HTTP/1.1 and HTTP/2 listeners, ensuring certificate consistency
HTTP/3 Server Initialization
The first time an HTTP/3 connection is required, Server.serveHTTP3 in modules/caddyhttp/server.go (lines 654-682) constructs an http3.Server instance from the quic-go library. This initialization includes:
- Handler Reuse: The same
http.Handler(the Caddy*Serverinstance) processes HTTP/3 requests, ensuring middleware chains work identically across all protocol versions - TLS Integration: The server uses the underlying TLS configuration from the TCP listener
- QUIC Configuration: A
quic.Configthat explicitly enables Version 1 and Version 2 of the QUIC protocol - Observability: Installation of the default QLOG tracer for debugging and performance analysis
Alt-Svc Header Advertisement
For clients connecting via HTTP/1.x or HTTP/2, Caddy advertises HTTP/3 availability through the Alt-Svc header. In modules/caddyhttp/server.go (lines 334-341), the server logic adds this header to responses only when the request is not already HTTP/3. This allows browsers to discover and upgrade to QUIC connections for subsequent requests without manual configuration.
TLS Requirements and Constraints
HTTP/3 is strictly dependent on TLS encryption. In modules/caddyhttp/app.go (lines 630-658), the application checks the useTLS flag before attempting to start a QUIC listener. If TLS is not configured, Caddy logs a warning and skips HTTP/3 initialization, falling back to standard TCP-based protocols. This requirement aligns with the HTTP/3 specification, which mandates TLS 1.3 as the security layer.
Unix Socket Limitations
Caddy automatically disables HTTP/3 when listening on Unix domain sockets. The check in modules/caddyhttp/app.go (lines 33-43) detects Unix socket networks and prevents QUIC initialization because the protocol cannot multiplex the STREAM-oriented TCP socket and the DGRAM-oriented UDP socket required for QUIC transport on the same Unix socket path. This platform limitation ensures the server starts reliably without transport conflicts.
Reverse Proxy HTTP/3 Support
When Caddy acts as a reverse proxy, it can communicate with upstream servers using HTTP/3 through an experimental transport layer. In modules/caddyhttp/reverseproxy/httptransport.go (lines 135-164), setting the transport protocol option to "3" creates an http3.Transport instance. Key characteristics include:
- TLS Mandate: The upstream connection requires TLS, as HTTP/3 cannot operate over cleartext
- ** Experimental Status**: While functional, this upstream transport is marked experimental in the current codebase
- Version Compatibility: Uses the same quic-go backend as the server implementation
Graceful Shutdown Integration
HTTP/3 listeners participate in Caddy's zero-downtime reloads and graceful shutdowns. The Server struct maintains a quicListeners slice (managed in modules/caddyhttp/server.go, lines 820-840) that tracks all active QUIC listeners. When the application receives a shutdown signal, these listeners close in coordination with TCP listeners, ensuring all in-flight QUIC connections complete or migrate appropriately before termination.
Configuration Examples
Enable HTTP/3 in a Caddyfile with automatic TLS:
example.com {
tls you@example.com
file_server
}
Programmatic configuration in Go:
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func main() {
srv := &caddyhttp.Server{
Listen: []string{":443"},
TLS: &caddyhttp.TLS{
// TLS configuration (certificates, ACME, etc.)
},
Protocols: []string{"h1", "h2", "h3"},
}
app := &caddyhttp.App{
Servers: []*caddyhttp.Server{srv},
}
caddy.Start(app)
}
Reverse-proxy to an HTTP/3 upstream:
reverse_proxy https://backend.example.com {
transport http {
protocol "3"
}
}
Summary
- quic-go Foundation: Caddy delegates QUIC protocol handling to the
github.com/quic-go/quic-golibrary while managing the HTTP/3 server lifecycle internally. - Automatic Discovery: The
Alt-Svcheader automatically advertises HTTP/3 capability to HTTP/1.x and HTTP/2 clients. - TLS Dependency: HTTP/3 requires active TLS configuration; Caddy skips QUIC initialization if
useTLSis false. - Platform Constraints: Unix socket listeners disable HTTP/3 automatically due to TCP/UDP multiplexing limitations.
- Upstream Support: Experimental
http3.Transportenables reverse proxying to HTTP/3 backends when configured withprotocol "3". - Graceful Operations: QUIC listeners integrate with Caddy's shutdown hooks via the
quicListenersslice in the server struct.
Frequently Asked Questions
Does Caddy HTTP/3 require manual configuration?
No. Caddy enables HTTP/3 automatically when TLS is configured. The server creates QUIC listeners alongside standard TCP listeners and advertises support via the Alt-Svc header. You only need explicit configuration when using Unix sockets (where HTTP/3 is unavailable) or when forcing specific protocol versions in the server configuration.
Why doesn't HTTP/3 work on Unix sockets?
HTTP/3 requires UDP (datagram) transport while standard HTTP uses TCP (stream). In modules/caddyhttp/app.go, Caddy detects Unix socket networks and disables HTTP/3 because a single Unix socket cannot simultaneously handle both TCP and UDP protocols. For Unix socket deployments, Caddy seamlessly falls back to HTTP/1.1 or HTTP/2.
Can Caddy proxy to HTTP/3 upstreams?
Yes, through the experimental http3.Transport implemented in modules/caddyhttp/reverseproxy/httptransport.go. Set protocol "3" in the transport configuration. Note that the upstream must support TLS, as HTTP/3 requires an underlying TLS 1.3 connection. This feature allows Caddy to act as a gateway to QUIC-only backends or services that prefer HTTP/3 communication.
What QUIC versions does Caddy support?
According to the quic.Config setup in Server.serveHTTP3 (modules/caddyhttp/server.go), Caddy enables both Version 1 and Version 2 of the QUIC protocol. This configuration ensures compatibility with modern browsers while supporting the latest protocol improvements for reduced latency and improved congestion control.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →