Skip to main content
Custom Caddy builds allow you to include only the modules you need, add third-party plugins, and optimize for your deployment environment.

Why Custom Builds?

From README.md:175 and the architecture:
  • Extensibility: Add functionality without modifying Caddy’s source code
  • Optimization: Include only needed modules to reduce binary size
  • Integration: Bundle organization-specific plugins
  • Version control: Pin specific versions of Caddy and plugins
  • Performance: Compile for specific architectures
Caddy’s highly extensible modular architecture from README.md:92 makes custom builds practical and maintainable.

Build Methods Overview

xcaddy (Recommended)

Fast, automated builds with plugin management

Manual Process

Full control over build configuration
From README.md:145, xcaddy is the official build tool.

Install xcaddy

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

Basic Custom Build

xcaddy build v2.8.0 \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/greenpau/caddy-security \
    --output /usr/local/bin/caddy
See the xcaddy documentation for complete details.

Manual Custom Build

For full control, follow these steps from README.md:151 and cmd/caddy/main.go:15:
1

Create build directory

mkdir caddy-custom
cd caddy-custom
2

Create main.go

Based on cmd/caddy/main.go:
package main

import (
    _ "time/tzdata"

    caddycmd "github.com/caddyserver/caddy/v2/cmd"

    // Standard modules
    _ "github.com/caddyserver/caddy/v2/modules/standard"

    // Custom plugins
    _ "github.com/caddy-dns/cloudflare"
    _ "github.com/greenpau/caddy-security"
    _ "github.com/mholt/caddy-ratelimit"
)

func main() {
    caddycmd.Main()
}
The blank import _ triggers the plugin’s init() function which registers the module.
3

Initialize Go module

go mod init caddy-custom
4

Pin Caddy version

go get github.com/caddyserver/caddy/[email protected]
5

Add plugin dependencies

go get github.com/caddy-dns/cloudflare@latest
go get github.com/greenpau/caddy-security@latest
go get github.com/mholt/caddy-ratelimit@latest
6

Tidy dependencies

go mod tidy
7

Build

From README.md:159:
go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -trimpath \
    -o caddy

Standard Modules

From modules/standard/imports.go:1, standard modules include:
import (
    _ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    _ "github.com/caddyserver/caddy/v2/modules/caddyevents"
    _ "github.com/caddyserver/caddy/v2/modules/caddyevents/eventsconfig"
    _ "github.com/caddyserver/caddy/v2/modules/caddyfs"
    _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/standard"
    _ "github.com/caddyserver/caddy/v2/modules/caddypki"
    _ "github.com/caddyserver/caddy/v2/modules/caddypki/acmeserver"
    _ "github.com/caddyserver/caddy/v2/modules/caddytls"
    _ "github.com/caddyserver/caddy/v2/modules/caddytls/distributedstek"
    _ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek"
    _ "github.com/caddyserver/caddy/v2/modules/filestorage"
    _ "github.com/caddyserver/caddy/v2/modules/logging"
    _ "github.com/caddyserver/caddy/v2/modules/metrics"
)
Do not remove modules/standard unless you know exactly which modules you need. Many features depend on these.

Minimal Build

For embedded systems or containers, create a minimal build:
package main

import (
    _ "time/tzdata"
    caddycmd "github.com/caddyserver/caddy/v2/cmd"
    
    // Only essential modules
    _ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
    _ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
    _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
    _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
    _ "github.com/caddyserver/caddy/v2/modules/caddytls"
)

func main() {
    caddycmd.Main()
}
Build with maximum optimization:
CGO_ENABLED=0 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w -X github.com/caddyserver/caddy/v2.CustomVersion=v2.8.0-minimal" \
    -trimpath

DNS Provider Build

For automated TLS with DNS-01 challenges:
import (
    _ "github.com/caddyserver/caddy/v2/modules/standard"
    
    // DNS providers
    _ "github.com/caddy-dns/cloudflare"
    _ "github.com/caddy-dns/route53"
    _ "github.com/caddy-dns/digitalocean"
)

Enhanced Security Build

import (
    _ "github.com/caddyserver/caddy/v2/modules/standard"
    
    // Security enhancements
    _ "github.com/greenpau/caddy-security"       // Auth portal, MFA
    _ "github.com/mholt/caddy-ratelimit"         // Rate limiting
    _ "github.com/porech/caddy-maxmind-geolocation" // GeoIP
)

Monitoring Build

import (
    _ "github.com/caddyserver/caddy/v2/modules/standard"
    
    // Monitoring and observability  
    _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/tracing" // OpenTelemetry
)
The tracing module is from modules/caddyhttp/tracing/module.go:15.

