Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Caddy Web Server — User Guide

Generated May 2026 from caddyserver/caddy at 41aee97

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Quick Start

Caddy is an extensible server platform written in Go that serves HTTP, manages TLS certificates automatically, and acts as a reverse proxy. It eliminates the complexity of HTTPS configuration by obtaining and renewing certificates from Let's Encrypt and ZeroSSL without any manual setup.

Key features:

  • Automatic HTTPS — TLS certificates obtained and renewed automatically via ACME
  • Caddyfile — human-readable configuration format that adapts to native JSON
  • Reverse proxy — built-in load balancing, health checks, retries, and streaming
  • File server — static file serving with directory browsing
  • HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) support
  • Admin API — live configuration changes without downtime via REST API on localhost:2019
  • Modular architecture — extend with plugins at compile time
  • Cross-platform — Linux, macOS, Windows, FreeBSD (amd64, arm64, arm, s390x, ppc64le, riscv64)

Prerequisites: None for pre-built binaries. Go 1.25+ for building from source.

Install and run in 30 seconds:

# Install (Debian/Ubuntu)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

Minimal Caddyfile (serve current directory over HTTPS):

localhost {
    file_server browse
}
caddy run
... INFO  serving initial configuration

Visit https://localhost — Caddy automatically provisions a locally-trusted TLS certificate.

Tip

For the fastest start, just run caddy file-server --browse in any directory — no config file needed. Caddy will serve files from the current directory on port 2015.

What to try next:

  • Set up a reverse proxy: see "Reverse Proxy" in Core Usage
  • Configure automatic HTTPS for a domain: see "Automatic HTTPS" in Core Usage
  • Learn the Caddyfile syntax: see Configuration
  • See the full Admin Guide for architecture, deployment, and security details

2. Installation

System Requirements

Requirement Minimum Recommended
OS Linux, macOS, Windows, FreeBSD Linux (systemd)
Architecture amd64, arm64, armv5/v6/v7, s390x, ppc64le, riscv64 amd64 or arm64
Go (source builds) 1.25.0 Latest stable
Memory 64 MB 256 MB+
Ports 80 (HTTP), 443 (HTTPS) for automatic TLS Also 443/UDP for HTTP/3

Installation Methods

Debian/Ubuntu (APT)

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Fedora/RHEL (DNF)

dnf install 'dnf-command(copr)'
dnf copr enable @caddy/caddy
dnf install caddy

macOS (Homebrew)

brew install caddy

Windows (Scoop)

scoop install caddy

Docker

docker run -d -p 80:80 -p 443:443 -p 443:443/udp \
  -v caddy_data:/data -v caddy_config:/config \
  caddy:latest

From Source

git clone https://github.com/caddyserver/caddy.git
cd caddy/cmd/caddy
go build

Important

On Linux, Caddy needs permission to bind to ports 80 and 443. If not running as root, use: sudo setcap cap_net_bind_service=+ep $(which caddy). Remember to re-run this after upgrading the binary.

Post-Install Verification

caddy version
v2.11.1 h1:abc123...
caddy environ
caddy.HomeDir=/root
caddy.AppDataDir=/root/.local/share/caddy
caddy.AppConfigDir=/root/.config/caddy
caddy.ConfigAutosavePath=/root/.config/caddy/autosave.json
caddy.Version=v2.11.1
runtime.GOOS=linux
runtime.GOARCH=amd64
...

Tip

The caddy environ command shows exactly which directories Caddy uses for data and configuration on your system. This is invaluable for debugging path issues.

Building with Plugins (xcaddy)

Caddy's standard distribution covers common use cases, but you can add third-party modules using xcaddy:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddy-dns/cloudflare

The caddy add-package and caddy remove-package commands can also add/remove modules to an existing binary without xcaddy:

caddy add-package github.com/caddy-dns/cloudflare
caddy remove-package github.com/caddy-dns/cloudflare

Warning

The upgrade, add-package, and remove-package commands are experimental and replace the running binary. Always keep a backup. Use --keep-backup to preserve the old binary automatically.

