Self-hosted service
umputun/reproxy avatar
umputun/reproxy

Reproxy: a small reverse proxy whose routing rules come from Docker, Consul or a file

Simple edge server / reverse proxy

1,309 stars96 forksGoMIT

At a glance

What is it?
A single-binary edge proxy in Go that takes its routing rules from a provider, so the same binary discovers services from Docker labels, from a Consul catalog, from a YAML file or from flags on the command line. The unusual part is the rule format, which packs a server, a source pattern, a destination, a health check, a timeout and a rate limit into one comma-separated string.
Who is it for?
Reproxy suits a team that wants one small binary in front of services that are already discovering each other, especially a Docker or Consul shop where the routing table should not be a second thing to maintain. It does not suit a project that needs a programmable routing language, since the rules are a fixed positional format, and it does not suit a deployment that cannot run a static binary or a scratch-based container.
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 47 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

One binary, several ways to learn the routes

Reproxy is a simple edge HTTP(s) server and reverse proxy supporting various providers, namely docker, static, file and consul catalog. One or more providers supply information about the requested server, the requested URL, the destination URL and the health check URL. It is distributed as a single binary or as a docker container.

The provider model is the interesting part. The rules that decide where a request goes are not in the proxy's configuration file, they come from somewhere else. A docker provider discovers them from running containers, a consul catalog provider discovers them from services and their tags, a file provider reads them from YAML, and a static provider takes them from the command line or the environment.

The README is explicit that each provider may define multiple routing rules for both the proxied request and the static assets, and that a user can set multiple providers at the same time. So the proxy can combine a file of hand-written routes with the services it finds in Docker, and the rule for which one wins is a detail to read in the documentation.

The feature list is long for something described as simple: automatic SSL termination with Let's Encrypt, support for user-provided certificates, per-route basic authentication, IP-based access control, user-defined size limits and timeouts, traffic compression, redirect rules, a limiter for overall activity and for per-user activity, live health check with failover and load balancing, a management server exposing routes info and Prometheus metrics, plugins over RPC, and logging in both Apache Log Format and a simplified stdout report.

The project is MIT licensed, the primary language is Go, the default branch is master, and the last push was on 2026-09-06. The releases are v1.7.1 from 2026-08-20, v1.7.0 from 2026-07-15 and v1.6.0 from 2026-05-12.

How a request is matched, and the regex groups that move it

The routing rules have a precise grammar, and it is worth reading twice because the ordering and the group expansion both surprise people.

The server can be an FQDN, for example `s.example.com`, a `*` catch all, or a regex. Exact match takes priority, so with two rules for `example.com` and `example\.(com|org)`, a request to `example.com/some/url` matches the former.

The requested URL can be a regex, for example `^/api/(.*)`, and the destination URL may have regex matched groups in it, `http://d.example.com:8080/$1`. The example the README gives is that `http://s.example.com/api/something?foo=bar` will be proxied to `http://d.example.com:8080/something?foo=bar`.

There is a convenience expansion. Requests with a trailing `/` and without regex groups are expanded to `/(.*)`, and destinations in those cases are expanded to `/$1`, so `/api/` to `http://127.0.0.1/service` becomes `^/api/(.*)` to `http://127.0.0.1/service/$1`.

Host substitution is also supported in the destination URL. A path of `/files/${host}` is replaced with the matched host name, and `$host` without the braces can be used as well.

The practical consequence is that a rule is a regular expression, not a prefix, and that the group numbering is what wires the source to the destination. A rule where the groups do not line up will still compile and will still route, which is why the README's examples are worth reading as a specification rather than as decoration.

The static provider, and what each positional field does

The static provider is the simplest one, defining all the mapping rules directly in the command line or the environment, and it supports multiple rules. Each rule is three to seven comma-separated elements, in the form `server,sourceurl,destination[,ping-url[,forward-health-checks[,timeout[,throttle]]]]`.

The README's own examples decode the format. A rule of `*,^/api/(.*),https://api.example.com/$1` proxies all requests to any host with an `/api` prefix to `https://api.example.com`. A rule of `example.com,/foo/bar,https://api.example.com/zzz,https://api.example.com/ping` proxies all requests to `example.com` with the `/foo/bar` URL to `https://api.example.com/zzz`, and uses `https://api.example.com/ping` for the health check.

