Open-source project
mholt/caddy-l4 avatar
mholt/caddy-l4

mholt/caddy-l4: composable TCP and UDP routing for Caddy

Layer 4 (TCP/UDP) app for Caddy

1,801 stars129 forksGoApache-2.0

At a glance

What is it?
mholt/caddy-l4 adds an experimental layer 4 application to Caddy, with servers, routes, matchers, and handlers for raw TCP and UDP traffic. It fits unusual protocol-routing cases, but its warning about breaking changes makes configuration compatibility the first adoption concern.
Who is it for?
Adopt caddy-l4 when Caddy needs to route non-HTTP TCP or UDP traffic using connection properties or early stream data, and avoid it when experimental configuration changes would outpace your deployment needs. Before adoption, verify the exact module versions, review the documented Echo Server configuration, and confirm that every required matcher and handler exists for the selected release.
Can I use it commercially?
Yes. Apache-2.0 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 3 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 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Where caddy-l4 differs from Caddy's HTTP routing

caddy-l4 addresses deployments that cannot be expressed cleanly through Caddy's HTTP application alone. It listens on raw sockets and makes routing decisions from connection properties or the beginning of a stream. That makes it relevant to TCP echo services, database traffic, SSH, TLS-wrapped protocols, DNS-over-TLS, SOCKS, Proxy Protocol connections, and mixed HTTP and HTTPS endpoints within the same Caddy configuration.

The plugin is intended for operators who want to express low-level traffic policy in Caddy configuration. The README lists actions such as echoing input, forwarding raw bytes to an upstream, terminating TLS, selecting an upstream from HTTP Host or TLS ServerName, blocking selected IP ranges, and throttling data flow. These are connection-level actions rather than ordinary HTTP route actions.

It is not an official repository of the Caddy Web Server organization, although it can operate alongside Caddy apps such as the HTTP server and TLS certificate manager. That distinction matters when judging documentation, release compatibility, and support expectations. The project itself calls the app experimental and warns users to expect breaking changes.

The servers, routes, matchers, and handlers model

The configuration model follows the shape of Caddy's `http` app. You define servers, and each server contains routes. A route has matchers and handlers. When a connection satisfies the route's matchers, the associated handlers are invoked.

This design separates decision criteria from actions. Matchers can determine whether a connection uses TLS, carries HTTP, uses a particular TLS ALPN value, presents a specific ServerName, matches an IP range, or satisfies another documented condition. Handlers then perform operations such as proxying, echoing, TLS termination, or flow control. The README gives examples that combine these features, including terminating TLS before checking for HTTP and selecting different upstreams according to SNI.

The practical advantage is composability. One listener can distinguish several traffic classes rather than relying on separate processes or a rigid chain of port mappings. The trade-off is configuration complexity. A policy expressed as several matchers and handlers needs careful ordering and testing, especially when early stream inspection, TLS termination, and upstream proxying are combined. The repository points to `docs/servers.md`, `docs/routes.md`, `docs/matchers.md`, and `docs/handlers.md` for the supported components.

Building caddy-l4 and checking the first load

The recommended build path is `xcaddy`, which produces a Caddy binary containing the plugin. Run this from an environment where `xcaddy` is already available:

bash
xcaddy build --with github.com/mholt/caddy-l4

The resulting binary should be used as a Caddy binary with the `layer4` modules included. Before writing a traffic policy, you can ask the built executable for its module inventory:

bash
xcaddy list-modules --versions

According to the README, the output should include modules associated with `layer4`. If those modules are absent, the plugin was not included in the binary invoked by the command. This check is more useful than assuming that cloning the repository has registered the app with Caddy, because Caddy plugins are compiled into the executable.

For the first real configuration exercise, use the repository's Echo Server example rather than constructing a matcher and handler chain from memory. The example index identifies `docs/examples/echo_server.md` as the starting point. Its stated outcome is the simplest useful case: accept input on a socket and echo it back to the client. Confirm that listener and Caddy process behavior first, then add proxying, TLS inspection, or more selective matchers.

Configuration formats and protocol-specific examples

The project supports both Caddyfile and Caddy's native JSON format. The README also points to a third-party Caddy JSON schema plugin by `@abiosoft`, which can add completion and documentation while editing JSON configuration. Schema tooling may reduce configuration mistakes, but it does not change the app's experimental status or guarantee that future releases preserve existing keys.

The documentation inventory shows the range of documented use cases. There are dedicated examples for combining apps, DNS-over-TLS, HTTP and HTTPS mixing, IMAPS with Proxy Protocol, mutual TLS, Postgres matching, Postgres over TLS, SOCKS proxying, SSH over TLS, TLS ALPN routing, and dynamic upstreams selected through TLS SNI. These examples are more informative than a generic summary because each protocol depends on a particular sequence of matching and handling.

Routing by TLS ALPN differs from routing by TLS SNI. ALPN selects behavior from a negotiated protocol identifier, while SNI selects an upstream from the requested server name. Mutual TLS introduces another condition because the peer must present a client certificate accepted by the configured trust settings. Users should inspect the specific matcher and handler documentation rather than assuming that all TLS cases are interchangeable.

