# tokio-tungstenite: Async WebSockets for the Tokio Stack

> tokio-tungstenite wraps the tungstenite WebSocket library in Tokio's non-blocking I/O, and the README is candid that performance is capped by the layer underneath. Here is what it does, how to get a first connection running, and where it stops being the right pick.

**snapview/tokio-tungstenite** — Future-based Tungstenite for Tokio. Lightweight stream-based WebSocket implementation

- Repository: https://github.com/snapview/tokio-tungstenite
- Stars: 2,512 · Forks: 270
- Language: Rust
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/snapview-tokio-tungstenite

## What tokio-tungstenite is for, and who ends up using it

WebSocket work in Rust splits into two layers: the protocol itself (framing, masking, the handshake, close codes) and the I/O that carries the bytes. tokio-tungstenite sits at the seam. It does not implement the protocol; it takes tungstenite, which does, and provides Tokio bindings and wrappers so the socket can be a non-blocking TcpStream from the Tokio stack. The README states this directly: the crate "provides Tokio bindings and wrappers for it, so you can use it with non-blocking/asynchronous TcpStreams from and couple it together with other crates from Tokio stack."

That positioning tells you the audience. If you are building a service that already runs on Tokio and you want to speak WebSocket to a peer, you want a Stream of incoming messages and a Sink for outgoing ones, and you are prepared to write your own connection loop, this is the natural dependency. If you want a framework to own the upgrade, route the request and hand you a socket inside a handler, you are looking one level up. The crate is a transport, not an application server.

## How the tungstenite core and the Tokio layer divide the work

The repository layout shows the split plainly. The src/ directory holds the Tokio-side code, and the Cargo.toml declares tungstenite as a dependency at the same version as the crate itself, with default features off. Feature flags then decide what gets pulled in. The default feature set is connect plus handshake, and connect itself expands to stream, tokio/net and handshake. So a plain dependency gives you the ability to open a connection and complete the WebSocket handshake without any TLS provider attached.

TLS is opt-in and comes in three shapes. The native-tls feature wires in the native-tls crate and tokio-native-tls; rustls-tls-native-roots and rustls-tls-webpki-roots both enable a private __rustls-tls feature, which pulls rustls, rustls-pki-types, tokio-rustls and tungstenite's own rustls support, and the two differ in where root certificates come from: the platform store or the webpki-roots bundle. The README is explicit that neither is enabled by default, and that you must enable one of them if you require secure WebSockets over wss://.

The data flow follows from that: bytes arrive on a Tokio TcpStream, optionally pass through a TLS wrapper, and are decoded by tungstenite into messages that the wrapper exposes through futures-util's Sink and Stream traits. The dependency list confirms the pieces: log, futures-util with the sink and std features, and tokio with io-util only. There is no runtime baked in beyond what tokio itself brings, which is why the README points new users at Tokio's own documentation if they have not worked with it before.

## Installing tokio-tungstenite and opening a first connection

The README gives exactly one installation step: add the crate to your Cargo.toml. The version it names is 0.30.

```toml
[dependencies]
tokio-tungstenite = "0.30"
```

That gets you the default features, which as noted above means connect and handshake but no TLS. If your target is a wss:// URL, add one of the TLS features. The README names native-tls, rustls-tls-native-roots and rustls-tls-webpki-roots, and warns that enabling rustls with version 0.23.0 or higher may produce a panic that the linked issue describes and fixes.

```toml
[dependencies]
tokio-tungstenite = { version = "0.30", features = ["rustls-tls-webpki-roots"] }
```

For a working example, the repository ships an examples/ directory rather than putting a full listing in the README. It contains client.rs and echo-server.rs alongside autobahn-client.rs, autobahn-server.rs, interval-server.rs, server.rs, server-custom-accept.rs and server-headers.rs, with an examples/README.md describing them. The README's instruction is to "take a look at the examples/ directory for client and server examples." Copy the pattern from examples/client.rs for a connecting client and examples/echo-server.rs for a listener, and check Cargo.toml for the feature list rather than guessing at flag names, because the README does not enumerate them beyond the TLS trio. Note also that the package metadata declares rust-version 1.85 and edition 2018, so an older toolchain will fail before any of your code runs.

## The performance ceiling is inherited, and the README says so

Most crates in this position write a performance paragraph that promises more than it can show. This one does the opposite. The README states that "in essence, tokio-tungstenite is a wrapper for tungstenite, so the performance is capped by the performance of tungstenite," and then concedes that tungstenite is "definitely not the fastest WebSocket library in the world at the moment of writing this note." It names fastwebsockets as the comparison point and says the maintainers know what changes both crates would need to close the gap.

There is a partial update in the same paragraph: versions above 0.26.2 are described as more performant and "should be more on-par with fastwebsockets." Treat that as the maintainers' own claim rather than a measured result, because no benchmark numbers appear in the README, and the linked comment is where the pending work is tracked. The practical reading is that if your workload is dominated by WebSocket frame throughput, you should benchmark against fastwebsockets on your own traffic before committing, and if your workload is ordinary request-response or pub/sub traffic, the ceiling is unlikely to be what limits you. The honest framing here is unusual and worth crediting: the crate tells you where it stands instead of implying parity.