The fifth element forwards the health check to the backend and takes `true`, `yes` or `1`, so `example.com,/foo/bar,https://api.example.com/zzz,https://api.example.com/ping,true` adds that behaviour.

The sixth is a per-route request timeout as a Go duration, `example.com,^/upload/(.*),https://api.example.com/$1,,,5m`, with the fourth and fifth fields left empty to skip the ping URL and the health check forwarding. A value of `0` or empty inherits the global `--timeout.write` setting.

The seventh is a per-route requests per second limit per user, `example.com,^/login,https://api.example.com/login,,,,2`, with the positional fields before it left empty. Zero or empty inherits `--throttle.user`.

Empty positional fields are allowed, and the README uses `,,` for the unused middle fields. That is the sharp edge of the format: a rule is a fixed tuple, and the way to skip a field is to leave a hole in it.

Discovery providers and the rest of the configuration

The four providers are `file`, `docker`, `static` and `consul-catalog`, and each can be enabled independently.

The docker provider does automatic discovery, and the containerised form of the whole thing is a one-liner. The examples given are:

code
reproxy --static.enabled --static.rule="*,example.com/api/(.*),https://api.example.com/$1"
reproxy --docker.enabled --docker.auto
docker up -p 80:8080 umputun/reproxy --docker.enabled --docker.auto

And with automatic SSL added, which is the form most people actually want:

code
docker up -p 80:8080 -p 443:8443 umputun/reproxy --docker.enabled --docker.auto --ssl.type=auto --ssl.fqdn=example.com

The consul catalog provider discovers by service tags, and the file provider reads rules from a YAML file, with the repository shipping `reproxy-example.yml` as the template and a `examples/` directory with subdirectories for assets, docker, file, metrics, plugin, ssl and static.

Starting reproxy requires at least one provider to be defined. The README says the rest of the parameters are strictly optional and have sane defaults.

The other large feature block is the operational side. Multiple virtual hosts are supported, with the server matching on FQDN, wildcard or regex. A built-in static assets server with an optional SPA friendly mode covers the single page application case. There is a management server exposing routes info and Prometheus metrics, which is what the `examples/metrics/` directory demonstrates.

The plugins support via RPC is the extension point, and `examples/plugin/` shows the shape of it, which is how custom functionality is added without forking the proxy.

Distribution, packaging and the container image

Reproxy is distributed as a small self-contained binary as well as a docker image, and both support multiple architectures and operating systems, namely linux_x86_64, linux_arm64, linux_arm, macos_x86_64, macos_arm64, windows_x86_64 and windows_arm. There are also deb and rpm packages for both arm64 and x86.

The install routes are a binary from the release section, Homebrew with `brew install umputun/apps/reproxy`, and docker from Docker Hub or the GitHub Container Registry, `docker pull umputun/reproxy` or `docker pull ghcr.io/umputun/reproxy`. The latest stable version has a `:vX.Y.Z` docker tag with a `:latest` alias, and the current master has a `:master` tag.

The `Dockerfile` is a scratch-based image, which is the smallest sensible choice for a Go binary with no runtime dependencies. The build stage is `golang:1.26-alpine` with `CGO_ENABLED=0`, and it stamps a version into the binary with the git branch, the commit hash and a timestamp, or the CI branch and commit when it is running in CI. The final stage is `FROM scratch`, with the zoneinfo database, the CA certificate bundle, the passwd and group files copied in from a base image, and an environment flag `REPROXY_IN_DOCKER=1`.

There is a comment about the user being implicitly root and a link to the container security section of the project site, which is an honest note about the price of a scratch image: there is no shell in the container, and the process runs as root unless you say otherwise.

The repository also has a `reproxy.service` file for systemd, a `completions/` directory for shell completion, a `scripts/` directory, and a separate `Dockerfile.site` used by the Makefile's site target to build the project website.

The dependency list, and the DNS providers that come with it

The `go.mod` for Go 1.26 is worth reading, because it shows the scope of what the proxy covers beyond HTTP.

The ACME support is `caddyserver/certmagic`, which is what provides the automatic Let's Encrypt certificates, and `golang.org/x/crypto` is there for the rest of the cryptography. The rate limiting is `didip/tollbooth/v7`, and the per-user throttling the rules mention is that library. The logging is `go.uber.org/zap` with `lumberjack` for rotation, and the command line parsing is `umputun/go-flags`, with `gorilla/handlers` for the HTTP middleware and `go-pkgz/rest` and `go-pkgz/repeater` for the management API and the retrying client. `miekg/dns` is a direct dependency, which is for the DNS provider integration.

