Skip to main content
The Caddyfile is Caddy’s native configuration format - a human-friendly alternative to JSON. It provides a simple, intuitive syntax for configuring your web server.

Overview

The Caddyfile adapter converts Caddyfile syntax to Caddy’s native JSON configuration format. This allows you to write configs in a more readable format while maintaining the full power of Caddy’s JSON structure.
The Caddyfile is parsed into tokens and then adapted to JSON. Environment variables in {$ENVIRONMENT_VARIABLE} notation are replaced before parsing begins.

Structure

A Caddyfile consists of server blocks, which define how Caddy should handle requests for specific sites or addresses.

Server Blocks

From caddyconfig/caddyfile/parse.go:753-761:
type ServerBlock struct {
    HasBraces    bool
    Keys         []Token
    Segments     []Segment
    IsNamedRoute bool
}
Each server block:
  • Starts with one or more addresses (site addresses or labels)
  • Contains directives that configure behavior
  • Can optionally use curly braces {} for multiple directives
localhost {
    respond "Hello World"
}

Syntax Rules

Tokens and Whitespace

From the lexer implementation (caddyconfig/caddyfile/lexer.go:27-37):
  • Tokens are separated by whitespace
  • Quoted strings can contain whitespace: "hello world"
  • Backtick strings preserve literal content: `raw text`
  • Comments start with # and continue to end of line

Quotes and Escaping

# Escape quotes inside quoted strings
respond "He said \"Hello\""
Heredoc markers must contain only alphanumeric characters, dashes, and underscores. The closing marker must match the opening marker exactly.

Environment Variables

Environment variables are replaced during parsing (caddyconfig/caddyfile/parse.go:67-111):
# Basic substitution
{$API_KEY}

# With default value
{$PORT:8080}

# In a full example
localhost:{$PORT:2015} {
    reverse_proxy {$BACKEND_HOST:localhost:8080}
}

Directives

Directives are keywords that configure Caddy’s behavior. From caddyconfig/caddyfile/parse.go:783-795:
type Segment []Token

// Directive returns the directive name for the segment.
func (s Segment) Directive() string {
    if len(s) > 0 {
        return s[0].Text
    }
    return ""
}
Directives can:
  • Appear on a single line: respond "OK"
  • Open a block with arguments: reverse_proxy localhost:8080 { ... }
  • Span multiple lines with line continuation \
localhost {
    log {
        output file ./caddy.log
        level INFO
    }
}

Advanced Features

Snippets

Reusable configuration blocks defined with parentheses (caddyconfig/caddyfile/parse.go:710-717):
# Define a snippet
(common-config) {
    encode gzip
    log
}

# Use the snippet
example.com {
    import common-config
    reverse_proxy localhost:8080
}

Named Routes

Define reusable route handlers with &(name) syntax:
&(api-handler) {
    reverse_proxy localhost:3000
}

example.com {
    handle /api/* {
        invoke api-handler
    }
}

Import Directive

Import configurations from other files (caddyconfig/caddyfile/parse.go:356-579):
import sites/*.conf
Glob patterns in imports may only contain one wildcard (*) to prevent performance issues. Imported files starting with . are automatically skipped.

Matchers

Request matchers filter which requests a directive applies to:
localhost {
    # Define a matcher
    @api {
        path /api/*
        header Content-Type application/json
    }
    
    # Use the matcher
    reverse_proxy @api localhost:3000
    
    # Inline matcher
    respond /health 200
}
Matchers must be defined within a site block, not globally. Attempting to define matchers globally (e.g., @matcher ...) will result in an error.

Parsing Process

The Caddyfile parsing follows these steps (caddyconfig/caddyfile/parse.go:30-58):
  1. Tokenization - Input is lexed into tokens
  2. Environment Variable Expansion - {$VAR} patterns are replaced
  3. Token Grouping - Tokens are grouped by server blocks
  4. Directive Parsing - Each directive is parsed into segments
  5. Import Processing - Import directives are resolved and inlined
  6. Adaptation - The structured data is converted to JSON
func Parse(filename string, input []byte) ([]ServerBlock, error) {
    tokens, err := allTokens(filename, inputCopy)
    if err != nil {
        return nil, err
    }
    p := parser{
        Dispenser: NewDispenser(tokens),
        importGraph: importGraph{
            nodes: make(map[string]struct{}),
            edges: make(adjacency),
        },
    }
    return p.parseAll()
}

Common Patterns

HTTPS and TLS

example.com {
    # Automatic HTTPS is enabled by default
    reverse_proxy localhost:8080
}

# Custom TLS configuration
secure.example.com {
    tls cert.pem key.pem
    reverse_proxy localhost:8080
}

# Use internal CA for local development
localhost {
    tls internal
}

Multiple Routes

example.com {
    # Static files
    handle /static/* {
        root * /var/www/static
        file_server
    }
    
    # API proxy
    handle /api/* {
        reverse_proxy localhost:3000
    }
    
    # Default handler
    handle {
        reverse_proxy localhost:8080
    }
}

Health Checks and Logging

localhost {
    log {
        output file ./caddy.access.log
    }
    
    log health_check_log {
        output file ./caddy.health.log
        no_hostname
    }
    
    @healthCheck path('/healthz*')
    handle @healthCheck {
        log_name health_check_log
        respond "Healthy"
    }
    
    handle {
        respond "Hello World"
    }
}

Best Practices

Formatting: Use caddy fmt to automatically format your Caddyfile. The adapter checks formatting and warns if input differs from formatted output.
  1. Use snippets for common configuration blocks
  2. Organize with imports for large configurations
  3. Define matchers for complex routing logic
  4. Add comments to document your configuration
  5. Test configs with caddy validate before deploying

Error Handling

Common parsing errors and their solutions:
ErrorCauseSolution
unexpected token '{'Missing space before {Add space: directive {
wrong argument countMissing required argumentsCheck directive documentation
unexpected EOFUnclosed blockAdd closing }
import pattern not foundInvalid import pathVerify file exists
mismatched heredoc markerIncorrect closing markerMatch opening marker exactly

See Also

Build docs developers (and LLMs) love