Config Adapter Build

import (
    _ "github.com/caddyserver/caddy/v2/modules/standard"
    
    // Config adapters
    _ "github.com/caddyserver/nginx-adapter"     // Nginx config
    _ "github.com/caddyserver/yaml-adapter"      // YAML config
)

Version Information

From caddy.go:940, inject custom version info:
go build -ldflags "-X github.com/caddyserver/caddy/v2.CustomVersion=v2.8.0-company-$(date +%Y%m%d)"
The version appears in:
./caddy version
# v2.8.0-company-20260301

Cross-Platform Builds

Linux AMD64

GOOS=linux GOARCH=amd64 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -o caddy-linux-amd64

Windows AMD64

GOOS=windows GOARCH=amd64 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -o caddy-windows-amd64.exe

macOS ARM64 (Apple Silicon)

GOOS=darwin GOARCH=arm64 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -o caddy-darwin-arm64

Linux ARM64 (ARM servers)

GOOS=linux GOARCH=arm64 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -o caddy-linux-arm64

Linux ARM (Raspberry Pi)

GOOS=linux GOARCH=arm GOARM=7 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -o caddy-linux-armv7

Build Optimization

Reduce Binary Size

go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -trimpath
Flags:
  • -tags: Exclude optional dependencies
  • -ldflags="-s -w": Strip debug info
  • -trimpath: Remove file paths
Further compress with UPX:
upx --best --lzma caddy

Static Binary

From README.md:93, build with no external dependencies:
CGO_ENABLED=0 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w -extldflags '-static'" \
    -trimpath

Faster Builds

Enable parallel compilation:
GO_BUILD_PARALLEL=8 go build
Use build cache:
# Cache is automatic, but can be cleared:
go clean -cache

Dockerfile Build

Multi-stage build for minimal image:
# Build stage
FROM golang:1.25-alpine AS builder

RUN apk add --no-cache git

WORKDIR /build

# Copy main.go with plugin imports
COPY main.go .
RUN go mod init caddy-custom && \
    go get github.com/caddyserver/caddy/[email protected]

RUN CGO_ENABLED=0 go build \
    -tags=nobadger,nomysql,nopgx \
    -ldflags="-s -w" \
    -trimpath \
    -o caddy

# Runtime stage
FROM scratch

COPY --from=builder /build/caddy /usr/bin/caddy
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 80 443 2019

ENTRYPOINT ["/usr/bin/caddy"]
CMD ["run", "--config", "/etc/caddy/Caddyfile"]

Automated Build Pipeline

GitHub Actions Example

name: Build Custom Caddy

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-go@v5
        with:
          go-version: '1.25'
      
      - name: Install xcaddy
        run: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
      
      - name: Build
        run: |
          xcaddy build v2.8.0 \
            --with github.com/caddy-dns/cloudflare \
            --output caddy-linux-amd64
      
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: caddy-linux-amd64
          path: caddy-linux-amd64

Verification

After building, verify your custom binary:
# Check version
./caddy version

# List all modules (verify plugins are included)
./caddy list-modules

# Search for specific plugin
./caddy list-modules | grep cloudflare

# Validate configuration
./caddy validate --config Caddyfile

# Test run
./caddy run --config Caddyfile

Distribution

Create Release Archive

tar -czf caddy-custom-v1.0.0-linux-amd64.tar.gz caddy

Generate Checksums

sha256sum caddy-custom-v1.0.0-linux-amd64.tar.gz > checksums.txt

Sign Release

gpg --armor --detach-sign caddy-custom-v1.0.0-linux-amd64.tar.gz

Maintenance

Update Caddy Version

go get github.com/caddyserver/caddy/[email protected]
go mod tidy
go build

Update Plugins

go get -u github.com/caddy-dns/cloudflare
go mod tidy
go build

Security Updates

# Update all dependencies
go get -u all
go mod tidy

# Verify no vulnerabilities
go list -json -m all | nancy sleuth

Best Practices

Version everything: Pin specific versions of Caddy and all plugins in production.
Test thoroughly: Run your test suite against custom builds before deploying.
Document plugins: Maintain a list of included plugins and their versions.
Automate builds: Use CI/CD to build consistently across environments.
Keep builds minimal: Only include plugins you actually use.

Next Steps

xcaddy Tool

Master xcaddy for easier custom builds

Module Development

Create your own custom modules

Testing Guide

Test your custom builds thoroughly

Contributing

Share your build configurations

Build docs developers (and LLMs) love