Self-hosted service
andrearaponi/dito avatar
andrearaponi/dito

Dito: a Go reverse proxy with signed, loadable plugins

an advanced reverse proxy server written in Go

639 stars26 forksGoNOASSERTION

At a glance

What is it?
Dito is a layer 7 reverse proxy written in Go that adds hot configuration reloading, per-location transport control, response size limits, Prometheus metrics and a plugin system whose plugins are signed with Ed25519 keys and verified at startup. It suits platform teams that want to extend a proxy in Go, and it asks for a very specific build environment to make that work.
Who is it for?
Adopt Dito if you need a proxy you can extend from Go with hot reloading, explicit transport and response control, and signed plugins, and you can commit to a Linux build environment with CGO enabled. Do not adopt it if you need a proxy that runs on Windows, if you cannot rebuild plugins against the exact Go toolchain of the server binary, or if you need a mature, versioned release.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 21 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 October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A layer 7 proxy designed to be extended from Go

Dito is described as an advanced, highly extensible reverse proxy server written in Go, and the interesting part of that sentence is the word extensible. Most reverse proxies are configurable; this one is programmable.

The feature list is divided into what a proxy does and what its plugins add. The base capabilities are a layer 7 reverse proxy handling HTTP and HTTPS, WebSocket proxying, dynamic configuration reloading so configurations can be updated without restarting the server, custom TLS certificate management with support for mTLS and custom certificates for backend connections, header manipulation to add or remove HTTP headers, asynchronous logging with customisable verbosity, custom transport configuration fine-tuned per location or globally, response body size limits with a 413 status code when exceeded, response buffering control that can be enabled or disabled per location, and Prometheus metrics.

The plugin system is where the project puts its weight. Plugins are custom Go plugins that can add authentication mechanisms, implement caching strategies, apply rate limiting, transform requests and responses, and add custom logging. The README also notes that plugins can manage their own dependencies and provide custom middleware functionality, which is the detail that makes the system coherent: a plugin is not a script bolted onto the request path, it is a component with its own module boundary that the proxy loads and calls.

The project structure in the repository supports that reading. There is a plugin/ directory for plugin loading, signing and verification, a plugins/ directory for the implementations, a middlewares/ directory for the built-in middlewares that plugins can extend, and separate transport/, websocket/, writer/, metrics/ and logging/ packages. The deployments/ directory holds Kubernetes, OpenShift and Docker Compose setups, and configs/templates/ holds configuration templates.

The homepage is ditoproxy.com, the primary language is Go, and the go.mod file declares the module as dito with Go 1.27.1.

Ed25519 signatures and the toolchain constraint that comes with Go plugins

Two facts about the plugin system define what you can realistically do with Dito, and they are related.

The first is the security model. Plugins are signed using Ed25519 keys, and Dito verifies these signatures at startup. That is a real design decision, and a good one. A proxy that loads code at startup is a proxy that will load whatever is placed in its plugin directory, so requiring a signature from a key the operator controls turns an arbitrary code execution path into a key-management problem, which is a much better problem to have. The Makefile shows where the keys live, with the public and private key files named bin/ed25519_public.key and bin/ed25519_private.key, and there are separate development and production variants of the key generation and signing targets so that a development key never ends up in a production deployment.

The second fact is the toolchain constraint. The installation section says to ensure you have Go, version 1.27.1 or newer, and make installed, and then states that plugins must be built with exactly the same Go toolchain as the Dito binary.

That requirement comes from how Go plugins work. A Go plugin is not a portable component, it is a compiled artefact that has to share a runtime with its host, and the build has to match closely enough for the two to agree on internal runtime state. A plugin built with a different toolchain will either fail to load or behave unpredictably, and the README does not describe a graceful fallback.

The container build confirms how seriously this is taken. The Dockerfile is a multi-stage build on the Red Hat UBI 8 go-toolset, pinned by tag and digest, and it sets GOTOOLCHAIN to go1.27.1 so the go command downloads the official toolchain. The comment above it explains that Red Hat does not ship a Go 1.27 go-toolset yet, so the bootstrap image is only used to fetch the real toolchain, and that the builder stays on UBI 8 so that the CGO-enabled binaries, required by Go plugins, link against the same glibc as the ubi-minimal runtime image. The build sets CGO_ENABLED=1, GOOS=linux and GOARCH=amd64, and the final comment on the file repeats the rule that the host binary and plugins must be built with the exact same toolchain.

So the honest summary is that Dito's plugin model buys you in-process extensibility and signed artefacts, and charges for it with a build environment you have to keep uniform. If your plugin build drifts from the server build, you find out at startup.

Hot reload, per-location transport and bounded responses

The base proxy is where most day-to-day work happens, and three of its features are the ones worth understanding before you deploy it.

Dynamic configuration reloading is the one the README leads with. Update configurations without restarting the server. In a Kubernetes or OpenShift deployment that matters more than it does on a laptop, because a restart of a proxy pod is a blip in every connection it is carrying. The config/ package is described as the configuration loader and hot-reload, and the deployment targets include update-config and update-prod-config commands that write the key paths and hashes into bin/config.yaml, plus a fix-config command to repair configuration after a setup, and a debug-config command to work out what is wrong with a configuration. That is a lot of tooling around a single YAML file, which is what you would expect from a project where the configuration is the primary interface.