3. Configuration

Caddy accepts configuration in two formats: Caddyfile (human-friendly) and native JSON (machine-friendly). The Caddyfile is automatically adapted to JSON before being applied.

Caddyfile Basics

A Caddyfile consists of site blocks that define how to handle requests for specific addresses:

# Serve static files for example.com
example.com {
    root * /var/www/html
    file_server
}

# Reverse proxy for api.example.com
api.example.com {
    reverse_proxy localhost:8080
}

Key syntax rules:

Element Syntax Example
Site address Domain, IP, or port example.com, localhost:8080, :443
Directive Keyword inside site block file_server, reverse_proxy, respond
Matcher @name definition or inline path @api path /api/*
Placeholder {http.request.*} or shorthand {host} respond "Host: {host}"
Global options Block at the top with { } { admin off }
Snippet (name) { ... } (common) { encode gzip }
Import import <file_or_snippet> import common
Environment variable {env.VAR} or {$VAR} {env.SITE_ROOT}
Comment # # This is a comment
Heredoc <<TAG ... TAG Multi-line values

Global Options Block

The global options block must appear first in the Caddyfile (before any site blocks) and has no site address:

{
    # TLS email for ACME account
    email [email removed]

    # Admin API settings
    admin localhost:2019

    # Default bind address
    default_bind 0.0.0.0

    # Logging
    debug

    # ACME settings
    acme_dns <provider> ...

    # Skip installing the CA root in system trust store
    skip_install_trust

    # HTTP and HTTPS ports
    http_port 80
    https_port 443

    # Graceful shutdown period
    grace_period 10s

    # Server options for specific listeners
    servers :443 {
        protocols h1 h2 h3
    }
}

Note

Not all options are listed above. See the Admin Guide for a complete configuration reference of all global options and server settings.

Using JSON Configuration

Caddy's native format is JSON. You can view the JSON equivalent of any Caddyfile with caddy adapt:

caddy adapt --config Caddyfile --pretty

You can also configure Caddy entirely via the admin API:

curl localhost:2019/load \
  -H "Content-Type: application/json" \
  -d @caddy.json

Environment Files

Caddy can load environment variables from .env files using the --envfile flag:

caddy run --envfile .env

The env file uses KEY=VALUE format. Lines starting with # are comments. Existing environment variables are not overwritten.

4. Core Usage

Running Caddy

caddy run --config Caddyfile

Caddy runs in the foreground, blocking the terminal. Use Ctrl+C to stop. This is the recommended way to run Caddy, especially with service managers like systemd.

Background

caddy start --config Caddyfile
Successfully started Caddy (pid=12345) - Caddy is running in the background
caddy stop

Warning

On Windows, caddy start keeps the child process attached to the terminal. Closing the terminal forcefully stops Caddy. Prefer caddy run with a service manager instead.

Reloading Configuration

caddy reload --config Caddyfile

This sends the new configuration to the running instance via the admin API. The reload is graceful — active connections are not dropped.

Use --force to force a reload even if the config hasn't changed:

caddy reload --config Caddyfile --force

Resuming Previous Configuration

caddy run --resume

Caddy automatically saves its current configuration to autosave.json in the config directory. The --resume flag loads this file on startup instead of the specified config file.

Watching for Config Changes

caddy run --config Caddyfile --watch

Caution

The --watch flag polls the config file every second and automatically reloads on changes. This is intended for local development only — it can cause unintended config changes in production.

Automatic HTTPS

Caddy automatically obtains and renews TLS certificates for any site address that looks like a hostname:

example.com {
    respond "Hello, HTTPS!"
}

When Caddy starts with this config, it will:

  1. Obtain a certificate from Let's Encrypt (or ZeroSSL) via ACME
  2. Set up HTTP → HTTPS redirects on port 80
  3. Enable HTTP/2 and HTTP/3
  4. Renew the certificate before it expires

No extra configuration needed — this is the default behavior.

Note

Caddy automatically agrees to the ACME CA's terms of service when using automatic HTTPS. For domains using ACME, ports 80 and 443 must be publicly reachable (or use DNS challenge for private networks).

Disable automatic HTTPS per-site:

http://example.com {
    respond "Plain HTTP only"
}

Prefixing with http:// disables automatic HTTPS for that site. You can also disable it globally:

{
    auto_https off
}

Using local certificates for development:

localhost {
    respond "Hello from localhost!"
}

For localhost and other local addresses, Caddy generates a self-signed certificate from its own internal CA and installs it in the system trust store (unless skip_install_trust is set).

Reverse Proxy

example.com {
    reverse_proxy localhost:8080
}

Multiple backends with load balancing:

example.com {
    reverse_proxy node1:8080 node2:8080 node3:8080 {
        lb_policy round_robin
    }
}

With health checks:

example.com {
    reverse_proxy backend:8080 {
        health_uri /health
        health_interval 30s
        health_timeout 5s
    }
}

Proxying to Unix sockets:

example.com {
    reverse_proxy unix//var/run/app.sock
}

Header manipulation:

example.com {
    reverse_proxy backend:8080 {
        header_up Host {upstream_hostport}
        header_up X-Real-IP {remote_host}
        header_down -Server
    }
}

Tip

Caddy automatically adds X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers to proxied requests. You don't need to configure these manually.

File Server

Serve static files:

example.com {
    root * /var/www/html
    file_server
}

Enable directory browsing:

example.com {
    root * /var/www/files
    file_server browse
}

Quick file server from the command line:

caddy file-server --root /var/www/html --listen :8080 --browse

Note

The caddy file-server convenience command disables the admin API since it configures entirely from command-line flags.

Serve with SPA fallback (try_files):

example.com {
    root * /var/www/app
    try_files {path} /index.html
    file_server
}

Respond (Quick Testing)

For quick responses without a file server or proxy:

example.com {
    respond "Hello, world!" 200
}

From the command line:

caddy respond --listen :8080 "Service is up" 200

Request Matchers

Matchers control which requests a directive applies to:

example.com {
    # Named matcher
    @api {
        path /api/*
        method GET POST
    }
    reverse_proxy @api backend:8080

    # Path matcher shorthand
    reverse_proxy /ws/* websocket-server:9090

    # All other requests
    file_server
}

Common matcher types:

Matcher Syntax Description
path path /api/* Match request path
method method GET POST Match HTTP method
host host example.com Match hostname
header header Content-Type application/json Match request header
remote_ip remote_ip 192.168.1.0/24 Match client IP
protocol protocol https Match protocol
expression expression {http.request.uri}.contains("/api") CEL expression
file file {path} Match if file exists
not not path /secret/* Negate a matcher

Request and Response Headers

example.com {
    header {
        # Add/set headers
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        Strict-Transport-Security "max-age=31536000; includeSubDomains"

        # Remove headers
        -Server
        -X-Powered-By
    }
    file_server
}

Encoding (Compression)

example.com {
    encode gzip zstd
    file_server
}

Caddy supports gzip, zstd (Zstandard), and brotli compression. It also supports serving pre-compressed files (.gz, .br, .zst extensions).

Rewrite and Redirect

example.com {
    # Internal rewrite (URL changes internally, not visible to client)
    rewrite /old-path /new-path

    # External redirect (client sees 301/302)
    redir /old-page /new-page 301

    # URI manipulation
    uri strip_prefix /api
    uri replace /v1/ /v2/
}

Templates

Caddy can process templates in served files:

example.com {
    templates
    file_server
}

Templates use Go's text/template syntax and have access to request information, Markdown rendering (via Goldmark), and more.

Logging

example.com {
    log {
        output file /var/log/caddy/access.log {
            roll_size 100MiB
            roll_keep 5
            roll_keep_for 720h
        }
        format json
        level INFO
    }
    file_server
}

TLS Configuration

For fine-grained TLS control beyond automatic HTTPS:

example.com {
    tls cert.pem key.pem
}

Using internal (self-signed) certificates:

internal.example.com {
    tls internal
}

Client certificate authentication:

example.com {
    tls {
        client_auth {
            mode require_and_verify
            trust_pool file {
                pem_file /path/to/client-ca.pem
            }
        }
    }
}

Basic Authentication

example.com {
    basicauth /admin/* {
        admin $2a$14$hashOfPassword
    }
    file_server
}

Generate password hashes with:

caddy hash-password

Important

The hash-password command is provided by the caddyauth module included in the standard distribution. Passwords are hashed using bcrypt by default.

PHP with FastCGI

example.com {
    root * /var/www/html
    php_fastcgi localhost:9000
}

The php_fastcgi directive is a shortcut that configures try_files, reverse_proxy with FastCGI transport, and appropriate index handling for PHP.

HTTP/3 (QUIC)

HTTP/3 is enabled by default when HTTPS is active. No additional configuration is needed. Caddy listens on UDP port 443 for QUIC connections and advertises it via the Alt-Svc header.

Metrics (Prometheus)

{
    servers {
        metrics
    }
}

Caddy exports Prometheus-compatible metrics at the admin API endpoint /metrics.

5. Common Tasks

Task: Serve a Static Website with HTTPS

Goal: Serve a static site at example.com with automatic HTTPS.

Prerequisites: DNS A record pointing example.com to your server IP; ports 80 and 443 open.

Steps:

  1. Create the Caddyfile:

    example.com {
        root * /var/www/example.com
        file_server
    }
    
  2. Start Caddy:

    caddy run --config Caddyfile
    
  3. Verify:

    curl -I https://example.com
    

If it fails: Check that DNS resolves correctly and that ports 80/443 are not blocked by a firewall.

Task: Set Up a Reverse Proxy with Load Balancing

Goal: Distribute traffic across multiple backend servers.

Steps:

  1. Create the Caddyfile:

    example.com {
        reverse_proxy node1:3000 node2:3000 {
            lb_policy least_conn
            health_uri /health
            health_interval 10s
            fail_duration 30s
        }
    }
    
  2. Reload:

    caddy reload --config Caddyfile
    

Task: Redirect www to Non-www

www.example.com {
    redir https://example.com{uri} permanent
}

example.com {
    respond "Hello!"
}

Task: Serve Multiple Sites

site1.example.com {
    root * /var/www/site1
    file_server
}

site2.example.com {
    root * /var/www/site2
    file_server
}

Each site automatically gets its own TLS certificate.

Task: Restrict Access by IP

example.com {
    @blocked remote_ip 10.0.0.0/8
    respond @blocked "Forbidden" 403

    reverse_proxy backend:8080
}

Task: Use Caddy as a Pure HTTPS Terminator

example.com {
    reverse_proxy http://internal-service:8080
}

Caddy handles TLS termination and proxies plaintext HTTP to the backend.

Task: Use the Admin API to Change Config at Runtime

# View current configuration
curl localhost:2019/config/

# Update a specific path
curl -X PATCH localhost:2019/config/apps/http/servers/srv0/routes \
  -H "Content-Type: application/json" \
  -d '[{"handle":[{"handler":"static_response","body":"Updated!"}]}]'

# Load an entirely new configuration
curl localhost:2019/load \
  -H "Content-Type: application/json" \
  -d @new-config.json

# Stop Caddy
curl -X POST localhost:2019/stop

Tip

The admin API supports GET, POST, PUT, PATCH, and DELETE methods on /config/ paths, allowing fine-grained updates to any part of the running configuration without full reloads.

Task: Export and Import TLS Storage

# Export certificates and keys
caddy storage export --config Caddyfile --output backup.tar

# Import to a new instance
caddy storage import --config Caddyfile --input backup.tar

# Pipe between instances
caddy storage export --config old.json --output - | \
  caddy storage import --config new.json --input -

Warning

The storage command is experimental. When using file system storage, run the command as the user that owns the storage directory.

Task: Validate Your Configuration

caddy validate --config Caddyfile
Valid configuration

Task: Format Your Caddyfile

# Preview formatted output
caddy fmt Caddyfile

# Overwrite in place
caddy fmt --overwrite Caddyfile

# Show diff
caddy fmt --diff Caddyfile

Task: Set Up a Development Server with Auto-Reload

caddy run --config Caddyfile --watch

Caddy polls the Caddyfile every second and reloads automatically on changes.

Task: Run Caddy as a systemd Service

After installing via the package manager, Caddy comes with systemd units:

sudo systemctl enable caddy
sudo systemctl start caddy

Edit your Caddyfile at /etc/caddy/Caddyfile, then reload:

sudo systemctl reload caddy

Task: List Installed Modules

caddy list-modules
admin.api.load
admin.api.metrics
caddy.adapters.caddyfile
...

  Standard modules: 100

Use --json for machine-readable output, or --skip-standard to show only plugins:

caddy list-modules --skip-standard --versions

6. Troubleshooting

Diagnostic Decision Tree

Caddy won't start
├── Port already in use? → Check with `ss -tlnp | grep :443`
├── Permission denied on port 80/443? → Use setcap or run as root
├── Config error? → Run `caddy validate --config Caddyfile`
└── Certificate error? → Check DNS, firewall, and ACME account

HTTPS not working
├── Certificate not issued? → Check ACME logs, DNS, port 80 reachability
├── HTTP redirect not working? → Check `auto_https` setting
└── Mixed content warnings? → Ensure all assets use HTTPS URLs

Reverse proxy errors
├── 502 Bad Gateway? → Backend is down or unreachable
├── Timeout? → Adjust timeout settings in reverse_proxy
└── Wrong headers? → Check header_up/header_down configuration

Error: permission denied when binding to port 80 or 443

Cause: Linux requires elevated privileges to bind to ports below 1024.

Solution:

sudo setcap cap_net_bind_service=+ep $(which caddy)

Prevention: Use the package manager installation, which configures this automatically via systemd.

Error: config is not valid JSON

Cause: You passed a Caddyfile or other non-JSON config without specifying an adapter.

Solution:

caddy run --config Caddyfile --adapter caddyfile

Or simply name your file Caddyfile — Caddy detects it automatically.

Prevention: Caddy auto-detects Caddyfile format when the file is named Caddyfile or has a .caddyfile extension.

Error: TLS certificate not obtained

Cause: ACME challenge failed. Usually a DNS or firewall issue.

Solution:

  1. Ensure ports 80 and 443 are publicly reachable
  2. Verify DNS A/AAAA records resolve to your server
  3. Check Caddy logs for ACME-specific errors
  4. Consider using DNS challenge for private networks

Prevention: Test with caddy run in foreground to see real-time ACME logs.

Error: caddy responded with error: HTTP 400

Cause: Invalid configuration sent to the admin API during reload.

Solution:

caddy validate --config Caddyfile

Fix any validation errors, then reload again.

Error: socket activation problems with ACME (community issue #7525)

Cause: Socket activation (systemd fd passing) can conflict with ACME certificate acquisition in certain configurations.

Solution: If using socket activation, ensure the ACME challenge ports are not also passed as activated sockets. Consider falling back to direct port binding for ACME.

Error: ACME broken for .internal addresses (community issue #7147)

Cause: .internal TLDs are not publicly resolvable and cannot use standard ACME HTTP/TLS-ALPN challenges.

Solution: Use tls internal to generate self-signed certificates for .internal addresses, or configure a DNS challenge provider for private ACME servers.

Error: {block} ignored when imported from a separate file (community issue #7557)

Cause: Regression in Caddyfile import handling for block arguments in snippets loaded from external files.

Solution: If affected, inline the snippet in the main Caddyfile as a workaround, or upgrade to a version with the fix.

Error: neither HOME nor USERPROFILE environment variables are set

Cause: Caddy cannot determine where to store data (certificates, config) without a home directory.

Solution: Set the HOME (Linux/macOS), USERPROFILE (Windows), or the XDG variables (XDG_DATA_HOME, XDG_CONFIG_HOME, XDG_CACHE_HOME).

Error: auto_https redirects not working with multiple bind addresses (community issue #6226)

Cause: When multiple bind addresses are specified, automatic HTTP → HTTPS redirects may not be created for all addresses.

Solution: Explicitly configure redirect behavior or ensure bind addresses are consistent across HTTP and HTTPS.

Error: HTTP/3 connection drops (ERR_QUIC_PROTOCOL_ERROR) in Chrome

Cause: Various QUIC protocol edge cases can trigger connection resets in some browsers (community issue #7556).

Solution: If persistent, disable HTTP/3 for the affected server:

{
    servers {
        protocols h1 h2
    }
}

Prevention: Keep Caddy updated — HTTP/3 stability improves with each release.

Error: Excessive memory growth with reverse_proxy

Cause: TLS failures to upstream backends may accumulate state (community issue #7665).

Solution: Ensure upstream TLS certificates are valid. Monitor memory usage and restart if needed.

7. FAQ

Q: How does Caddy handle HTTPS automatically?

Caddy uses the ACME protocol to obtain certificates from Let's Encrypt and ZeroSSL. It handles HTTP-01 and TLS-ALPN-01 challenges automatically, sets up HTTP → HTTPS redirects, and renews certificates before expiry. By using Caddy, you automatically agree to the CA's terms of service — this is intentional since Caddy often runs non-interactively.

Q: Can I use Caddy without automatic HTTPS?

Yes. Use http:// prefix in your site address to disable HTTPS for that site, or set auto_https off in the global options block.

Q: What is the admin API and how do I use it?

The admin API listens on localhost:2019 by default and allows you to view, modify, and reload configuration at runtime via REST requests. You can change the listen address with the CADDY_ADMIN environment variable or the admin global option. To disable it entirely: { admin off }.

Q: How do I use Caddy with Docker?

Mount a volume for /data (certificates/keys) and /config (autosave), expose ports 80, 443, and 443/udp (HTTP/3), and provide your Caddyfile. See the Docker Hub page for official images.

Q: What are placeholders and how do I use them?

Placeholders like {host}, {uri}, {remote_host}, {env.VAR} are dynamic values that Caddy resolves at request time. Use them in Caddyfile directives, headers, and responses. The full list is available in the replacer.go source module.

Q: How do I add third-party modules/plugins?

Use xcaddy build --with <module> to build a custom binary, or use the experimental caddy add-package <module> command to add modules to an existing binary.

Q: What is the difference between caddy run and caddy start?

caddy run runs in the foreground and blocks until stopped. caddy start spawns a background process and returns immediately. Use caddy stop to stop a backgrounded instance.

Q: How do I generate shell completions?

caddy completion bash > /etc/bash_completion.d/caddy   # Bash
caddy completion zsh > "${fpath[1]}/_caddy"             # Zsh
caddy completion fish > ~/.config/fish/completions/caddy.fish  # Fish
caddy completion powershell > caddy.ps1                  # PowerShell

Q: Does Caddy support WebSocket proxying?

Yes. reverse_proxy transparently handles WebSocket upgrade requests. No extra configuration is needed.

Q: Can Caddy act as its own certificate authority?

Yes. Caddy has a built-in PKI app that can issue certificates from a local CA. Use tls internal to use internally-issued certificates. You can manage the local CA with caddy trust (install root CA into system trust store) and caddy untrust (remove it).

Q: How do I use environment variables in my Caddyfile?

Use {env.VARIABLE_NAME} or {$VARIABLE_NAME} syntax. Load additional env vars from files with --envfile .env.

Q: What happens to active connections during a config reload?

Caddy performs graceful reloads. Active HTTP connections complete normally. Active WebSocket connections are maintained across configuration reloads for unrelated routes, though this behavior has known edge cases (community issue #7222).

Q: Where does Caddy store certificates?

By default, in $XDG_DATA_HOME/caddy (or ~/.local/share/caddy on Linux, ~/Library/Application Support/Caddy on macOS, %AppData%\Caddy on Windows). You can change this with the storage configuration.

Q: What is the NO_COLOR environment variable?

Setting NO_COLOR to any value disables color output in Caddy's console log encoder.