OpenDHT: a C++17 distributed hash table with a Python binding and a REST API
OpenDHT: a C++17 Distributed Hash Table implementation
At a glance
- What is it?
- OpenDHT is a lightweight C++17 implementation of a distributed hash table that gives every node read and write access to a distributed in-memory store with redundancy, a public key layer for optional signing and encryption, and bindings for C, Rust and Python 3. It grew out of Juliusz Chroboczek's original dht and is now maintained by Savoir-faire Linux.
- Who is it for?
- OpenDHT suits a developer who wants a small, embeddable key-value store over a public network rather than a server, for instance a peer to peer application that needs discovery without a central authority, because every node can read and write and the redundancy gives some resilience to churn. It is the wrong tool for application data that must be reliably persisted, since the store is in memory and distributed, and it is the wrong choice if you need a transactional database.
- Can I use it commercially?
- Yes. MIT 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 10 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A DHT as a shared in-memory store
OpenDHT provides an easy to use distributed in-memory data store, and every node in the network can read and write values to the store. Values are distributed over the network, with redundancy.
That sentence defines the model. A distributed hash table is normally described as a lookup structure for finding peers, and the first thing a reader has to understand about OpenDHT is that the authors treat it as a general-purpose key-value store rather than only as a peer discovery mechanism. You put a value under a key, any node can get it, and the data lives wherever the routing algorithm decides to put it.
The feature list gives the engineering constraints. Lightweight and scalable, designed for large networks and small devices. High resilience to network disruption, which is the property the redundancy buys. A public key cryptography layer providing optional data signature and encryption using GnuTLS. IPv4 and IPv6 support. A clean and powerful C++17 map API. Bindings for C, Rust and Python 3. And a REST API with optional HTTP client and server with push notification support.
The public key layer is the part that makes it more than a Kademlia clone. Signing a value binds it to an identity, and encrypting it means a node can store data it cannot read. That turns a lookup table into a store with an access model, and it is why the same codebase is used for private messaging and for Jami, the distributed communications system the bootstrap node in the examples points at.
The project is MIT licensed, with the copyright line reading 2014 to 2026 Savoir-faire Linux Inc., and it was originally based on jech/dht by Juliusz Chroboczek.
The C++ example, and what a node actually does
The C++ example in the README is short and it is the clearest statement of the API.
#include <opendht.h>
#include <vector>
int main()
{
dht::DhtRunner node;
// Launch a dht node on a new thread, using a
// generated RSA key pair, and listen on port 4222.
node.run(4222, dht::crypto::generateIdentity(), true);
// Join the network through any running node,
// here using a known bootstrap node.
node.bootstrap("bootstrap.jami.net", "4222");
// put some data on the dht
std::vector<uint8_t> some_data(5, 10);
node.put("unique_key", some_data);
// put some data on the dht, signed with our generated private key
node.putSigned("unique_key_42", some_data);
// get data from the dht
node.get("other_unique_key", [](const std::vector<std::shared_ptr<dht::Value>>& values) {
for (const auto& value : values)
std::cout << "Found value: " << *value << std::endl;
return true; // return false to stop the search
});
// wait for dht threads to end
node.join();
return 0;
}Five things happen. A DhtRunner is created, which is the object that owns the node. run() launches it on a new thread with a generated RSA key pair and a port to listen on. bootstrap() joins the network through a known node, and the two arguments are the host and the port. Then put() and putSigned() write values, get() reads them back through a callback, and join() waits for the threads to finish.
The get callback returning a bool is worth pausing on. Returning true continues the search, returning false stops it. That is how a caller bounds a lookup, and it is the mechanism you use when you already have what you need and do not want the network traffic to keep going.
The distinction between put and putSigned is the whole point of the crypto layer in one line. A plain put is anonymous data that anybody can replace, and a putSigned is a value whose signature ties it to the generated private key, so a consumer can verify who published it and that it was not modified.
The tools directory carries the same API as command line programs. dhtnode is a command line tool for running a DHT node and performing operations supported by the library, get, put and so on, with text values. dhtchat is a very simple IM client working over the dht.
Two Python APIs, and the one that fits an event loop
The Python binding is installed with one command.
pip install opendhtIt works on Linux, macOS and Windows, and there are two ways to use it. The first is the simple, blocking API.
import opendht as dht
node = dht.DhtRunner()
node.run()
node.bootstrap("bootstrap.jami.net", "4222")
node.put(dht.InfoHash.get("unique_key"), dht.Value(b'some binary data'))
results = node.get(dht.InfoHash.get("unique_key"))
for value in results:
print(value)The comment on the put call notes that it is a blocking call, and that providing callback arguments makes the call non-blocking. So the blocking API is really the same API with the callbacks optional.
The second is the asyncio version, in the opendht.aio module, and the README shows a fuller example.
import asyncio
import opendht.aio as dht
async def dht_async_demo(key_str: str):
async with dht.DhtRunner(
bootstrap=(("bootstrap.jami.net", "4222"),)
) as node:
key = dht.InfoHash.get(key_str)
await node.put(key, dht.Value(b'tata data'))
results = await node.getAll(key)Three parts of that example are worth more than the rest. The node is an async context manager, so entering the with block starts it and leaving it shuts it down, and the README notes that it is also possible to call run() and await shutdown() manually. The bootstrap argument is passed at construction as a tuple of host and port pairs rather than through a separate call. And there are three different read shapes: getAll() collects everything, get() used as a context manager streams values as they arrive from the network, and listen() watches a key for changes, yielding each value together with an expired flag so you can react to a value being replaced as well as to new ones appearing.
That last one is what makes OpenDHT usable for live data rather than only lookups. A listen subscription gives you a push feed of changes at a key, which is the primitive a peer to peer application needs.
The dependency set, and the optional REST layer
The core dependencies are small and specific.
msgpack-c 1.2 or later is used for data serialization, and the choice of a C library for that is not accidental in a DHT, because the wire format is where a lightweight implementation wins or loses. GnuTLS 3.3 or later is used for cryptographic operations, Nettle 2.4 or later is a GnuTLS dependency for crypto, and fmt 9.0 or later is for log formatting.
The optional dependencies exist for the REST API, which the README describes as the DHT proxy client and server, and they are restinio, llhttp, jsoncpp 1.7.4 to 3 or later, and simdutf.
That is a notable set of choices. restinio is a C++ HTTP framework, llhttp is the HTTP parser from Node, jsoncpp is the JSON library, and simdutf is the fast Unicode validation library that jsoncpp can use. So the REST layer is not a thin wrapper over the C library, it is a full HTTP server stack with JSON and UTF-8 handling, and it is optional so that a pure DHT user does not pay for it.
The build environment is documented in detail. The build is tested with GCC 9 or later on GNU/Linux and on Windows with MinGW, and with Clang and LLVM on GNU/Linux, Android, macOS and iOS. It is also tested with Microsoft Visual Studio 2019 and 2022.
The list of toolchains is a good proxy for who uses the library, and the presence of iOS and Android alongside Windows and the Unix platforms says this is not only a server-side component.
The repository supports several build systems side by side. There is a CMakeLists.txt with CMakePresets.json, a meson.build with meson.options, and a vcpkg.json with vcpkg-configuration.json for the vcpkg package manager. There is also a .devcontainer/ directory, a .clang-format and .clang-tidy for formatting and linting, a doc/ directory, and separate language directories for the bindings, with c/, python/ and rust/ at the root alongside src/ and include/ for the C++ core.
Running a public node, and why that is a commitment
The documentation section points at the wiki and gives one instruction that deserves more attention than it gets.
You can help contribute to the public network by running a stable node with a public IP address.
That sentence is a request, and it describes what the public DHT actually is. The bootstrap.jami.net host in the examples is a node, and a network of this kind is only as good as the nodes that stay up. A DHT with churn, with nodes behind NAT, with nodes that disappear, degrades into something that stores data nobody can find.
So the request for a stable node with a public IP is a real operational burden, and it is worth recognising that running one is contributing infrastructure rather than running a service for yourself. The wiki has a page on running a node with dhtnode.
The high resilience to network disruption claim in the feature list is what the redundancy is for, and the two are related. Redundancy means several nodes hold a copy of a value, so losing one does not lose the data. That is a mitigation for churn, not a substitute for a stable population.
The build instructions are in BUILD.md, and the acknowledgement that this project was originally based on jech/dht by Juliusz Chroboczek is worth noting for a different reason. That original dht was the basis for the Bittorrent DHT, so the code lineage runs straight through one of the largest decentralised systems ever deployed, and the current project carries a decade of protocol compatibility in its wire format.
Version 4.x, with three releases in one day
The release history shows v4.4.0, v4.3.0 and v4.2.0 all tagged on 2026-09-04, and the last push was on 2026-10-13, so the tree is being worked on.
Three releases on the same afternoon is not the pattern of a project shipping features steadily. It is the pattern of a project that had to cut several releases in sequence, most likely because a packaging or dependency problem was found and each fix needed its own version, or because the maintainer was preparing a coordinated push across the several language bindings in the same repository.
The repository has separate directories for the C, Python and Rust bindings alongside the C++ core, and a single version number applies to all of them. A mistake in a shared layer therefore forces a coordinated release, and a burst of same-day tags is the natural result.
The 4.x numbering also tells you this is a mature project with a stable wire protocol, since a DHT's interoperability depends on the participants all speaking the same protocol version, and the fact that a major version has been reached without the project being abandoned says the protocol has been stable across a long period.
The licence is MIT, with the copyright line covering 2014 to 2026 for Savoir-faire Linux Inc., and the LICENSE file is at the root. There is an AGENTS.md at the root as well, plus a CHANGELOG in the repository history and the usual tooling for a project with a large multi-language build.
The practical takeaway for an adopter is that the bindings and the core move together, so upgrading one means upgrading all of them, and the release burst in September is a good reminder to read the changelog for that window before pinning a version.
Editorial conclusion
OpenDHT suits a developer who wants a small, embeddable key-value store over a public network rather than a server, for instance a peer to peer application that needs discovery without a central authority, because every node can read and write and the redundancy gives some resilience to churn. It is the wrong tool for application data that must be reliably persisted, since the store is in memory and distributed, and it is the wrong choice if you need a transactional database. Before you adopt it, read the node-running guide in the wiki, because a public node needs a stable address and a long uptime, and check the build instructions in BUILD.md since the dependency set includes GnuTLS and msgpack-c that have to be present before the library compiles.
Frequently asked questions
What is OpenDHT?
It is a lightweight C++17 distributed hash table implementation that provides an easy to use distributed in-memory data store, where every node in the network can read and write values, distributed over the network with redundancy.
How do I install the OpenDHT Python binding?
Run pip install opendht, which works on Linux, macOS and Windows. The binding is published on PyPI as opendht and offers both a simple blocking API and an asyncio API in the opendht.aio module.
What are the core dependencies of OpenDHT?
msgpack-c 1.2 or later for data serialization, GnuTLS 3.3 or later for cryptographic operations, Nettle 2.4 or later as a GnuTLS dependency, and fmt 9.0 or later for log formatting. The REST API adds optional restinio, llhttp, jsoncpp and simdutf.
What is the difference between put and putSigned in OpenDHT?
put stores an anonymous value that anybody could replace, while putSigned stores a value signed with the node's generated private key, so a consumer can verify the publisher and that the value was not modified.
How do I watch a key for changes in OpenDHT?
Use the listen() method on a node, which in the asyncio API is used as a context manager and yields each value together with an expired flag, so you see both new values and replacements.
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/savoirfairelinux-opendht)Was this useful?