coredhcp: a DHCP server where the request path is a plugin chain you configure
Fast, multithreaded, modular and extensible DHCP server written in Go
At a glance
- What is it?
- coredhcp is a Go DHCP server whose features are plugins, so a request is evaluated by calling each plugin in order until one answers or drops it. That makes DHCPv4 and DHCPv6 services composable in YAML, at the cost of a project the README still calls a work in progress.
- Who is it for?
- coredhcp fits an operator or developer who needs DHCP behaviour that the stock daemon does not offer, in particular a mix of DHCPv4 and DHCPv6 services in one process, driven by a YAML file where the order of plugins is the policy.
- 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?
- Activity is slowing. The repository last received commits 6 months 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 October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The plugin order in the config is the request policy
coredhcp is a DHCP server written in Go, and its defining design choice is stated plainly: in CoreDHCP almost everything is implemented as a plugin. The order of plugins in the configuration matters, because every request is evaluated by calling each plugin in order until one breaks the evaluation and either responds to or drops the request.
That is a small sentence with a large consequence. A DHCP request becomes a pipeline rather than a switch statement, and the YAML file becomes an executable policy. Moving the file plugin above the dns plugin changes what a client is told, because the first plugin to break the chain decides the outcome.
It also explains why the project is described as modular and extensible. The core plugins live under the plugins/ directory, and additional plugins are published separately in the coredhcp/plugins repository. A plugin can supply a lease source, a server identifier, DNS servers, a static allocation, a hook into an external system, or a test hook that only prints packets.
The repository layout reflects the same structure at the top level: server/ for the listener and dispatch, handler/ for the per message type logic, plugins/ for the built in plugins, config/ for the configuration plumbing, cmds/ for the binaries, logger/ for logging, and integ/ for integration tests.
The cost of this design is that behaviour is not obvious from the binary. Two servers with the same plugin list in a different order are different servers, and reading a coredhcp configuration means reading the plugin implementations, not just the file.
A DHCPv6-only server in nine lines of YAML
The example configuration in the project documentation is short enough to read in full, which is the best argument the design has.
server6:
# this server will listen on all the available interfaces, on the default
# DHCPv6 server port, and will join the default multicast groups. For more
# control, see the `listen` directive in cmds/coredhcp/config.yml.example .
plugins:
- server_id: LL 00:de:ad:be:ef:00
- file: "leases.txt"
- dns: 8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844Three plugins do the work. server_id sets the server identifier with a link layer type and a hardware address, file points at the lease store, and dns hands the client a resolver list that mixes IPv4 and IPv6 addresses. Everything else, the listening interface, the port, the multicast groups, comes from the server6 block and its defaults.
Note the separate server6 block. The configuration is keyed by server, so a process can host a DHCPv4 server and a DHCPv6 server side by side with different plugin lists, which is the reason to choose this project over a single protocol daemon in the first place.
For anything beyond this, the documentation points at config.yml.example in cmds/coredhcp, which is where the listen directive lives, so the example above is a starting point and not the whole configuration language.
Building the example server and watching the plugin chain load
The server binary lives under cmds/coredhcp, so the build happens in that directory, and the run needs root because it binds the privileged DHCP ports.
cd cmds/coredhcp
go build
sudo ./coredhcpThe startup log is worth reading as documentation in its own right, because it shows the order of operations. The server registers each plugin by name, loads the configuration, reports the arguments it found for each plugin, then loads the plugins one at a time. The file plugin logs that it is reading leases from leases.txt and how many leases it loaded. Then the listener starts.
INFO[2019-01-05T22:28:07Z] Starting DHCPv6 listener on [::]:547
INFO[2019-01-05T22:28:07Z] WaitingTwo things to notice. The plugin loading is sequential and reported per plugin, so a configuration problem shows up in the log before any packet arrives. And the listener binds the wildcard address on the default DHCPv6 port 547, which is the multicast, all interfaces behaviour the configuration comment describes.
There is a matching test client under cmds/client, built the same way, which sends a SOLICIT and prints the message it constructs: the message type, the transaction id, the client identifier with its DUID and hardware address, the requested options, the elapsed time, and the IANA option with the IAID and the t1 and t2 renewal times. That is the fastest way to see the protocol layer without a client on the network.
The go.mod requires Go 1.24.0 with a go1.24.2 toolchain directive, so the toolchain matters for a source build.
The Docker path runs privileged and publishes both UDP ports
There is a Dockerfile and a docker-compose.yml, and the compose file is short enough to state exactly what it does.
services:
coredhcp:
privileged: true
build:
context: .
dockerfile: Dockerfile
ports:
- 67:67
- 547:547
volumes:
- ./etc/coredhcp/:/etc/coredhcp/The container is privileged and both the DHCPv4 and DHCPv6 ports are published over UDP. The configuration directory is mounted from the host, and the documentation says docker compose expects a configuration file under /etc/coredhcp/config.yaml, mapped to ./etc/coredhcp/config.yaml on the host. You can change the exported volume in docker-compose.yml to point at a different host path. The example configuration file in cmds/coredhcp is the stated starting point.
The container entrypoint passes the configuration path explicitly.
CMD coredhcp --conf /etc/coredhcp/config.yamlThe Dockerfile is where the caveats live. It starts from ubuntu:22.04, installs build tooling, downloads the Go toolchain from golang.org as a tarball, and that download is pinned to go1.23.4.linux-amd64. The go.mod in the repository asks for Go 1.24.0 with a go1.24.2 toolchain, so the image's toolchain and the module's declared requirement are not the same version. The build also sets CGO_ENABLED=1, which matters because the module depends on a SQLite driver, mattn/go-sqlite3, that needs cgo, and that is why build-essential is installed.
So the image is convenient for evaluation and carries a version skew you will want to reconcile if you build it yourself.
A generator for custom plugin sets, and an example plugin as the tutorial
Two mechanisms let you change what the binary contains.
The first is the coredhcp-generator tool under cmds/coredhcp-generator, which builds a server with a custom set of plugins. That matters for a specific reason: if plugins are linked in at build time, then shipping a plugin to a site that runs the stock binary means shipping a new binary. The generator turns the plugin list into the build, so the deployment artifact is defined explicitly rather than implied by whatever happened to be imported.
The second is the example plugin under plugins/example, and the project says the best way to learn to write a plugin is to read its comments and source. It implements a simple plugin that prints a packet every time one is received by the server, which is exactly the right shape for a first exercise: you can see the request arrive, the arguments the plugin was constructed with, and the point at which a plugin can decide to answer, to drop, or to pass the request on.
The dependency list explains the shape of the server. The DHCP protocol work comes from insomniacslk/dhcp, and packet handling and inspection from google/gopacket. Configuration goes through spf13/viper with spf13/pflag and spf13/cast, so the YAML file is parsed by viper. Logging is logrus with a logrus-prefix helper and rifflock/lfshook for file output. github.com/vishvananda/netns suggests network namespace handling, fsnotify suggests file watching, and bits-and-blooms/bitset is a dependency worth noting because it is a data structure rather than a DHCP concern.
The result is a server whose configuration is data, whose behaviour is code you can add, and whose binary is a build artefact you control.
No GitHub releases and a README that still says work in progress
The project carries an honest disclaimer at the top of its own README: this is still a work in progress. That is a useful signal, and it is more useful than it looks, because the project also publishes a homepage at coredhcp.io and a documentation site, so there is somewhere authoritative to look beyond the repository.
What the repository shows about maintenance is thinner. There are no GitHub releases, so there is no versioned artefact to download and no changelog to read in the usual place. The last push was on 2026-04-13, which is more than six months before the date of this article, and the repository is not archived. The default branch is master, and the continuous integration badge in the README points at Travis CI, with codecov and Go Report Card badges alongside it and a stickler configuration file in the tree, which suggests an older continuous integration setup rather than a current one.
What that means in practice depends on your risk appetite. If you need a DHCP service where an unattended upgrade is unacceptable, a dated release tag and a signed artefact would be the sensible requirement, and this repository does not offer them. If you need a DHCP service whose behaviour you can read, patch and rebuild from source, the situation is different: the code is on GitHub, the go.mod is pinned to specific dependency versions including a pseudo version of the DHCP library, and the plugin architecture means you can change behaviour without forking the whole server.
The licence is MIT, with a LICENSE file at the root, so running it in a lab or a homelab carries no copyleft obligation.
Against a monolithic DHCP daemon, the difference is where the policy lives
The realistic alternative is a traditional DHCP server with a fixed feature set, where behaviour is selected through configuration keywords and new capabilities require the daemon itself to change. That is the fundamental difference in approach, and it is worth being concrete about what it means on the ground.
With a plugin chain, the behaviour of a request is a function of the order of your YAML list. You can put a plugin that overrides a client identifier before the plugin that assigns a lease, or after it, and get different results. You can drop a plugin in that talks to an external system, or one that only logs, without touching the server. The cost is that every behaviour is now a function of a chain you have to reason about, and a misordered list is a bug that does not look like a bug.
With a monolithic daemon, behaviour is a function of a configuration file with a defined vocabulary, and the vocabulary is stable because it is shipped with the binary. The cost there is that anything the vocabulary does not cover needs a patch to the daemon.
For a lab, a home network, or a testing environment where you want DHCPv4 and DHCPv6 in one process with a handful of custom behaviours, the plugin chain wins. For a production network where the address plan, failover and lease persistence are the whole job, the maturity of a conventional daemon is a bigger factor than flexibility.
One more practical difference: the file plugin reads leases from a text file, and the module also pulls in a SQLite driver. A deployment that needs a real lease store, or DHCP failover, has to supply it, and the project does not document those cases as built in.
Editorial conclusion
coredhcp fits an operator or developer who needs DHCP behaviour that the stock daemon does not offer, in particular a mix of DHCPv4 and DHCPv6 services in one process, driven by a YAML file where the order of plugins is the policy. It does not fit an operator who wants a packaged appliance, a stable long term release cadence, or the reassurance of a maintained upstream, because there are no GitHub releases and the last push was on 2026-04-13, more than six months before this article, with the README still describing the project as a work in progress. Check three things before you rely on it. Check the lease backend you need, because the built in file plugin reads leases from a text file and the go.mod also carries a SQLite driver, so confirm which store you are actually configuring. Check the plugin order in your config, since order is the evaluation order and a plugin that breaks the chain ends the request. And check the deployment shape, because the Docker path runs the container privileged and publishes both 67 and 547 over UDP, and the Dockerfile builds with a Go toolchain that predates the go.mod requirement. The licence is MIT, so there is no copyleft obligation on a deployment that uses it.
Frequently asked questions
How do I build and run the coredhcp example server?
Enter cmds/coredhcp, run go build, then run the binary with sudo so it can bind the privileged DHCP ports. The go.mod requires Go 1.24.0 with a go1.24.2 toolchain. A matching test client lives under cmds/client.
What does the coredhcp plugin order actually control?
Every request is evaluated by calling each plugin in the configured order, until one breaks the evaluation and either responds to the request or drops it. So the order of the plugin list in the YAML file decides the request policy.
Can coredhcp serve DHCPv4 and DHCPv6 from the same process?
The configuration is keyed by server, with a server6 block shown for a DHCPv6-only server. The example configuration file in cmds/coredhcp/config.yml.example is the place to look for the listen directive and further server blocks.
How do I add a feature to coredhcp?
Write a plugin. The project points to the example plugin under plugins/example as the guide, since it implements a plugin that prints a packet on every request received. Built in plugins live under plugins/ and more are published in the coredhcp/plugins repository.
How does the coredhcp Docker image run?
The compose file runs the container privileged, publishes UDP ports 67 and 547, and mounts ./etc/coredhcp/ to /etc/coredhcp/, where the configuration file is expected. The container starts with coredhcp --conf /etc/coredhcp/config.yaml.
What license is coredhcp released under?
MIT, with a LICENSE file at the root of the repository. The repository has no GitHub releases, and the last push was on 2026-04-13.
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/coredhcp-coredhcp)Was this useful?