# A Caddy built from source reports no version, and the documented plugin build drops three database backends

> A read of caddyserver/caddy: why the development build embeds no version information while xcaddy does, what the nobadger, nomysql and nopgx build tags remove, what the module graph says about ACME, the local CA, QUIC and OpenTelemetry, and why binding port 80 leads to a sudoers instruction.

**caddyserver/caddy** — Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

- Repository: https://github.com/caddyserver/caddy
- Website: https://caddyserver.com
- Stars: 76,121 · Forks: 5,009
- Language: Go
- License: Apache-2.0
- Published: 2026-08-08 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/caddyserver-caddy

## The development build embeds no version, and the builder tool fixes that

The build section carries a warning before any command, and it points at a long-standing Go issue. The plain development build does not embed proper version information, and the readme routes anyone who needs it to the next section instead.

```bash
$ git clone "https://github.com/caddyserver/caddy.git"
$ cd caddy/cmd/caddy/
$ go build
```

That is three commands and a single binary, and it is the fastest way to see whether Caddy does what you want. What it does not produce is a `caddy version` string that identifies a release, so a bug report from a hand-built binary carries no version a maintainer can match, and a support question about a behaviour change has nothing to anchor it to a tag.

The tool that fixes this is `xcaddy`, a separate builder, and one command is all it takes.

```bash
$ xcaddy build
```

The readme then spells out what that command automates, and the list is the real specification of how a customised build is made: create a folder, change into it, copy Caddy's `main.go` into it and add imports for any custom plugins, initialise a Go module, optionally pin a Caddy version with a git tag, commit or branch name, optionally add plugin imports, and compile. The build also has to know which Go version you are on, since the requirement is Go 1.25.0 or newer.

## The documented compile step drops three storage backends to save space

The last line of that automated list is the one with teeth, because it is the only place the readme mentions what a default Caddy binary contains.

```bash
go build -tags=nobadger,nomysql,nopgx
```

Three build tags, named for three storage engines: an embedded key value store, MySQL, and Postgres. A Caddy built without them has no database backend for the data that a server accumulates at runtime, which for Caddy means certificate storage and automation state, the material that makes automatic HTTPS survive a restart. The default build, and the release binaries, carry those drivers so a deployment can choose a database without recompiling.

So the tag list is a size optimisation with a functional cost, and it appears in a numbered list of steps rather than under a heading that warns you. A reader who copies the seven steps gets a smaller binary that quietly cannot use a MySQL or Postgres certificate store, and the symptom arrives at renewal time rather than at build time. If you need a shared store for a cluster, either drop the tags or use xcaddy, whose own configuration is where the plugin set is chosen deliberately.

## Port 80 needs a Linux capability, and the convenience path edits sudoers

Automatic HTTPS means the server wants to bind the low ports, and the readme addresses that head on. When you run Caddy it may try to bind to low ports unless your config says otherwise, and if your operating system requires elevated privileges for that, the new binary needs permission. On Linux that is a file capability rather than a full root login.

```bash
sudo setcap cap_net_bind_service=+ep ./caddy
```

There is a second path for people who prefer `go run`, which only creates temporary binaries and would lose the capability between runs. The repository includes a `setcap.sh` for that.

```bash
$ go run -exec ./setcap.sh main.go
```

The third path is where the readme draws its only hard line. If you would rather not type a password for `setcap`, you can allow it through sudoers without one, and the example line is given verbatim.

```bash
username ALL=(ALL:ALL) NOPASSWD: /usr/sbin/setcap
```

The accompanying text asks readers to be careful, says the project is only qualified to document how to use Caddy rather than Go tooling or your computer, and offers these instructions for convenience at your own risk. That disclaimer is warranted: a passwordless sudoers entry is a permanent change to a machine's security model, taken to avoid typing a password during development.

## The module graph is the honest feature list, from ACME to a pinned root store

The go.mod is where the server's actual scope is visible, and it is a longer list than the feature bullets. Certificate issuance for public names runs through Caddy's own CertMagic with a ZeroSSL client and the ACME library, and internal names and IPs are handled by Smallstep's certificate authority, which is a second, entirely separate CA implementation in the same binary. QUIC support is quic-go, which is what makes HTTP/3 work by default alongside HTTP/1.1 and HTTP/2.

The rest of the graph is operational. OpenTelemetry appears in five forms, a bridge for Prometheus, an auto exporter, HTTP instrumentation, automatic propagators, and the SDK with metric support, so tracing and metrics are dependencies rather than plugins. Templating comes from Sprig, Markdown from goldmark, syntax highlighting from chroma, configuration expressions from cel-go, and TOML support from BurntSushi's parser, which is how a non-JSON config adapter works at all. Logging is zap with DeRuina's timberjack for rotation, and there are two limit-related packages, automaxprocs and automemlimit, for CPU and memory ceilings in a container.

One dependency deserves attention on its own: `golang.org/x/crypto/x509roots/fallback` is pinned to a dated pseudo version. That is a snapshot of the trusted root certificates, so a root authority added after that date is not trusted until the module moves, and a deployment that needs a new internal or public root has to rebuild rather than reload.

## Automatic HTTPS is the default, and the internal CA is a different product

