# How Does Caddy's HTTP/3 Support Work: A Deep Dive into QUIC Implementation

> Discover how Caddy implements HTTP/3 using quic-go. Learn about its automatic QUIC listener creation, Alt-Svc advertisement, and graceful shutdown.

- Repository: [Caddy/caddy](https://github.com/caddyserver/caddy)
- Tags: deep-dive
- Published: 2026-03-03

---

**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`](https://github.com/caddyserver/caddy/blob/main/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 `listen` directive should include the `http3` protocol 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`](https://github.com/caddyserver/caddy/blob/main/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 `*Server` instance) 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.Config` that 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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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:

```caddy
example.com {
    tls you@example.com
    file_server
}

```

Programmatic configuration in Go:

```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:

```caddy
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-go` library while managing the HTTP/3 server lifecycle internally.
- **Automatic Discovery**: The `Alt-Svc` header 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 `useTLS` is false.
- **Platform Constraints**: Unix socket listeners disable HTTP/3 automatically due to TCP/UDP multiplexing limitations.
- **Upstream Support**: Experimental `http3.Transport` enables reverse proxying to HTTP/3 backends when configured with `protocol "3"`.
- **Graceful Operations**: QUIC listeners integrate with Caddy's shutdown hooks via the `quicListeners` slice 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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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`](https://github.com/caddyserver/caddy/blob/main/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.