# fabio: Consul-driven HTTP and TCP routing without a config file

> fabio watches Consul service registrations and turns urlprefix tags into routes, so deploying a new service needs no load balancer edit. The trade-off is that the routing model is the tag format, and there is no documented rollback.

**fabiolb/fabio** — Consul Load-Balancing made simple

- Repository: https://github.com/fabiolb/fabio
- Website: https://fabiolb.net
- Stars: 7,346 · Forks: 623
- Language: Go
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/fabiolb-fabio

## What fabio solves, and who it is for

Most load balancers need a configuration file that someone edits when a service moves. fabio inverts that. The README describes it as a "zero-conf load balancing HTTP(S) and TCP router for deploying applications managed by consul": you register a service in Consul, attach a health check, and fabio begins routing traffic to it. No configuration required, in the project's own words.

The audience is therefore narrow and specific. It is teams that already run Consul as their service registry and want the registry to be the single source of truth for routing. If your services register somewhere else, or if routing decisions are made by hand during a change window, fabio's main advantage disappears and you are left maintaining a Go proxy for no reason.

The project has been in production at scale. The README states it powers gumtree.com.au and has delivered 23.000 req/sec every day since Sep 2015. That is a claim from the project, not an independent measurement, but it is a concrete one.

## How the Consul tag becomes a route

The mechanism is a watch loop over the Consul catalog. Each service instance registers with a unique ServiceID and a service name without spaces. Each instance then carries one urlprefix- tag per host/path prefix it serves. fabio reads those tags, and by default only watches services whose health check is passing, unless that is overridden with registry.consul.service.status.

The tag format carries more than the prefix. The README gives these examples: urlprefix-/css is a path route, urlprefix-i.com/static is a host-specific path route, urlprefix-mysite.com/ is a host-specific catch-all, urlprefix-/foo/bar strip=/foo forwards /bar to the upstream after stripping the prefix, and urlprefix-/foo/bar proto=https selects the upstream protocol. So the route table is not a file you edit; it is the sum of tags across healthy instances, recomputed as Consul changes.

The repository layout reflects that split. There is a registry/ directory for the Consul and Vault integration, a route/ directory for the routing table, a proxy/ directory for the data plane, and an admin/ directory for the WebUI and admin endpoints. The Dockerfile exposes ports 9998 and 9999, which are the admin and proxy ports in the shipped configuration. Routing changes are applied without a restart, which the feature list calls dynamic reloading.

## Installing fabio and routing your first service

The README lists four install paths: from source with Go, a pre-built binary from the releases page, Docker, and Homebrew. The Go path needs Go 1.15 or higher according to the README, while the notes state that release 1.6.1 raised the minimum supported Go version to 1.16.

```bash
go install github.com/fabiolb/fabio@latest
```

On macOS the README also gives brew install fabio for the stable formula and brew install --devel fabio for the development one. The Docker route is a single pull:

```bash
docker pull fabiolb/fabio
```

The shipped Dockerfile builds a scratch image, copies fabio.properties to /etc/fabio/fabio.properties, runs as nobody:nogroup, and starts with -cfg /etc/fabio/fabio.properties. Ports 9998 and 9999 are exposed. If you run the container, mount your own properties file over that path rather than editing the image.

Next, register the service in Consul with a unique ServiceID and a name without spaces, and register a health check. Then attach the route tag. The README gives the tag forms directly:

```
# HTTP/S examples
urlprefix-/css                                     # path route
urlprefix-i.com/static                             # host specific path route
urlprefix-mysite.com/                              # host specific catch all route
urlprefix-/foo/bar strip=/foo                      # path stripping (forward '/bar' to upstream)
urlprefix-/foo/bar proto=https
```

What you should see: once the health check passes, requests matching a urlprefix tag reach the registered instance. If the check is failing, fabio ignores the instance, because passing status is the default filter.

## The limits you inherit with the tag model

The tag-as-route design is elegant until it is not. Route definitions live in Consul, so anyone who can register a service can influence the routing table. There is no separate review step for a route change, because there is no route file to review. That is the point of the design and also its sharpest edge.

The README does not document rollback. There is no described procedure for reverting a bad route beyond changing the tags in Consul again, and no snapshot of the previous routing table is mentioned. If you need a change-control story around routing, you will be building it yourself.

Two version notes matter operationally. From release 1.6.0, the statsd metrics backend is no longer supported; statsd_raw works similarly and resets counters appropriately, and datadog users are pointed at the dogstatsd backend, which supports tags. Graphite histogram behaviour changed with the move to the go-kit framework. Separately, from release 1.5.14 the binary releases are compiled with Go 1.15+, which means fabio no longer validates upstream HTTPS certificates lacking SAN extensions matching the server name. The README names tlsskipverify=true on the route as the workaround, which trades validation away to make a misconfigured backend work.