The dependency list also contains libraries for DNS, Proxy Protocol, Prometheus metrics, SOCKS5, QUIC, rate timing, cryptography, and networking. These dependencies show the implementation's scope, but the README does not claim that every listed capability is exposed as a stable public configuration interface.

Operational limits, compatibility risk, and the wrong tool

The clearest limitation is stated by the README: caddy-l4 is still in development and breaking changes are expected. That warning matters more here than for a conventional reverse proxy because a changed matcher or handler key can alter listener policy after an upgrade. Three releases are listed, v0.1.0, v0.1.1, and v0.1.2, which is consistent with an early-stage module but does not establish interface stability.

The app is also the wrong choice when plain TCP or UDP forwarding is all that is required and a dedicated proxy already provides that function. caddy-l4 earns its extra configuration surface when decisions depend on connection properties, protocol detection, TLS metadata, or an ordered combination of handlers. If port-to-upstream mapping is sufficient, a simpler proxy may reduce operational and upgrade work.

There is another boundary around protocol understanding. This module can inspect the beginning of a stream and route based on documented conditions, but the README does not claim that it recognizes every application protocol or repairs malformed traffic. A successful matcher should not be treated as proof that all later bytes are valid for the selected protocol.

The repository is not archived, and the last push was on 2026-10-15. Recent repository activity shows continued work, but it does not remove the project's explicit warning about incompatible changes. Upgrade planning should therefore include reviewing configuration and matcher behavior rather than merely replacing a binary.

License, dependency footprint, and build ownership

caddy-l4 is licensed under Apache-2.0. Its module is written in Go and declares Go 1.26.0. The top-level `go.mod` directly depends on Caddy v2.11.7 and also lists packages for filesystem notification, DNS, Proxy Protocol, Prometheus, QUIC, SOCKS5, logging, cryptography, networking, and rate limiting.

Because `xcaddy` creates a custom Caddy executable, adopting the plugin means owning that build artifact and its dependency set. The repository contains `integration/`, module packages, and Go module files, but the README does not document a prebuilt binary download or a supported distribution channel. Its documented source for the executable is a local build using `xcaddy`.

Apache-2.0 provides the project's stated licensing terms, with its notice and license documentation in the repository. Dependency licenses remain a separate build-review concern, and this review does not infer permissions or obligations beyond the license identifiers stated in the repository files. Operators changing the Go dependency graph should inspect those files directly.

The latest listed release is v0.1.2, published on 2026-07-16. Release cadence alone cannot establish upgrade safety. The more concrete review target is whether a candidate Caddy version, matcher set, and handler chain produce the intended policy for the deployed traffic.

Choosing caddy-l4 against a plain TCP or UDP proxy

A plain proxy normally treats traffic as a byte stream and forwards it according to listener and upstream addresses. caddy-l4 adds a policy layer above that basic action. It can decide whether to echo or proxy, inspect TLS before forwarding, terminate TLS, identify HTTP after TLS termination, route by SNI or ALPN, or combine these actions in one configuration.

That difference is useful when one endpoint accepts several unrelated protocols. A plain proxy may require separate ports because the repository does not document an equivalent matcher and handler model for caddy-l4. caddy-l4 can express conditional routing where a single listener recognizes the traffic class before choosing an action. It also fits operators who want that policy to remain within the Caddy process and configuration system.

The alternative is simpler when all traffic on a listener goes to the same upstream and no early-stream or TLS-based decision is required. caddy-l4 adds dependency, ordering, and upgrade considerations in exchange for conditional routing. The README documents a broad capability set, but the application remains experimental, so that breadth should not be confused with production stability.

Editorial conclusion

Adopt caddy-l4 when Caddy needs to route non-HTTP TCP or UDP traffic using connection properties or early stream data, and avoid it when experimental configuration changes would outpace your deployment needs. Before adoption, verify the exact module versions, review the documented Echo Server configuration, and confirm that every required matcher and handler exists for the selected release.

Frequently asked questions

How do I install caddy-l4?

The README recommends building a custom Caddy binary with `xcaddy build --with github.com/mholt/caddy-l4`. The plugin is compiled into the resulting Caddy executable rather than installed as a standalone proxy.

Can caddy-l4 route HTTP and non-HTTP traffic?

Yes. Its matchers can inspect connection properties or the beginning of a stream, and its handlers can route matching connections. The README lists HTTP and HTTPS mixing, SSH, Postgres, SOCKS, DNS-over-TLS, and other examples.

Is caddy-l4 suitable for production use?

The README describes the project as experimental and explicitly warns users to expect breaking changes. The repository is not archived and the last push was on 2026-10-15, but recent activity does not remove that compatibility warning.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. mholt/caddy-l4 on GitHub
  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/mholt-caddy-l4.svg)](https://hysenlabs.com/projects/mholt-caddy-l4)

Was this useful?