Custom transport configuration is the second. The transport/ package holds the HTTP transport settings, and the feature is described as fine-tuning HTTP transport settings per location or globally. Per location is the interesting part, because different upstreams want different behaviour, connection pooling and timeouts, and a proxy that only offers global transport settings forces you to run several instances to serve several backends.

Response body size limits are the third, and they are a security control as much as a resource control. The README specifies control of maximum response body sizes globally and per location with proper error handling, using a 413 status code. A proxy that will buffer an arbitrarily large body from an upstream is a memory risk, and per-location limits let you apply them where they matter. The companion feature is response buffering control, which can be enabled or disabled per location for performance, and that pairs with the writer/ package of custom response writers and buffering.

Together with header manipulation, the Prometheus metrics and the asynchronous logging, the base proxy covers the checklist most people actually have for a production edge: control what comes in, control how it is fetched, control how big it may be, and see what happened. The plugin system then exists for the things that are specific to your deployment.

Getting Dito running with make quick-start

The installation path is designed to be one command, and the make targets carry most of the documentation value in this project.

The prerequisites are Go 1.27.1 or newer and make. Then the recommended quick start is two lines:

bash
# Clone repo
git clone https://github.com/andrearaponi/dito.git && cd dito

# One-command setup & start
make quick-start

The README explains what that command does, and the list is the best summary of the plugin architecture you will find. It builds all binaries, Dito and the plugin-signer. It generates Ed25519 keys. It builds and signs the plugins. It updates the configuration with the correct paths and hashes. And it starts the Dito server.

The step-by-step route separates the same work into three commands, which is what you want when something fails and you need to see where:

bash
# 1. Clone repo
git clone https://github.com/andrearaponi/dito.git && cd dito

# 2. Setup (build, keys, plugins, config)
make setup

# 3. Start server
make run

For a production setup there is a parallel set of targets. setup-prod does the full production setup with persistent keys and a production configuration, generate-prod-keys creates a persistent key pair, sign-plugins-prod signs with the production keys, update-prod-config writes the production key paths and hashes, and update-k8s-config creates configs/config-prod-k8s.yaml for a Kubernetes deployment.

There is also a manual path for anyone who does not want the make layer. The build of the binary itself is a single go build command:

bash
go build -o bin/dito ./cmd/dito/main.go

with a note in the README that the path to main.go must be correct, for example ./cmd/dito/main.go if main.go is in cmd/dito/. The build output goes to bin/, and the Makefile also points the config file at cmd/config.yaml while the update targets write to bin/config.yaml, so the paths differ between the manual and scripted routes and it is worth checking which one you are on before you start editing configuration.

The make targets that reveal how the project works

The Makefile target table is long, and a few entries say more about the project than the README does.

The CI section has two targets. ci runs every CI check except the image build, stopping at the first failure. ci-selftest is described as proving that every CI gate fails on the defect it must catch. A self-test for the gates is an unusual thing to ship, and it is a strong signal about the project's priorities: the authors care that a lint rule, a test or a scan actually fires when it should, not just that it exists.

The development section has test to run the tests, vet to run go vet on the main module and on the plugins, fmt to format the code, and sonar to run SonarQube analysis, with the Sonar host URL and project key defined in the Makefile. The debugging section has debug-config, and the cleanup section has clean and clean-plugins.

The e2e target runs the end-to-end scenarios of the proxy, with an SCENARIO environment variable that selects which ones run. The repository has an e2e/ directory at the top level, so end-to-end behaviour is a first-class part of the project rather than an afterthought.

The lint story is also worth noting. lint-all shows every golangci-lint issue, including the pre-existing ones, which implies that the default lint run is scoped to new code, and that there is a known backlog. The repository has a .golangci.yml at the root, and renovate.json for dependency update proposals, and a .walden/ directory whose purpose is not explained in the documentation.

The OpenShift targets are extensive: deploy-ocp for a complete production deployment, deploy-ocp-dev for a quick development deployment, status-ocp and logs-ocp for checking on it, and clean-ocp to remove the resources. That set, together with the OpenShift production deployments in deployments/openshift/ and the UBI-based container images, tells you where this project expects to run. The topics list agrees, with docker, kubernetes and microservices among them, and the description of the project is simply an advanced reverse proxy server written in Go.

Where Dito is the wrong tool

The first limitation is the one already discussed but is worth stating as a decision point: Go plugins, and the exact toolchain match they require. Teams that build plugins in a separate pipeline, on a different machine, or on a different Go version will hit a startup failure. If your extension model is a sidecar, an external service, or a configuration change, Dito's plugin system is not the mechanism you want, and you are paying for the complexity of a mechanism you will not use.

The second is the platform. The container build sets GOOS=linux and GOARCH=amd64, the runtime image is a Red Hat UBI minimal base, and the deployment targets are Kubernetes, OpenShift and Docker Compose. There is no Windows path in the documentation, and the OpenShift-first framing tells you the shape of the intended environment.

