Skip to main content
โšก Calmops

Caddy Server: Complete Static Website Configuration Guide

Introduction

Caddy is a modern web server written in Go that automatically provisions and renews HTTPS certificates via Let’s Encrypt. Its configuration syntax (the Caddyfile) is far simpler than Nginx or Apache, making it an excellent choice for static sites, reverse proxies, and development servers.

Installation

# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
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

# macOS
brew install caddy

# Verify
caddy version

Basic Static Website

The minimal Caddyfile to serve a static site with automatic HTTPS:

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

That’s it. Caddy automatically:

  • Obtains a TLS certificate from Let’s Encrypt
  • Redirects HTTP to HTTPS
  • Serves files from the specified root directory

Full Production Static Site Config

example.com www.example.com {
    # Document root
    root * /var/www/example.com

    # Serve static files
    file_server

    # Gzip/Zstd compression
    encode gzip zstd

    # Access logging
    log {
        output file /var/log/caddy/example.com.access.log {
            roll_size 100mb
            roll_keep 5
            roll_keep_for 720h  # 30 days
        }
        format json
    }

    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
        -Server  # remove Server header
    }
}

Redirects

# Redirect www to non-www
www.example.com {
    redir https://example.com{uri} permanent
}

# Redirect HTTP to HTTPS (Caddy does this automatically, but explicit example)
http://example.com {
    redir https://example.com{uri} permanent
}

# Redirect a specific path
example.com {
    redir /old-page /new-page 301
    redir /blog/* /posts/{path} 301

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

Custom 404 Page

example.com {
    root * /var/www/example.com
    file_server {
        # Serve custom 404 page
        hide .git
    }

    handle_errors {
        @404 expression {http.error.status_code} == 404
        rewrite @404 /404.html
        file_server
    }
}

Caching Headers

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

    # Cache static assets for 1 year
    @static {
        path *.css *.js *.png *.jpg *.jpeg *.gif *.svg *.ico *.woff *.woff2
    }
    header @static Cache-Control "public, max-age=31536000, immutable"

    # No cache for HTML
    @html {
        path *.html
    }
    header @html Cache-Control "no-cache, must-revalidate"
}

Reverse Proxy

Caddy is also excellent as a reverse proxy in front of an application server:

# Proxy to a local app server
api.example.com {
    reverse_proxy localhost:8080
}

# Proxy with load balancing
app.example.com {
    reverse_proxy {
        to localhost:8081 localhost:8082 localhost:8083
        lb_policy round_robin
        health_uri /health
        health_interval 10s
    }
}

# Proxy with path stripping
example.com {
    # Serve static files for most paths
    root * /var/www/example.com
    file_server

    # Proxy /api/* to backend
    handle /api/* {
        uri strip_prefix /api
        reverse_proxy localhost:3000
    }
}

Hugo Static Site (Common Use Case)

For a Hugo-generated site with clean URLs:

example.com www.example.com {
    # Redirect www to apex
    @www host www.example.com
    redir @www https://example.com{uri} permanent

    root * /var/www/example.com/public

    # Try .html extension for clean URLs
    try_files {path} {path}.html {path}/index.html

    file_server

    encode gzip zstd

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    # Cache assets
    @assets path /css/* /js/* /images/* /fonts/*
    header @assets Cache-Control "public, max-age=31536000, immutable"

    log {
        output file /var/log/caddy/example.com.log {
            roll_size 50mb
            roll_keep 3
            roll_keep_for 168h
        }
    }
}

Local Development Server

# Caddyfile for local dev (no HTTPS needed)
localhost:8080 {
    root * ./public
    file_server browse  # enable directory listing
    encode gzip
}

Run with:

caddy run --config Caddyfile
# or
caddy file-server --root ./public --listen :8080

Multiple Sites on One Server

# Site 1
blog.example.com {
    root * /var/www/blog
    file_server
    encode gzip
}

# Site 2
shop.example.com {
    root * /var/www/shop
    file_server
    encode gzip
}

# Site 3 โ€” reverse proxy
api.example.com {
    reverse_proxy localhost:4000
}

Useful Caddy CLI Commands

# Start Caddy with a Caddyfile
caddy run --config /etc/caddy/Caddyfile

# Reload config without downtime
caddy reload --config /etc/caddy/Caddyfile

# Validate config syntax
caddy validate --config /etc/caddy/Caddyfile

# Format/prettify Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile

# Quick file server (no Caddyfile needed)
caddy file-server --root ./public --listen :8080

# Check Caddy version
caddy version

# View running config via API
curl localhost:2019/config/

Systemd Service

# Enable and start Caddy as a system service
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy

# Reload after config change
sudo systemctl reload caddy

# View logs
sudo journalctl -u caddy --follow

Caddy vs Nginx: Quick Comparison

Feature Caddy Nginx
Automatic HTTPS Yes (built-in) No (needs certbot)
Config syntax Simple Caddyfile Complex, verbose
HTTP/3 support Yes Requires build flag
Performance Excellent Excellent
Ecosystem/plugins Growing Mature
Memory usage Slightly higher Lower

Resources

Comments