hashicorp/yamux: a Go multiplexer that turns one TCP or Unix socket into thousands of streams
Golang connection multiplexing library
At a glance
- What is it?
- Yamux is a stream-oriented multiplexing library for Go that sits on top of a reliable, ordered connection. It is small, it ships a spec, and it leaves connection management, reconnection and security to you.
- Who is it for?
- Adopt yamux when you already own a reliable connection and need many logical streams over it without writing your own framing, and when you are willing to handle reconnection, TLS and session lifecycle yourself. Skip it if you need an encrypted transport, connection migration, or interoperability with SPDY or HTTP/2, since the README states yamux is not interoperable with SPDY.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 33 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What yamux is for, and who ends up using it
Yamux is a stream multiplexing library for Go. The README describes it as relying on an underlying connection to provide reliability and ordering, such as TCP or Unix domain sockets, and then providing stream-oriented multiplexing on top. That division of labour is the whole design. Yamux does not encrypt, does not reconnect, does not do name resolution and does not negotiate anything. It takes a connection that already delivers bytes in order and turns it into many independent, bidirectional streams.
The intended audience is Go engineers building a protocol or a proxy layer where one long-lived connection carries many concurrent logical conversations. The README lists server-side push and streams opened by either side, and notes that server-initiated streams are useful for NAT traversal. That matters if you are behind a NAT and the client cannot accept inbound connections: the server can open a stream back over the connection the client already established. Keep alives are listed as a feature that enables persistent connections over a load balancer, which tells you the authors expect deployments where an idle connection gets reaped by an intermediary.
It is not an application framework. There is no session manager, no reconnect loop, no address book. If you want those, you build them or you use a project that already built them on top of yamux.
How the framing and flow control actually work
The mechanism is a session object wrapping a net.Conn. On the client side you call yamux.Client(conn, nil) and on the server side yamux.Server(conn, nil); both return a session. The session exposes Open() to create a stream and Accept() to receive one. A stream implements net.Conn, so existing code that reads and writes bytes works unchanged once you hand it a stream instead of a socket.
The repository layout supports this: session.go holds the session type and its accept loop, stream.go holds the per-stream state machine, mux.go and const.go hold the framing constants, and addr.go provides the address type used by the net.Conn interface. The README states that the full specification lives in spec.md and that the file is intended as a guide for implementors of interoperable libraries. That spec is the real contract. If you are writing a peer in another language, spec.md is what you read, not the Go source.
Flow control is described in the README as preventing starvation and providing back-pressure so a receiver is not overwhelmed. In practice that means the sender cannot keep writing into a stream that the peer is not draining; the window closes and the write blocks. This is the property that makes thousands of streams over one connection survivable, because a slow consumer on one stream does not let a fast producer on the same stream consume the whole connection's bandwidth. The README also claims thousands of logical streams with low overhead, which is a design goal rather than a measured result.
One structural consequence worth naming: because all streams share one underlying connection, head-of-line effects at the transport layer still apply. If the TCP connection stalls, every stream stalls. Yamux removes the cost of many connections; it does not remove the failure domain of one.
Installing yamux and opening your first stream
The module path is github.com/hashicorp/yamux and go.mod declares go 1.23. Add it with the standard module command; the README points to Godoc for complete documentation rather than reproducing an install section.
go get github.com/hashicorp/yamuxThe README's usage example is the shortest path to a working pair. On the client, dial a connection, wrap it with yamux.Client, then call session.Open() to get a stream.
conn, err := net.Dial(...)
if err != nil {
panic(err)
}
session, err := yamux.Client(conn, nil)
if err != nil {
panic(err)
}
stream, err := session.Open()
if err != nil {
panic(err)
}
stream.Write([]byte("ping"))The second argument is the configuration, and the README passes nil for the default. The server side mirrors it: accept the connection, wrap it with yamux.Server, and call session.Accept() to receive the stream the client opened.
conn, err := listener.Accept()
if err != nil {
panic(err)
}
session, err := yamux.Server(conn, nil)
if err != nil {
panic(err)
}
stream, err := session.Accept()
if err != nil {
panic(err)
}
buf := make([]byte, 4)
stream.Read(buf)After the client writes four bytes and the server reads into a four-byte buffer, the server has the payload. From there you treat the stream as a net.Conn: pass it to a codec, wrap it in a bufio.Reader, or hand it to an HTTP server as a listener. The repository also ships a Makefile target for the test suite, which is the fastest way to confirm the library builds and passes under the race detector on your machine:
make testThat target runs go test -v -race ./... according to the Makefile.
Where yamux is the wrong tool
Yamux assumes the underlying connection is reliable and ordered. The README says so directly. If you are on a datagram transport, or on a link that drops and reorders, yamux will not repair that for you; its framing and flow control assume the bytes arrive intact and in sequence. Putting yamux on top of UDP without a reliability layer underneath is a category error.
There is no encryption. Yamux does not mention TLS anywhere in the README, and nothing in the top-level file list suggests a crypto layer. If your streams cross an untrusted network, TLS or another secure channel is your responsibility, applied to the underlying connection before yamux wraps it.
There is no reconnection or session recovery. When the underlying connection dies, the session dies with it, and every stream on it fails. The README does not document rollback, resumption, or stream migration. Applications that need to survive a network change, such as a laptop moving from Wi-Fi to cellular, will not get that behaviour here. QUIC was designed with connection migration as part of the transport; yamux explicitly is not that, and a search for yamux versus QUIC is really a question about two different layers.
Finally, interoperability is bounded. The README states yamux is inspired by SPDY but is not interoperable with it. So a peer that speaks SPDY or HTTP/2 framing cannot talk to a yamux session. Cross-language use is possible, but only against spec.md, and only if the other implementation follows it precisely.
Yamux compared with smux and with QUIC
The closest alternative in the same niche is smux, another Go stream multiplexer. Both take a net.Conn and expose streams, and both are used as the muxing layer inside larger Go networking stacks. The difference is in the contract. Yamux ships spec.md and states that the specification is provided as a guide for implementors of interoperable libraries, which makes it a candidate when a non-Go peer must speak the same protocol. Whether smux offers an equivalent written specification is not something the yamux material answers, so treat that as the thing to check before choosing between them.
QUIC is a different layer entirely, and the comparison is worth stating plainly because it comes up in search. QUIC bundles transport, encryption and multiplexing together and runs over UDP. Yamux bundles none of those; it sits above a connection that something else established. Choosing QUIC means adopting a full transport with its own handshake and TLS integration. Choosing yamux means keeping TCP or Unix sockets and adding one library for stream fan-out. They can coexist in the same system, but they are not substitutes.
Within the Go ecosystem yamux has a second life as a component. Searches for libp2p yamux and libp2p go yamux reflect that: yamux is one of the multiplexers available to libp2p-based stacks, where the transport is chosen separately and yamux provides the stream layer. If you are already in that ecosystem, the decision is largely made for you by the stack's configuration.
Maintenance, licence and upgrade cost
The repository is not archived, and the last push was on 2026-08-28. The most recent tagged release listed is v0.1.2 from 2024-09-24. That gap between the last push and the last tag is normal for a library that changes rarely, but it also means you should read CHANGELOG.md and the commit history rather than assuming a release will accompany a fix.
The module is versioned below 1.0, which under Go module semantics means no compatibility promise across minor versions. In practice the API surface is tiny (Client, Server, Open, Accept, and net.Conn on streams), so the blast radius of an upgrade is small, but the version number is the honest signal about what the maintainers have committed to. Pin a version in go.mod and read the changelog before bumping.
The build tooling is pinned deliberately. The Makefile installs copywrite at a specific commit tagged v0.25.3 and golangci-lint v2 at a specific commit, and the default target runs copywrite headers --plan, lint and test in that order. Contributors will need those exact tools; the Makefile's deps target installs them. Running the default target on a machine without them fails at the first step.
Licence is MPL-2.0, a file-level copyleft licence. Modifications to yamux's own source files carry obligations; code that merely imports the library is generally treated differently, but the boundary depends on your use and jurisdiction. This is not legal advice, and if you are embedding yamux in a distributed product, have counsel read the MPL-2.0 text rather than relying on a summary.
Editorial conclusion
Adopt yamux when you already own a reliable connection and need many logical streams over it without writing your own framing, and when you are willing to handle reconnection, TLS and session lifecycle yourself. Skip it if you need an encrypted transport, connection migration, or interoperability with SPDY or HTTP/2, since the README states yamux is not interoperable with SPDY. Before committing, read spec.md and confirm that the framing and flow-control rules match the peer implementation you intend to talk to, then run make test to confirm the race detector passes on your platform.
Frequently asked questions
What is yamux?
Yamux is a multiplexing library for Go that relies on an underlying connection such as TCP or a Unix domain socket for reliability and ordering, and provides stream-oriented multiplexing on top of it. Streams are bidirectional and can be opened by either side, and each stream implements net.Conn.
How does yamux compare with QUIC?
They operate at different layers. Yamux assumes a reliable, ordered connection already exists and only adds stream multiplexing, flow control and keep alives. QUIC is a transport in its own right, so choosing between them is a question of whether you want a muxing library above your connection or a replacement transport below it.
Is yamux compatible with SPDY or HTTP/2?
No. The README states that yamux is inspired by SPDY but is not interoperable with it. Cross-implementation compatibility depends on a peer following the rules in spec.md.
Does yamux encrypt the streams it opens?
The README does not describe any encryption layer. Yamux wraps an existing connection, so if the traffic crosses an untrusted network, securing that underlying connection is left to the caller.
Can yamux streams be opened by the server?
Yes. The README lists bi-directional streams that can be opened by either client or server, and notes that server-initiated streams are useful for NAT traversal and server-side push.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hashicorp-yamux)
Community notes