The third is maturity. The releases are 0.6.0, a licence update to Apache 2.0, dated 2024-12-31, 0.7.0, the plugin system, dated 2025-05-30, and 0.7.5, response limits and enhanced error handling, dated 2025-06-08. The last push was on 2026-10-02. A project at 0.7.x whose headline feature landed in May 2025 is pre-1.0, and the version scheme is the honest signal.

The fourth is the documentation. The README opens with a note that the primary documentation for Dito will be migrated to an mdBook format, with a request for patience. So the documentation is in transition, and the details that matter for a proxy, such as the full configuration reference, the plugin API contract and the deployment topology, are not all in the file you have read.

The fifth is what a proxy does not do. Dito is a layer 7 proxy. It does not terminate TLS for clients out of the box in the way an ingress controller does, it does not manage DNS, it does not do service discovery, and it does not provide the dashboard and policy model of an API gateway. If you need those, you are composing Dito with other pieces, and the plugin system is where that composition has to happen.

Licence, notices and what the release history says

The licence picture needs a careful reading, because the signals do not all point the same way. The README badge says Apache 2, and the release named 0.6.0 is literally called License Update to Apache 2.0. But the repository's own licence field records a custom licence that GitHub cannot classify, and there is both a LICENSE file and a NOTICE file at the root of the repository. This article does not interpret any of those documents, but a project whose licence field and badge disagree is a project whose licence you read rather than assume, and a NOTICE file usually exists because the distribution includes third-party code whose attribution has to travel with it.

The rest of the repository is the picture of a serious infrastructure project. There is a .golangci.yml, a renovate.json, a .dockerignore, a Dockerfile, a docker-build.sh, an e2e/ directory, tools.mk holding the tool versions, a Makefile that separates the verification targets, an app/ directory, handlers/, config/, plugin/, plugins/, transport/, websocket/, writer/, logging/, metrics/ and scripts/ directories, and a configs/ directory with templates.

The go.mod is short and tells you the dependency surface: fatih/color for terminal output, gorilla/websocket for the WebSocket support, lmittmann/tint for structured logging, the Prometheus client library and its model package for the metrics, stretchr/testify for tests, and yaml.v3 for configuration. A reverse proxy that depends on a WebSocket library, a YAML parser and a metrics client is not doing anything exotic, which is a good sign.

The development practice is the strongest signal in the repository. The Makefile has a self-test target that proves each CI gate catches the defect it exists for, a lint target that can show the pre-existing backlog, a scenario-selectable end to end suite, and separate OpenShift lifecycle commands for deploying, inspecting, tailing logs and cleaning up. Those are the habits of someone who has had to operate this in production.

So the sensible way to approach Dito is as a capable, opinionated proxy with a genuine extension model and a well-disciplined build, and to budget time for the two things it does not hide: the exact toolchain coupling of the plugin system, and a documentation set that is still being reorganised.

Editorial conclusion

Adopt Dito if you need a proxy you can extend from Go with hot reloading, explicit transport and response control, and signed plugins, and you can commit to a Linux build environment with CGO enabled. Do not adopt it if you need a proxy that runs on Windows, if you cannot rebuild plugins against the exact Go toolchain of the server binary, or if you need a mature, versioned release. Verify first that the plugin toolchain requirement is acceptable, that the licence position is settled, since the README badge and the release notes say Apache 2.0 while the repository field records a custom licence, and that the documented configuration for your use case exists, because the project states its documentation is being migrated to an mdBook format, the last push was on 2026-10-02, and the newest tag is 0.7.5 from 2025-06-08.

Frequently asked questions

How do I install and run Dito?

The README asks for Go 1.27.1 or newer and make, then git clone https://github.com/andrearaponi/dito.git && cd dito and make quick-start, which builds the binaries, generates Ed25519 keys, builds and signs the plugins, updates the configuration and starts the server. The step-by-step route is make setup followed by make run.

How does Dito secure its plugins?

Plugins are signed using Ed25519 keys and Dito verifies the signatures at startup. The Makefile provides separate development and production key generation and signing targets, with the key files held as bin/ed25519_public.key and bin/ed25519_private.key.

What does Dito add beyond basic reverse proxying?

Beyond HTTP and HTTPS proxying and WebSocket support, the README lists dynamic configuration reloading, custom TLS certificate handling including mTLS for backend connections, header manipulation, asynchronous logging, custom transport settings per location or globally, response body size limits returning 413, per-location response buffering control, Prometheus metrics, and a Go plugin system for authentication, caching, rate limiting, request and response transformation and custom logging.

Which licence does Dito use?

The README badge and the 0.6.0 release notes point to Apache 2.0, while the repository licence field records a custom licence that GitHub cannot classify, and the repository contains both a LICENSE file and a NOTICE file. The recent tags are 0.7.5 from 2025-06-08, 0.7.0 from 2025-05-30 and 0.6.0 from 2024-12-31, with the last push on 2026-10-02.

Official sources

  1. andrearaponi/dito on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/andrearaponi-dito.svg)](https://hysenlabs.com/projects/andrearaponi-dito)

Was this useful?