An excerpt from the repository, fetched Nov 13, 2026.
Read the projectās introduction
quiche is an implementation of the QUIC transport protocol and HTTP/3 as
specified by the IETF. It provides a low level API for processing QUIC packets
and handling connection state. The application is responsible for providing I/O
(e.g. sockets handling) as well as an event loop with support for timers.
For more information on how quiche came about and some insights into its design
you can read a post on Cloudflare's blog that goes into some more detail.
Who uses quiche?
Cloudflare
quiche powers Cloudflare edge network's HTTP/3 support. The
cloudflare-quic.com website can be used for
testing and experimentation.
Android
Android's DNS resolver uses quiche to implement DNS over HTTP/3.
curl
quiche can be integrated into curl to provide support for HTTP/3.
Getting Started
Command-line apps
Before diving into the quiche API, here are a few examples on how to use the
quiche tools provided as part of the quiche-apps crate. These are not
suitable for production environments; see disclaimers and
notes.
After cloning the project according to the command mentioned in the building section, the client can be run as follows:
$ cargo run --bin quiche-client -- https://cloudflare-quic.com/
while the server can be run as follows:
$ cargo run --bin quiche-server -- --cert apps/src/bin/cert.crt --key apps/src/bin/cert.key
(note that the certificate provided is self-signed and should not be used in
production)
Use the --help command-line flag to get a more detailed description of each
tool's options.
Configuring connections
The first step in establishing a QUIC connection using quiche is creating a
Config object:
let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
config.set_application_protos(&[b"example-proto"]);
// Additional configuration specific to application and use case...
The Config object controls important aspects of the QUIC connection such
as QUIC version, ALPN IDs, flow control, congestion control, idle timeout
and other properties or features.
QUIC is a general-purpose transport protocol and there are several
configuration properties where there is no reasonable default value. For
example, the permitted number of concurrent streams of any particular type
is dependent on the application running over QUIC, and other use-case
specific concerns.
quiche defaults several properties to zero, applications most likely need
to set these to something else to satisfy their needs using the following:
set_initial_max_streams_bidi()
set_initial_max_streams_uni()
set_initial_max_data()
set_initial_max_stream_data_bidi_local()
set_initial_max_stream_data_bidi_remote()
set_initial_max_stream_data_uni()
Config also holds TLS configuration. This can be changed by mutators on
the an existing object, or by constructing a TLS context manually and
creating a configuration using with_boring_ssl_ctx_builder().
A configuration object can be shared among multiple connections.
Connection setup
On the client-side the connect() utility function can be used to create
a new connection, while accept() is for servers:
// Client connection.
let conn = quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?;
// Server connection.
let conn = quiche::accept(&scid, None, local, peer, &mut config)?;
Handling incoming packets
Using the connection's recv() method the application can process
incoming packets that belong to that connection from the network:
let to = socket.local_addr().unwrap();
loop {
let (read, from) = socket.recv_from(&mut buf).unwrap();
let recv_info = quiche::RecvInfo { from, to };
let read = match conn.recv(&mut buf[..read], recv_info) {
Ok(v) => v,
Err(e) => {
// An error occurred, handle it.
break;
},
};
}
Generating outgoing packets
Outgoing packet are generated using the connection's send() method
instead:
loop {
let (write, send_info) = match conn.send(&mut out) {
Ok(v) => v,
Err(quiche::Error::Done) => {
// Done writing.
break;
},
Err(e) => {
// An error occurred, handle it.
break;
},
};
socket.send_to(&out[..write], &send_info.to).unwrap();
}
When packets are sent, the application is responsible for maintaining a
timer to react to time-based connection events. The timer expiration can be
obtained using the connection's timeout() method.
let timeout = conn.timeout();
The application is responsible for providing a timer implementation, which
can be specific to the operating system or networking framework used. When
a timer expires, the connection's on_timeout() method should be called,
after which additional packets might need to be sent on the network:
// Timeout expired, handle it.
conn.on_timeout();
// Send more packets as needed after timeout.
loop {
let (write, send_info) = match conn.send(&mut out) {
Ok(v) => v,
Err(quiche::Error::Done) => {
// Done writing.
break;
},
Err(e) => {
// An error occurred, handle it.
break;
},
};
socket.send_to(&out[..write], &send_info.to).unwrap();
}
Pacing
It is recommended that applications pace sending of outgoing packets to
avoid creating packet bursts that could cause short-term congestion and
losses in the network.
quiche exposes pacing hints for outgoing packets through the [at] field
of the [SendInfo] structure that is returned by the send() method.
This field represents the time when a specific packet should be sent into
the network.
Applications can use these hints by artificially delaying the sending of
packets through platform-specific mechanisms (such as the SO_TXTIME
socket option on Linux), or custom methods (for example by using user-space
timers).
Sending and receiving stream data
After some back and forth, the connection will complete its handshake and
will be ready for sending or receiving application data.
Data can be sent on a stream by using the
Read the full README on GitHub