Caddy’s extensibility is a major selling point, but most users stick to the official binaries. What if you need a plugin not included in the official builds, like caddy-security or caddy-dns? That’s where xcaddy comes in. It’s the official tool for building custom Caddy binaries with any plugin you want.

Let’s say you want to build a Caddy binary that includes the caddy-security plugin.

First, you need xcaddy itself. If you don’t have it, you can grab it with:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

This will install xcaddy into your $GOPATH/bin directory. Make sure that directory is in your $PATH.

Now, to build Caddy with the caddy-security plugin, you’d run:

xcaddy build --with github.com/caddy-security/caddy-security

This command tells xcaddy to fetch the caddy-security plugin from its GitHub repository and compile it directly into your Caddy binary. xcaddy handles all the Go module management and compilation details for you.

The resulting binary, by default named caddy, will be in your current directory. You can then run this custom binary instead of the official one. For example, if you’re in a directory with a Caddyfile, you can run:

./caddy run

This custom binary will now recognize directives and configurations specific to the caddy-security plugin.

What if you need multiple plugins? Just list them with --with:

xcaddy build --with github.com/caddy-security/caddy-security --with github.com/caddy-dns/cloudflare

This builds a Caddy binary with both caddy-security and cloudflare DNS provider support.

You can also use xcaddy to build for different operating systems and architectures. For instance, to build a Linux AMD64 binary for a remote server:

xcaddy build --os linux --arch amd64 --with github.com/caddy-security/caddy-security

This will produce a caddy executable suitable for a Linux environment.

The xcaddy tool uses Go’s build infrastructure. When you specify --with, it essentially adds the specified module to the Caddy project’s go.mod file and compiles it. The Caddy server then dynamically loads these included plugins at runtime. This is a compile-time operation, meaning the plugin code becomes part of the executable itself.

A common pitfall is forgetting to update the plugin versions. If a plugin has a breaking change, you might need to specify a particular commit or tag. For example:

xcaddy build --with github.com/caddy-security/caddy-security@v1.0.0

This ensures you’re building with a known, stable version of the plugin.

The xcaddy tool is also how many plugins are tested and maintained. The official Caddy plugins are typically developed in a way that makes them easily discoverable and compilable by xcaddy.

When you build a custom binary, it’s important to remember that Caddy’s auto-updates will not apply to your custom build. You’ll need to periodically rebuild your custom binary with xcaddy to incorporate the latest Caddy core updates and plugin improvements.

You can also use xcaddy to build a plugin into a specific version of Caddy, not just the latest. For example, to build plugin X against Caddy v2.6.4:

xcaddy build v2.6.4 --with github.com/some/plugin

This is crucial if you have a dependency on a particular Caddy version.

The most surprising thing is that Caddy’s plugin system isn’t a traditional plugin API where you load .so files. Instead, plugins are compiled directly into the binary. This means there’s no runtime dependency on separate plugin files, and it allows for more static analysis and potentially better performance, but it also means you must rebuild Caddy to add or update plugins.

After building your custom Caddy binary with all necessary plugins, the next step is often integrating it into your deployment pipeline, ensuring it’s packaged and distributed correctly to your servers.

Want structured learning?

Take the full Caddy course →