rumqtt: a Rust MQTT broker that does not speak MQTT 5
The MQTT ecosystem in rust
At a glance
- What is it?
- rumqtt is two crates in one repository, a client and an embeddable broker, and the single most important line in its readme is an unchecked box. The client supports version 5 of the protocol and the broker does not, so the two halves of the same project cannot use the newer protocol between them, and the readme offers no explanation of what happens instead. Everything else, five install paths and a two stage container build, is comparatively straightforward.
- Who is it for?
- rumqtt is the right choice if you are writing a Rust service and want MQTT in the same binary, either as a client or as a broker you embed rather than operate, because a broker you can link into your process is something the general purpose brokers do not offer and it is the reason this project exists.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 154 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two crates, two version lines, and a protocol the halves do not share
The project is two libraries in one repository, and the table at the top of the readme is the whole catalogue. One is a high level, easy to use client. The other is a high performance, embeddable broker. Two consequences follow from that, and the second one is the reason to read this project carefully. The first is that they version independently, which the release record shows directly: the recent tags are for the client at version zero point two five point one, the broker at zero point twenty, and the client again at zero point two five. So you are tracking two version lines that are five minor versions apart, and a dependency on the client says nothing about the broker you are about to point it at. The second consequence is the feature lists, and the difference between them is stark. The client list has two entries: version 3 point 1 point 1 of the protocol, supported, and version 5, supported. The broker list has eight entries, seven supported and one not: version 3 point 1 point 1, both delivery quality levels zero and one, connections over TLS, retransmission after reconnect, a last will, retained messages, and delivery quality level two, all supported, and version 5 of the protocol, not supported. So the client in this project can speak the newer protocol and the broker in this project cannot, and the readme does not say what a client does when it connects to a broker that only offers the older one. The usual answer is that the connection negotiates downwards and the newer features simply are not available, which for a lot of deployments is fine, because the two features people most often want from the newer version are shared subscriptions and message expiry, and both are absent from an implementation that does not have it.
Seven lines of broker features, and everything not on the list is unknown
The feature lists are the specification in this project, and they are short enough to read in full, which is a virtue until you notice what is missing from them. For the broker, the list covers the protocol version, the two lower delivery quality levels, TLS, retransmission after reconnect, the last will, retained messages, and the second delivery quality level. What it does not mention includes anything about persistence, session state, wildcards and shared subscriptions, topic aliases, flow control, message expiry, or administrative controls. That does not mean those are absent, and the honest way to read the list is that an unlisted capability is unknown rather than unimplemented, and the readme offers no other place to find out. The broker's own documentation is deferred to a separate file inside the broker's directory, and the client's to a file inside the client's, so the top-level readme is an index rather than a reference and the detail is one click away. The word embeddable in the broker's one-line description deserves its own emphasis, because it is the capability that distinguishes this project from the brokers it is competing with. Embeddable means you can link the broker into your own Rust program and run it in the same process, on the same event loop, with no separate service to deploy, monitor, secure or keep alive. That is genuinely useful for a small deployment, for a test harness, or for an application that wants to expose an MQTT endpoint without operating one, and it is the reason to look at this project even if the protocol version worries you.
Five install paths, three config locations, two spellings of the same flag
The installation section is the longest part of the readme and it documents five ways to get the broker running, which is generous and also slightly disorganising. The first is a container image from a public registry, and the run command publishes two ports and attaches a terminal. The second is a prebuilt binary from the releases page, which you move into a directory on your path. The third installs the broker with the Rust package manager, but from the git URL rather than from a registry, and then fetches a sample configuration file over HTTPS with a curl invocation that pins the protocol and a minimum TLS version, redirects it into the current directory, and starts the broker against it. The fourth is an Arch User Repository package installed with an AUR helper, with the note that the helper can be any of them. The fifth is compiling from a shallow clone, which runs the broker straight from the working tree against the configuration file in the repository and turns the logging all the way up.
cargo install --git https://github.com/bytebeamio/rumqtt rumqttd
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/bytebeamio/rumqtt/main/rumqttd/rumqttd.toml > rumqttd.toml
rumqttd --config rumqttd.tomlOn Arch the helper is a one-liner, and the client is a registry dependency in either language:
paru -S rumqttd-bin
cargo add rumqttcThree things in that list are worth flagging. The install-from-git instruction means you are building from a branch rather than from a published, immutable artefact, which is a reproducibility decision you should make deliberately by pinning a revision. The Arch package puts its configuration at one path and registers a service under a name that does not match the binary, since the service is named for the project rather than the executable, and anyone writing a unit reference needs to know that. And the note after the sample configuration says to make sure you correct it for the specific version of the broker you are running, which is an honest warning with no supporting information: the readme does not say which keys changed between versions or how to tell which one you have.
A two stage musl build, one unexplained port, and the log level the packaging picks
The container image is ten lines and worth reading line by line. The builder stage starts from a Rust image on Alpine, installs a compiler toolchain plus the OpenSSL development headers and a protocol buffer compiler, copies the whole source tree, and builds the broker in release mode for that package specifically. The final stage starts from a clean Alpine image and copies in one file, the broker binary, into the path. There is no configuration file in the image, no shell utilities, and no user directive, so the process runs as root inside the container and the only thing you can do in there is run the broker. All of that is fine for a single-purpose image and it means the configuration has to be mounted, which is exactly what the second run command in the readme does.
docker pull bytebeamio/rumqttd
docker run -p 1883:1883 -p 1884:1884 -it bytebeamio/rumqttdAnd with a configuration file of your own mounted in:
docker run -p 1883:1883 -p 1884:1884 -v /absolute/path/to/rumqttd.toml:/rumqttd.toml -it rumqttd -c /rumqttd.tomlThat command also publishes two ports, and only one of them is the port everyone recognises for this protocol. The second is undocumented in the readme, and for a broker the usual reason to open a second port is a listener for browser clients, which means a second attack surface you did not choose. Confirm what it is before you expose it. The other detail is the one that will change your debugging experience. The image sets a default log level through an environment variable, at the informational level, while the source-build instructions pass three verbosity flags. So the same broker logs almost nothing when containerised and everything when you run it from a checkout, and if you are trying to diagnose a connection problem in a deployment, the first thing to change is that variable rather than the broker.
The broker installs from git, which is a reproducibility decision
There is a small inconsistency between the table at the top of the readme and the instructions below it, and it is the kind of thing that matters in production. The table gives both crates a badge linking to their page on the Rust registry, which implies the broker is published there as a library. The installation instructions, however, install the broker binary from the git URL rather than from the registry, and the client instruction is the opposite, a plain add of the published crate. So there are two different supply chains in one project. The client is a versioned registry dependency with a lock file entry and a checksum, which is what you want. The broker binary, installed the documented way, is a build of whatever the branch contained when you ran the command, and if you do that on ten machines over a week you may get ten different binaries. The fix is small and entirely on your side. Note the commit you built, build from that commit, and keep a record of it alongside the configuration you are running, because the readme's own advice about version-specific configuration means you need to be able to say which code and which configuration went together. The alternative is to use the prebuilt binary from the releases page, which is at least a fixed artefact, or the Arch package, which is built by someone else and pinned by that distribution. The source-build path in the readme is a development convenience, since it runs from a shallow clone with logging turned up, and it is not a deployment story.
Benchmarks are workspace members, which means a second router is being measured
Two directories in the repository are listed as workspace members alongside the two crates, and both are benchmarks. One is a benchmarks directory and the other is a second benchmarks subdirectory for something called a simpler router. That name is the interesting part. A second router implementation inside the benchmarks workspace, rather than a second crate, says that the broker's routing layer has a simpler variant that somebody wanted to measure against the real one, which is a strong hint about where the performance effort is going. It also means the benchmarks are compiled as part of the workspace, so a benchmark that does not build is a build failure, which is a slightly unusual and mildly healthy arrangement. The repository also carries a coverage service badge, a continuous integration workflow, a cross-compilation configuration for targets outside the host, a changelog, a contributing guide, and a shell script for building the container image alongside the Dockerfile itself, which means the image can be produced in more than one way and the script is the one to read if you are reproducing a build. Support channels are a chat server, two professional networks, and a company blog, and the contributing section asks you to read the code of conduct before opening an issue and the contributor guide to get a sense of what the maintainers would and would not accept. That second request is a good sign, and the phrasing is unusually blunt about it, which tells you the project is selective about changes and would rather discuss a feature before you write it.
Build profiles chosen for CI disk space and for a binary you distribute
The workspace manifest is short and each line has a reason attached, which makes it a good illustration of how build configuration reflects constraints rather than preference. The development profile turns debug information off, and the comment says why: producing it takes up a lot of space on the runner and it is not currently being used. That is a decision made for the benefit of continuous integration rather than for the developer, and it is the kind of comment that saves the next person from wondering whether the omission was an accident. The release profile does the opposite kind of tuning, setting the compilation to a single unit so the optimiser can see the whole program, enabling link time optimisation, and stripping the symbols out of the result, which is exactly right for a binary you are going to distribute through a container image and a package repository.
[profile.release]
codegen-units = 1
lto = true
strip = trueThe workspace uses the 2021 edition of the language and version 2 of the dependency resolver, which is a project that started before the current edition was available and has not moved, and there is nothing wrong with that. One named author is recorded in the shared package metadata, alongside the repository and the licence. The licence is Apache 2 with a licence file at the top of the repository, which for a network-facing broker with TLS support is the permissive grant most organisations will accept without discussion, and unlike several projects in this set there is no ambiguity about what you may do with the code.
Editorial conclusion
rumqtt is the right choice if you are writing a Rust service and want MQTT in the same binary, either as a client or as a broker you embed rather than operate, because a broker you can link into your process is something the general purpose brokers do not offer and it is the reason this project exists. It is the wrong choice if you need version 5 of the protocol end to end, because the client supports it and the broker does not, and no amount of client configuration changes what the broker will accept. Two things to settle before you commit. Which protocol version your deployment actually needs, since the broker's list of supported features is seven lines long and an unlisted feature is unknown rather than absent. And which config file goes with which release, because the readme warns that the sample configuration is version specific and provides no table saying what changes, and three different install paths expect the file in three different places under two different flag spellings. If you take it forward, run it behind a network boundary you control, watch the log level the packaging chooses for you, and pin the git revision rather than the branch.
Frequently asked questions
Does the rumqtt broker support MQTT 5?
No, and the feature list marks it as the one unsupported item. The client does support MQTT 5. A client from this project connecting to this project's broker will therefore negotiate the older protocol, and the newer features are not available between them. The readme does not explain the negotiation behaviour.
What does embeddable mean for the broker?
You can link the broker into your own Rust program and run it in the same process rather than deploying a separate service. That is the capability that separates this project from the general purpose brokers its topic tags place it against, and it is useful for small deployments, test harnesses and applications that want to expose an endpoint without operating a broker.
How do I install the broker?
Five documented paths: a container image from a public registry, a prebuilt binary from the releases page, an install from the git URL using the Rust package manager, an Arch User Repository package, or a build from a shallow clone. The git path builds from a branch rather than a published artefact, so pin a revision if you need a reproducible binary.
What are the two ports the container publishes?
The run command in the readme publishes two, and only one is the port normally associated with this protocol. The second is not explained anywhere in the readme, so confirm what it serves before exposing it, since a second listener on a broker is a second surface you did not choose.
Is the configuration file version specific?
The readme warns you to make sure the sample configuration is corrected for the specific version of the broker you are running, and provides no table of what changed. Three install paths also expect the file in three different locations under two different flag spellings, so check the path your chosen path expects.
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/bytebeamio-rumqtt)