TLS by default is the project's headline, and the sub-bullets are more informative than the headline. For public names there are two issuers, ZeroSSL and Let's Encrypt, with multi-issuer fallback between them. For internal names and IP addresses there is a fully managed local certificate authority, which means the certificate is trusted by clients that trust Caddy and by nobody else, and that the CA's own key becomes the thing to protect.

Two more properties matter for a deployment. Caddy can coordinate with other instances in a cluster, which is how several machines share certificate state instead of each fighting an issuance limit, and Encrypted ClientHello support exists for hiding the requested hostname from the network path.

There is also a resilience claim worth reading precisely: Caddy stays up when other servers go down due to TLS, OCSP or certificate related issues, which is a statement about renewal behaviour and cached validation rather than about upstream dependencies. What the readme does not describe is the storage that makes any of it survive a restart, or how cluster coordination is configured. Both are documentation topics, not code topics, and a first deployment has to go looking for them before the first renewal is due.

## Configuration arrives in four shapes, and the Caddyfile is adapted into JSON

Caddy offers four ways to describe what the server should do, and they are not variations on a theme. There is easy configuration in the Caddyfile format, native JSON configuration described as the powerful option, dynamic configuration through a JSON API, and config adapters for people who prefer something else. The TOML parser in the dependency list is one of those adapters.

The mechanism behind the Caddyfile is the detail that changes how you debug. A Caddyfile is not interpreted directly; it is adapted into the native JSON form at load time, which is why the JSON documentation is described as the powerful option and why config errors surface in adapted output. Teams that keep JSON in version control and generate Caddyfiles for humans end up with two representations of the same server, and the JSON is the one that decides behaviour.

The JSON API is the part with a security consequence. Dynamic configuration means the running server can be reconfigured without a restart, which is exactly what an orchestrator or a provisioning service needs and exactly what makes the admin endpoint's exposure the thing to get right. The test files at the root of the repository are telling here: one set covers the admin API's host handling and another covers its origin handling, both with case sensitivity in the file names, which is the kind of bug that appears when a request arrives with unexpected capitalisation.

## Platform files sit beside each other at the root, and the fuzz targets are there too

The repository root is unusually crowded for a Go project, and the names explain the portability story. There is a Windows variant of the file path helper, a Unix socket reuse listener with its own Windows version, the Unix listener with set option handling and a FreeBSD specific variant of that, and two signal trap files, one for POSIX systems and one for everything else. The claim that Caddy runs anywhere with no external dependencies, not even libc, describes the Go build; these files are where the differences between operating systems are absorbed.

Tests live at the same level. There is a `caddytest` directory for the integration style tests, and three fuzz targets sit next to the ordinary test files, one for duration parsing, one for listeners and one for the value replacer, which is the component that substitutes values into configuration and output.

Running them is one command, or one module at a time.

```bash
$ go test ./...
$ go test ./modules/caddyhttp/tracing/
```

The single module example is telling in itself, since it points at the tracing module and the dependency graph already showed OpenTelemetry spread across five modules. For contributors there is also a golangci lint configuration, a pre-commit configuration, a goreleaser configuration, an AUTHORS file and an AGENTS file at the root. On releases, the three most recent tags are v2.11.2 on 2026-03-06, v2.11.3 on 2026-05-12 and v2.11.4 on 2026-06-03, while the last push to master landed on 2026-09-26, so a build from master sits ahead of every published tag.

## Conclusion

Caddy is a good fit when automatic HTTPS and a single small binary matter more than a large plugin catalogue, and the module graph shows the ACME, local CA, QUIC and telemetry work is already done. If you build it yourself, use xcaddy rather than a plain go build so version information is embedded, decide deliberately whether you need the Badger, MySQL or Postgres storage backends before excluding them, and remember that the newest tagged release is v2.11.4 from 2026-06-03 while master is ahead of it.

## FAQ

### What is Caddy used for in software?

It is an extensible server platform that uses TLS by default, speaking HTTP/1.1, HTTP/2 and HTTP/3 out of the box, with configuration available as a Caddyfile, as native JSON, through a JSON API for dynamic changes, and through config adapters.

### How do I install Caddy?

The readme calls downloading the executable from GitHub Releases and placing it in your PATH the simplest cross-platform route, with other instructions in the online documentation. To build it instead you need Go 1.25.0 or newer, and the readme recommends the xcaddy builder when you want version information or plugins.

### How do I use Caddy as a reverse proxy?

The readme does not contain a reverse proxy example. What it does describe is the configuration layer you would use, the Caddyfile for easy configuration, native JSON for the powerful option, the JSON API for dynamic changes, and automatic HTTPS by default with a local certificate authority for internal names and IP addresses.

### How do I install Caddy on Ubuntu or another Linux system?

The readme's Linux specific instruction is about running a self-built binary: use `sudo setcap cap_net_bind_service=+ep ./caddy` so it can bind the low ports automatic HTTPS needs, or run it through the included `setcap.sh` when using `go run`. Distribution package instructions are in the online documentation.

### How do I use Caddy with an application such as Jellyfin?

The readme does not name any application. It points to the documentation site for tutorials, quick start guides and reference, and recommends the Getting Started guide for all users regardless of experience level before anything application specific.

## Sources

- [Official documentation](https://caddyserver.com)
- [Official README](https://github.com/caddyserver/caddy#readme)
- [Project repository](https://github.com/caddyserver/caddy)
- [Release notes](https://github.com/caddyserver/caddy/releases)

---

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