# HTTP/3

> Serve HTTP/3 over QUIC on Tokio (quinn) or Compio (compio-quic) with mandatory TLS, congestion control, and stream caps.

HTTP/3 runs over QUIC (UDP) with the `h3` crate for the protocol: `quinn` drives
QUIC on Tokio and `compio-quic` on Compio. It is gated behind the `http3` feature.
The same `Router` you use for HTTP/1.1 and HTTP/2 drives HTTP/3 unchanged, and
handlers see a `ConnInfo` whose transport is `Http3`.

<Callout type="warn">
  TLS is **mandatory** for QUIC, so a `TlsCert` is always required. On Compio a
  request body is bound to the runtime thread that received it, like every compio
  task; moving it to another thread panics. Enable `webtransport` to also accept
  [WebTransport sessions](/docs/transports/webtransport) on the same server.
</Callout>

## Quickstart

QUIC binds a UDP socket, not a `TcpListener`, so the address is passed at spawn
time. `spawn_h3` returns a handle for startup errors and shutdown; `try_spawn_h3`
binds before returning, so certificate and bind errors surface immediately.

<Tabs items="['Tokio','Compio']">
  <Tab value="Tokio">
    ```rust
    use anyhow::Result;
    use tako::Method;
    use tako::responder::Responder;
    use tako::router::Router;

    async fn hello() -> impl Responder {
      "Hello, HTTP/3 World!".into_response()
    }

    #[tokio::main]
    async fn main() -> Result<()> {
      let mut router = Router::new();
      router.route(Method::GET, "/", hello);

      tako::Server::builder()
        .tls(tako::TlsCert::pem_paths("cert.pem", "key.pem"))
        .build().spawn_h3("[::]:4433", router).result().await?;
      Ok(())
    }
    ```
  </Tab>

  <Tab value="Compio">
    ```rust
    use tako::router::Router;
    use tako::types::BoxError;
    use tako::{CompioServer, TlsCert};

    #[compio::main]
    async fn main() -> Result<(), BoxError> {
      let mut router = Router::new();
      router.get("/", || async { "Hello, HTTP/3 World!" });

      let handle = CompioServer::builder()
        .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
        .build()
        .try_spawn_h3("[::]:4433", router)?;
      handle.result().await?;
      Ok(())
    }
    ```
  </Tab>
</Tabs>

Test with a recent curl:

```
curl --http3 -k https://localhost:4433/
```

Source: [examples/hello-world-http3/src/main.rs](https://github.com/rust-dd/tako/blob/main/examples/hello-world-http3/src/main.rs)

```rust
use anyhow::Result;
use tako::Method;
use tako::responder::Responder;
use tako::router::Router;

async fn hello_world() -> impl Responder {
  "Hello, HTTP/3 World!".into_response()
}

async fn json_example() -> impl Responder {
  r#"{"message": "Hello from HTTP/3!", "protocol": "h3"}"#.into_response()
}

#[tokio::main]
async fn main() -> Result<()> {
  // HTTP/3 requires TLS certificates
  // You can generate self-signed certificates for testing with:
  // openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"

  let mut router = Router::new();
  router.route(Method::GET, "/", hello_world);
  router.route(Method::GET, "/json", json_example);

  println!("Starting HTTP/3 server on [::]:4433");
  println!("Test with: curl --http3 -k https://localhost:4433/");

  tako::Server::builder()
    .tls(tako::TlsCert::pem_paths("cert.pem", "key.pem"))
    .build()
    .spawn_h3("[::]:4433", router)
    .result()
    .await
    .expect("HTTP/3 server failed");

  Ok(())
}
```

## The Server builder

For graceful drain and an owned shutdown handle, build a `Server`, attach a
`TlsCert`, and call `spawn_h3` with the bind address. It returns the same
`ServerHandle` as every other transport.

```rust
use tako::{Server, TlsCert};
use tako::router::Router;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
  let mut router = Router::new();
  router.route(tako::Method::GET, "/", || async { "Hello over HTTP/3" });

  let server = Server::builder()
    .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
    .build();

  let handle = server.spawn_h3("[::]:4433", router);
  handle.join().await;
  Ok(())
}
```

Without a `TlsCert`, `spawn_h3` reports the error through `result()` and
`try_spawn_h3` returns it immediately — QUIC cannot run without TLS. Every
`TlsCert` variant (PEM paths, DER, `Resolver`, mTLS) flows through the same
rustls-config helper as HTTPS.

## Tuning QUIC

HTTP/3 knobs live on `ServerConfig` alongside the HTTP/1 and HTTP/2 settings and
apply on both runtimes:

| Field                            | Purpose                                                                      |
| -------------------------------- | ---------------------------------------------------------------------------- |
| `h3_max_concurrent_bidi_streams` | Cap on client-initiated bidirectional streams (default 100).                 |
| `h3_max_concurrent_uni_streams`  | Cap on client-initiated unidirectional streams (default 8).                  |
| `h3_max_idle_timeout`            | Idle timeout with no QUIC packets either direction (default 30 s).           |
| `h3_congestion`                  | Congestion controller: `Cubic` (default), `NewReno`, or `Bbr`.               |
| `h3_enable_datagrams`            | Enable QUIC datagrams (RFC 9221) on the connection.                          |
| `h3_use_retry`                   | Issue a QUIC Retry packet to validate source addresses (anti-amplification). |
| `h3_goaway_grace`                | Per-connection grace for in-flight streams after GOAWAY.                     |

```rust
use tako::{Server, ServerConfig, TlsCert};
use tako::router::Router;
use tako_rs_server::H3Congestion;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
  let mut router = Router::new();
  router.route(tako::Method::GET, "/", || async { "h3 tuned" });

  let server = Server::builder()
    .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
    .config(ServerConfig {
      h3_congestion: H3Congestion::Bbr,
      h3_use_retry: true,
      h3_enable_datagrams: true,
      ..ServerConfig::default()
    })
    .build();

  let handle = server.spawn_h3("[::]:4433", router);
  handle.join().await;
  Ok(())
}
```

`ServerConfig` is re-exported as `tako::ServerConfig`; the `H3Congestion` enum
lives in `tako-rs-server`, so import it from there.

The effective per-connection GOAWAY grace at runtime is
`min(h3_goaway_grace, drain_timeout)` — a long per-connection grace cannot push
the total shutdown past the global drain budget.

<Callout type="info">
  `h3_goaway_grace` larger than `drain_timeout` is a no-op beyond the global
  ceiling. Raise `drain_timeout` too if you need a longer overall drain window.
</Callout>

## Certificate provider note

With `http3`, rustls is compiled with two crypto providers: `aws-lc-rs` (its
default) and `ring` (pulled in by the QUIC stack). Tako installs `aws-lc-rs` as the
process-wide `CryptoProvider` when none is installed yet. To use a different one,
install it at startup — for example
`rustls::crypto::ring::default_provider().install_default()` — **before**
constructing any server; once a provider is installed, Tako uses it as-is.

## Related

* [HTTP](/docs/transports/http) — HTTP/1.1 and HTTP/2.
* [WebTransport](/docs/transports/webtransport) — browser WebTransport sessions on
  the HTTP/3 server, behind `webtransport`.
* [Feature flags](/docs/reference/features) — the `http3` feature and what it
  pulls in.

Source: https://tako.rust-dd.com/docs/transports/http3