## The rustls panic and other reasons to pick something else

The most concrete failure mode in the README concerns TLS. If you use the rustls features with version 0.23.0 or higher, the README says you "might observe a panic" and links both the issue and a discussion comment describing the fix. That is a real constraint on feature selection: a rustls build is not simply a drop-in swap for native-tls, and you should read the linked issue before choosing roots. The README does not document rollback behaviour for any of this, so if you need a documented recovery path you will not find one here.

The second limitation is scope. This crate has no router, no middleware, no request extractors and no server framework. If your application needs to distinguish paths, authenticate during the handshake or share state across handlers in a structured way, you are writing that yourself on top of a Stream and Sink. That is a deliberate design boundary, not an oversight, but it means the crate is the wrong tool when what you actually want is an HTTP framework with WebSocket support attached.

The third is the toolchain floor. rust-version 1.85 is declared in Cargo.toml, and the crate uses edition 2018. Neither is a problem for a current project, but both matter if you are maintaining something pinned to an older compiler.

## How tokio-tungstenite differs from axum's WebSocket support

The comparison people reach for is axum, and the difference is one of layer rather than quality. axum is an HTTP framework built on hyper; its WebSocket support lives in an extractor that upgrades an incoming request inside a route handler, so routing, path parameters, middleware and shared application state are all handled before your socket code runs. tokio-tungstenite has none of that. It gives you connect_async-style construction on the client side and a Stream/Sink pair on both sides, and you decide how connections are accepted and what happens to them.

The trade-off is direct. Choosing tokio-tungstenite means you own the accept loop, the connection bookkeeping and any per-connection state, which is more code but also more control: nothing sits between your socket and the runtime, and you can drive it from any Tokio task without a framework's opinions about request handling. Choosing axum means less boilerplate and a coherent story for an HTTP API that also needs a WebSocket endpoint, at the cost of pulling in the framework and fitting your socket into its handler model. If you are building a pure WebSocket client, or a server whose only job is WebSocket, the framework buys you little. If WebSocket is one endpoint among many in an HTTP service, the framework earns its weight. Searches around this crate also surface tokio-websockets and async-tungstenite as comparisons; the README does not describe either, so treat those as separate evaluations rather than something this crate's documentation settles.

## Maintenance, licensing and what upgrading costs you

The repository is not archived, and the last push was on 2026-07-11, which is recent enough that the project is being touched. The README's own performance paragraph refers to improvements "merged" over past years from community contributors, and the CHANGELOG.md at the repository root is where version history lives. The version to depend on is the one the README names, 0.30, and the changelog is the file to read before bumping.

Upgrade cost concentrates in the feature flags. Because TLS providers are opt-in and the rustls path carries a documented panic on 0.23.0 and above, a version bump that changes the TLS wiring is the change most likely to break a working build, and the CHANGELOG plus the linked issues are the places that record it. The dependency surface is small: log, futures-util and tokio for the core, plus whichever TLS stack you enable. There is no bundled runtime and no code generation step.

The licence is MIT, declared in Cargo.toml and shown in the README badge, with the LICENSE file at the repository root. MIT is permissive, so the usual obligations are around retaining the copyright notice and licence text in distributions. That is a general property of the licence, not advice about your situation; read the LICENSE file and your own legal requirements rather than treating this paragraph as guidance.

## Conclusion

Adopt tokio-tungstenite when you already have a Tokio runtime and want WebSocket frames as a Stream and Sink rather than a framework-managed endpoint; skip it if you need routing, extractors or per-route upgrade handling, because those belong to axum and similar frameworks, and skip it if you have measured a throughput ceiling that fastwebsockets already clears. Before writing production code, confirm your Rust toolchain is at least 1.85, decide between native-tls and the two rustls root options, and read the issue the README links about a rustls panic on 0.23.0 and above, since that is the failure you are most likely to hit first.

## FAQ

### What is tokio-tungstenite?

It is a Rust crate that provides Tokio bindings and wrappers for the tungstenite WebSocket library, so WebSocket connections can run over non-blocking Tokio TcpStreams. The README describes it as asynchronous WebSockets for the Tokio stack, and notes that performance is capped by tungstenite underneath.

### How do I use tokio-tungstenite?

Add tokio-tungstenite = "0.30" to your Cargo.toml, enable one of the TLS features if you need wss://, and follow the client and server examples in the repository's examples/ directory. The README points new users at Tokio's own documentation if they have not worked with the runtime before.

### How does tokio-tungstenite compare with axum for WebSockets?

axum is an HTTP framework whose WebSocket support upgrades a request inside a route handler, so routing and middleware come with it. tokio-tungstenite is only the transport layer: it exposes a Stream and Sink, and you write the accept loop and connection handling yourself.

## Sources

- [Issues](https://github.com/snapview/tokio-tungstenite/issues)
- [License: MIT](https://github.com/snapview/tokio-tungstenite/blob/master/LICENSE)
- [README](https://github.com/snapview/tokio-tungstenite/blob/master/README.md)
- [snapview/tokio-tungstenite on GitHub](https://github.com/snapview/tokio-tungstenite)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/snapview-tokio-tungstenite