Finally, the GOGC default changed from 800 back to the Go default of 100 in release 1.5.15. It remains configurable, but if you copied an old properties file that set it, you are now overriding a default the project considers more sensible.

## fabio against a config-file reverse proxy

The obvious alternative is a reverse proxy whose routes come from a file: nginx, HAProxy, or Envoy with static configuration. The difference is where the source of truth lives. With a file-based proxy, a deploy that adds an instance requires editing the proxy configuration and reloading it, and the proxy is a separate artifact with its own review and rollout. With fabio, the instance registers itself and the route appears.

That difference cuts both ways. A file-based proxy lets you express rules fabio's tag grammar cannot, and it keeps routing authority in one place that is version-controlled. fabio gives you less expressive power in exchange for removing the reload step entirely. If your team already has a mature config pipeline for HAProxy or Envoy, fabio is not an upgrade; it is a different trade.

A closer comparison is a service mesh data plane, which also derives routing from a registry and also avoids hand-edited config. fabio predates that category and stays smaller: it is a single Go binary with a properties file, a WebUI, and metrics backends, not a sidecar injected into every pod. If you want per-pod sidecars and mutual TLS between services, fabio is the wrong shape. If you want one router in front of everything, it is the right one.

## Maintenance, releases and the MIT licence

The repository is not archived, and the last push was on 2026-09-18. The most recent release is v1.8.0 from 2026-09-09, following v1.7.3 on 2026-08-06 and v1.7.2 on 2026-07-03. That is a steady cadence over the last few months, and the release notes ship breaking changes with migration guidance rather than silently.

The upgrade cost is concentrated in those notes. Moving from 1.5.x to 1.6.x can break your metrics pipeline if you use statsd, and it changes Graphite histogram behaviour. Moving past 1.5.14 can break HTTPS upstreams whose certificates lack SAN extensions. Each of these is documented, which is the good case. The bad case is the GOGC default change, which is easy to miss if your properties file already sets it.

fabio is MIT licensed, which the README states and the LICENSE file confirms. MIT is permissive: you can use, modify and redistribute it, including in commercial products, provided the copyright notice and permission notice are preserved. That is the general shape of the licence, not legal advice; if you are redistributing fabio inside a product, read the LICENSE text and get your own counsel.

## Conclusion

Adopt fabio if your services already live in Consul and you want routing derived from registration tags rather than a separate proxy config. Do not adopt it if you need a non-Consul service registry or documented rollback of a bad route, because the README does not describe one. Before committing, verify three things: that every instance registers a unique ServiceID and a service name without spaces, that each service has a passing health check, and that your upstream TLS certificates carry SAN extensions matching the server name, since the README states fabio no longer validates certificates without them unless you set tlsskipverify=true on the route.

## FAQ

### What is fabio used for?

fabio is a load balancing HTTP(S) and TCP router for applications registered in Consul. It reads service registrations and urlprefix tags and routes traffic to instances with passing health checks, without a separate configuration file.

### How do I install fabio?

The README lists four paths: go install github.com/fabiolb/fabio@latest, a pre-built binary from the GitHub releases page, docker pull fabiolb/fabio, and Homebrew on macOS with brew install fabio.

### How does fabio decide which services to route to?

It watches Consul and, by default, only routes to services with a passing health check. That filter can be overridden with registry.consul.service.status. Each instance needs a unique ServiceID and a service name without spaces.

### Can fabio route TCP as well as HTTP?

Yes. The feature list includes a raw TCP proxy, a TCP+SNI proxy for end-to-end TLS without decryption, an HTTPS+TCP+SNI proxy with HTTPS fallback, and a TCP dynamic proxy.

### What changed in fabio's recent releases?

The most recent release is v1.8.0 from 2026-09-09, after v1.7.3 on 2026-08-06 and v1.7.2 on 2026-07-03. Older notes record that statsd was dropped in 1.6.0, release hashes moved to a new PGP key in 1.5.14, and the default GOGC returned to 100 in 1.5.15.

## Sources

- [fabiolb/fabio on GitHub](https://github.com/fabiolb/fabio)
- [License: MIT](https://github.com/fabiolb/fabio/blob/master/LICENSE)
- [Project website](https://fabiolb.net)
- [README](https://github.com/fabiolb/fabio/blob/master/README.md)
- [Releases](https://github.com/fabiolb/fabio/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fabiolb-fabio
