CA

caddy-l4

Layer 4 proxy module for the Caddy server

Reverse Proxy & Gateways ★ 1.8k stars Medium setup Apache-2.0

Caddy-L4 is an experimental application-layer-aware Layer 4 proxy app for the Caddy server, enabling TCP and UDP routing alongside Caddy's HTTP features. It allows protocol multiplexing on a single port.

Key features

  • TCP and UDP proxying
  • Protocol multiplexing
  • Caddy integration
  • Connection matchers

Pros & cons

Strengths

  • Extends Caddy nicely
  • Port multiplexing

Trade-offs

  • Marked experimental
  • Requires Caddy knowledge

caddy-l4 replaces

Last reviewed Sep 13, 2026 · 858 words

None of caddy-l4 is in the Caddy binary you already have. It is a plugin, it has to be compiled in with xcaddy, and it is labelled experimental by its author, who is also Caddy's author. Those three facts decide whether it belongs in your stack before you read a line of config. If you are happy building a custom Caddy image and keeping it updated, caddy-l4 turns the reverse proxy you already run into a layer 4 router that can put SSH, a database, MQTT and HTTPS on one port. If you want a stock image and a stable feature, HAProxy has done this job for 20 years.

What a layer 4 app inside Caddy actually does

caddy-l4 is Apache-2.0, written in Go, first released in 2020, with 1,767 GitHub stars at the time of writing. It adds a layer4 app to Caddy's config alongside the familiar http app. Where the HTTP app understands requests and hosts, the layer4 app understands connections: it listens on a port, applies matchers to the first bytes of each connection, and hands the connection to a handler. The matchers are the interesting part. It can recognise a TLS ClientHello and read the server name, an HTTP request, an SSH banner, a PROXY protocol header, SOCKS, OpenVPN and a few more, without terminating anything.

The handlers are what you would expect: proxy to an upstream with load balancing and health checks, terminate TLS using Caddy's automatic certificates and then hand off, echo, or subroute to another set of matchers. Because it runs inside the same process, a matched HTTPS connection can be passed straight into Caddy's HTTP app, so the layer 4 layer and your existing sites cooperate rather than compete for port 443.

The build step is the whole difficulty rating

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

That produces a Caddy binary with the module compiled in. For Docker, the usual pattern is a two-stage Dockerfile that runs xcaddy in the official builder image and copies the result into the official runtime image. From then on you are responsible for rebuilding when either Caddy or caddy-l4 releases, which is the maintenance cost the difficulty rating is pricing in. Memory is a non-issue; 64 MB covers Caddy with the module loaded.

Configuration was JSON-only for most of the project's life, and JSON is still the reference. Caddyfile support arrived through a layer4 global option, so a modern build lets you keep one Caddyfile. Whichever you use, read the module's README rather than guessing, because the matcher and handler names are exact and Caddy refuses to start on an unknown one.

The three jobs a self-hoster actually uses it for

First, SSH and HTTPS on port 443. Some networks block everything but 443, and caddy-l4 can match the SSH banner and route it to port 22 while everything else goes to the HTTP app. This is what people used to run sslh for, and doing it inside Caddy means one fewer service.

Second, TLS passthrough by server name. A service that insists on managing its own certificate, a mail server or a Home Assistant instance you do not want to re-terminate, can be routed by SNI to its own upstream while Caddy still terminates the rest. Matching on the ClientHello means the certificate stays where it is.

Third, plain TCP and UDP proxying with Caddy's certificate handling in front: PostgreSQL, MQTT, a game server or a WireGuard endpoint exposed through one public IP. For these, the automatic TLS is the reason to prefer this over a bare iptables rule.

Where HAProxy and Traefik still win

HAProxy is the safer pick whenever the layer 4 job is the main job. It has stable configuration, a real stats page, decades of production hardening, and every load-balancing feature caddy-l4 offers plus several it does not. Traefik has native TCP and UDP routers in the stock image with no build step, which for a Docker-label-driven setup is the deciding advantage; the Caddy versus Traefik comparison is the right place to weigh that. caddy-l4 wins only when you already run Caddy for HTTP, want one process and one certificate store, and are comfortable with an experimental label on a module that has been in wide use for years despite it. The wider proxy category covers the rest of the field.

What I'd do

If Caddy is already my edge and I have one or two layer 4 needs, SSH on 443 or SNI passthrough to a mail server, I would build a custom image with caddy-l4, pin both versions in the Dockerfile, and rebuild monthly with Caddy releases. If layer 4 routing is central, with a dozen TCP services and real load balancing, I would put HAProxy in front and leave Caddy on HTTP only. Do not bring caddy-l4 in for a single port forward that a firewall rule would do; the build obligation outlasts the convenience.

Compare caddy-l4

9 head-to-head comparisons.

Similar reverse proxy & gateways apps