And then there are twelve `libdns` modules, one per DNS provider: cloudflare, digitalocean, dnsimple, duckdns, gandi, godaddy, hetzner, linode, namecheap, porkbun, route53 and scaleway, plus the libdns core itself. With `aws-sdk-go-v2` pulled in as an indirect dependency for the Route 53 one.

That list is the clearest statement of what the proxy is for. A reverse proxy in front of services that need a public hostname needs to be able to point that hostname at an address, and the way to do that without a control panel is to talk to the DNS provider directly. The auto-discovery, the ACME certificates and the DNS updates are the same job seen from three angles.

The remaining direct dependencies are the ordinary ones for a Go service: `prometheus/client_golang` for the metrics endpoint, `stretchr/testify` for the tests, `yaml.v3` for the file provider.

Where Reproxy is the wrong choice

The first wrong case is a project that needs a programmable router. The rule format is a fixed positional tuple of at most seven fields, and while regex source patterns and regex groups in the destination give you a lot of expressive power, you cannot write arbitrary routing logic, chain conditions, or make a decision based on anything but the host and the path. An nginx or an Envoy configuration is more expressive, and if you need that, use one of those.

The second is a team that already has a service mesh or a cloud load balancer. Reproxy's health checking, failover and load balancing are per-route and simple, and in an environment with an ingress controller they duplicate a component you already run.

The third is a deployment that cannot run a static binary or a scratch container. The image has no shell and runs as root by default, which is a deliberate trade for size, and some platforms are hostile to that.

The fourth is a team that wants a single source of truth for routing and does not have one. The whole model depends on the upstream system of record emitting the right labels or tags. If your services are not already annotated for discovery, you are maintaining the labels and the proxy, which is two places to change instead of one.

The fifth is anyone who expects this to be an ingress controller with a policy language. There is no per-route policy beyond a basic auth option and a per-user throttle, and no integration with an external authorisation system.

What Reproxy does well is the narrow, useful thing of being a very small proxy that discovers its own routes and terminates TLS while it is at it, and that combination is not something the larger proxies do for free.

Editorial conclusion

Reproxy suits a team that wants one small binary in front of services that are already discovering each other, especially a Docker or Consul shop where the routing table should not be a second thing to maintain. It does not suit a project that needs a programmable routing language, since the rules are a fixed positional format, and it does not suit a deployment that cannot run a static binary or a scratch-based container. Before adopting it, read the static provider's rule format carefully, because the fields are positional and a misplaced comma silently changes the health check or the timeout rather than failing, and check whether your upstream already emits Docker labels that match the discovery rules.

Frequently asked questions

What are the providers Reproxy can take routing rules from?

Four: `file`, which reads YAML rules, `docker`, which discovers them automatically from running containers, `static`, which takes them from the command line or the environment, and `consul-catalog`, which discovers them by service tags. Multiple providers can be enabled at the same time, and at least one provider must be defined.

What is the format of a Reproxy static rule?

Three to seven comma-separated elements: `server,sourceurl,destination[,ping-url[,forward-health-checks[,timeout[,throttle]]]]`. The fourth is the health check URL, the fifth forwards health checks to the backend, the sixth is a per-route timeout as a Go duration, and the seventh is a requests per second limit per user. Empty fields are allowed and inherit the global setting.

How does Reproxy handle SSL?

Both HTTP and HTTPS are supported, with static user-provided certificates or automated ACME certificates through Let's Encrypt, which is implemented with certmagic. The docker examples show the automatic form with `--ssl.type=auto --ssl.fqdn=example.com` and the 443 port published.

How do I install Reproxy?

As a binary from the release section for linux, macOS or Windows on several architectures, with deb and rpm packages, via Homebrew with `brew install umputun/apps/reproxy`, or as a container from Docker Hub or the GitHub Container Registry with `docker pull umputun/reproxy` or `docker pull ghcr.io/umputun/reproxy`.

Does Reproxy expose metrics and health information?

Yes. There is a management server that exposes routes info and Prometheus metrics, and the feature list also includes live health checks with failover and load balancing, with an optional per-route ping URL and an option to forward the health check requests to the backend.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. umputun/reproxy on GitHub
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/umputun-reproxy.svg)](https://hysenlabs.com/projects/umputun-reproxy)

Was this useful?