Caddy Web Server: Automatic HTTPS Made Effortless

Deploy Caddy as your web server and reverse proxy with zero-config automatic HTTPS. Covers Caddyfile syntax, reverse proxy patterns, Docker integration,...

Y
Yash Pritwani
11 min read

One-field diagnostic start

Send one work email. Yash replies with the matching service path, first evidence step, and owner handoff for this issue.

No calendar step. The full contact form stays available if you want to add system context.

One owner, one affected system, and the next buyer or recovery deadline mapped.

Why Caddy?

Caddy is a web server written in Go that provisions and renews HTTPS certificates automatically. No certbot, no cron jobs, no manual renewal. Just point your domain at Caddy, and it handles everything.

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 170" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="170" rx="12" fill="#1a1a2e"/><circle cx="60" cy="85" r="25" fill="#f59e0b" opacity="0.85"/><text x="60" y="82" text-anchor="middle" fill="#1a1a2e" font-size="9" font-family="system-ui" font-weight="bold">Trigger</text><text x="60" y="94" text-anchor="middle" fill="#1a1a2e" font-size="8" font-family="system-ui">webhook</text><polygon points="175,55 210,85 175,115 140,85" fill="#6366f1" opacity="0.85"/><text x="175" y="88" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">If</text><rect x="250" y="35" width="100" height="40" rx="6" fill="#2dd4bf" opacity="0.85"/><text x="300" y="55" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Send Email</text><text x="300" y="67" text-anchor="middle" fill="#1a1a2e" font-size="8" font-family="system-ui">SMTP</text><rect x="250" y="95" width="100" height="40" rx="6" fill="#a855f7" opacity="0.85"/><text x="300" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Log Event</text><text x="300" y="127" text-anchor="middle" fill="#ffffff" font-size="8" font-family="system-ui">database</text><rect x="400" y="55" width="100" height="40" rx="6" fill="#3b82f6" opacity="0.85"/><text x="450" y="75" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Update CRM</text><text x="450" y="87" text-anchor="middle" fill="#ffffff" font-size="8" font-family="system-ui">API call</text><circle cx="545" cy="75" r="18" fill="none" stroke="#2dd4bf" stroke-width="2"/><text x="545" y="79" text-anchor="middle" fill="#2dd4bf" font-size="9" font-family="system-ui">Done</text><defs><marker id="arrow10" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="87" y1="85" x2="138" y2="85" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="210" y1="72" x2="248" y2="55" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="210" y1="98" x2="248" y2="115" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="352" y1="55" x2="398" y2="68" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="352" y1="115" x2="398" y2="82" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="502" y1="75" x2="525" y2="75" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><text x="225" y="45" text-anchor="middle" fill="#2dd4bf" font-size="8" font-family="system-ui">true</text><text x="225" y="120" text-anchor="middle" fill="#a855f7" font-size="8" font-family="system-ui">false</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Workflow automation: triggers, conditions, and actions chain together to eliminate manual processes.</p></div>

Compare the config needed for HTTPS:

Nginx: 20+ lines for SSL config, plus certbot setup, plus renewal cron job. Caddy: Zero lines. It just works.

Installation

Docker

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"  # HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:

Binary

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

Caddyfile Basics

Static File Server

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

That is the entire configuration. Caddy will automatically obtain an HTTPS certificate from Let's Encrypt, redirect HTTP to HTTPS, and serve your files.

Reverse Proxy

api.example.com {
    reverse_proxy localhost:8080
}

app.example.com {
    reverse_proxy localhost:3000
}

docs.example.com {
    reverse_proxy localhost:4000
}

Three services, three lines each. Automatic HTTPS for all of them.

SPA (Single Page Application)

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

Path-Based Routing

example.com {
    handle /api/* {
        reverse_proxy api-service:8080
    }

    handle /docs/* {
        reverse_proxy docs-service:4000
    }

    handle {
        root * /var/www/frontend
        try_files {path} /index.html
        file_server
    }
}

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 170" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="170" rx="12" fill="#1a1a2e"/><text x="60" y="30" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Internet</text><circle cx="60" cy="60" r="25" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="60" y="57" text-anchor="middle" fill="#3b82f6" font-size="18" font-family="system-ui">&#x1F310;</text><rect x="155" y="25" width="120" height="120" rx="10" fill="#6366f1" opacity="0.15"/><rect x="155" y="25" width="120" height="120" rx="10" fill="none" stroke="#6366f1" stroke-width="1.5"/><text x="215" y="50" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Reverse</text><text x="215" y="65" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Proxy</text><text x="215" y="85" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">TLS termination</text><text x="215" y="98" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">Load balancing</text><text x="215" y="111" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">Path routing</text><text x="215" y="124" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">Rate limiting</text><rect x="350" y="20" width="110" height="35" rx="6" fill="#2dd4bf" opacity="0.8"/><text x="405" y="42" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">app.example.com</text><rect x="350" y="65" width="110" height="35" rx="6" fill="#a855f7" opacity="0.8"/><text x="405" y="87" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">api.example.com</text><rect x="350" y="110" width="110" height="35" rx="6" fill="#f59e0b" opacity="0.8"/><text x="405" y="132" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">cdn.example.com</text><defs><marker id="arrow11" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="87" y1="60" x2="153" y2="75" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow11)"/><line x1="277" y1="55" x2="348" y2="37" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow11)"/><line x1="277" y1="85" x2="348" y2="82" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow11)"/><line x1="277" y1="115" x2="348" y2="127" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow11)"/><text x="120" y="55" text-anchor="middle" fill="#2dd4bf" font-size="8" font-family="system-ui">HTTPS</text><text x="505" y="42" text-anchor="start" fill="#94a3b8" font-size="8" font-family="system-ui">:3000</text><text x="505" y="87" text-anchor="start" fill="#94a3b8" font-size="8" font-family="system-ui">:8080</text><text x="505" y="132" text-anchor="start" fill="#94a3b8" font-size="8" font-family="system-ui">:9000</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">A reverse proxy terminates TLS, routes requests by hostname, and load-balances across backend services.</p></div>

Advanced Features

Compression

example.com {
    encode gzip zstd
    reverse_proxy backend:8080
}

Rate Limiting

example.com {
    rate_limit {
        zone dynamic {
            key {remote_host}
            events 100
            window 1m
        }
    }
    reverse_proxy backend:8080
}

Headers and Security

example.com {
    header {
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        -Server
    }
    reverse_proxy backend:8080
}

Basic Authentication

admin.example.com {
    basicauth {
        admin JDJhJDE0JDlENmRYaHlBdGVJNk5...(bcrypt hash)
    }
    reverse_proxy admin-panel:9090
}

Generate the hash with:

caddy hash-password --plaintext "your-password"

WebSocket Proxy

ws.example.com {
    reverse_proxy backend:8080 {
        # WebSocket support is automatic in Caddy
        # No special headers needed
    }
}

Caddy with Docker Compose

services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

  frontend:
    image: myapp/frontend:latest

  api:
    image: myapp/api:latest

  docs:
    image: myapp/docs:latest
# Caddyfile
app.example.com {
    reverse_proxy frontend:3000
}

api.example.com {
    reverse_proxy api:8080
}

docs.example.com {
    reverse_proxy docs:4000
}

Caddy vs Nginx vs Traefik

Feature
Caddy
Nginx
Traefik

|---------|-------|-------|---------|

Auto HTTPS
Built-in
certbot
Built-in
Config Format
Caddyfile
nginx.conf
YAML/TOML/Labels
Docker Discovery
Plugin
No
Built-in
Memory
~30MB
~5MB
~50MB
HTTP/3
Built-in
Experimental
Built-in
Config Reload
Instant API
nginx -s reload
Hot reload
Best For
Simplicity
Raw performance
Docker/K8s

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="200" rx="12" fill="#1a1a2e"/><rect x="60" y="30" width="140" height="140" rx="6" fill="none" stroke="#e2e8f0" stroke-width="1.5"/><text x="130" y="24" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Production</text><rect x="70" y="40" width="120" height="22" rx="3" fill="#6366f1" opacity="0.8"/><circle cx="82" cy="51" r="3" fill="#2dd4bf"/><text x="130" y="55" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Web Server</text><rect x="70" y="68" width="120" height="22" rx="3" fill="#6366f1" opacity="0.8"/><circle cx="82" cy="79" r="3" fill="#2dd4bf"/><text x="130" y="83" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">App Server</text><rect x="70" y="96" width="120" height="22" rx="3" fill="#a855f7" opacity="0.8"/><circle cx="82" cy="107" r="3" fill="#2dd4bf"/><text x="130" y="111" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Database</text><rect x="70" y="124" width="120" height="22" rx="3" fill="#f59e0b" opacity="0.6"/><circle cx="82" cy="135" r="3" fill="#2dd4bf"/><text x="130" y="139" text-anchor="middle" fill="#1a1a2e" font-size="9" font-family="system-ui">Monitoring</text><rect x="290" y="30" width="140" height="140" rx="6" fill="none" stroke="#e2e8f0" stroke-width="1.5"/><text x="360" y="24" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Staging</text><rect x="300" y="40" width="120" height="22" rx="3" fill="#3b82f6" opacity="0.6"/><circle cx="312" cy="51" r="3" fill="#2dd4bf"/><text x="360" y="55" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Web Server</text><rect x="300" y="68" width="120" height="22" rx="3" fill="#3b82f6" opacity="0.6"/><circle cx="312" cy="79" r="3" fill="#2dd4bf"/><text x="360" y="83" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">App Server</text><rect x="300" y="96" width="120" height="22" rx="3" fill="#a855f7" opacity="0.5"/><circle cx="312" cy="107" r="3" fill="#f59e0b"/><text x="360" y="111" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Database</text><line x1="200" y1="100" x2="290" y2="100" stroke="#2dd4bf" stroke-width="1.5" stroke-dasharray="5,3"/><text x="245" y="95" text-anchor="middle" fill="#2dd4bf" font-size="8" font-family="system-ui">VLAN</text><rect x="480" y="60" width="90" height="70" rx="6" fill="none" stroke="#f59e0b" stroke-width="1" stroke-dasharray="4,3"/><text x="525" y="85" text-anchor="middle" fill="#f59e0b" font-size="9" font-family="system-ui">Backup</text><text x="525" y="100" text-anchor="middle" fill="#f59e0b" font-size="9" font-family="system-ui">Storage</text><text x="525" y="115" text-anchor="middle" fill="#94a3b8" font-size="8" font-family="system-ui">3-2-1 Rule</text><line x1="430" y1="100" x2="478" y2="95" stroke="#f59e0b" stroke-width="1" stroke-dasharray="4,3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Server infrastructure: production and staging environments connected via VLAN with offsite backups.</p></div>

When to Choose Caddy

You want zero-config HTTPS
You value simplicity over fine-grained control
You do not need Docker service discovery (use Traefik for that)
You want HTTP/3 support out of the box
You are running a small to medium deployment

At TechSaaS, we use Traefik for our Docker-based infrastructure because of its native service discovery. But for standalone projects and client deployments that do not use Docker, we recommend Caddy. Its simplicity is unmatched.

Need help choosing a web server? Contact [email protected].

#caddy#web-server#https#reverse-proxy#lets-encrypt

Need the next owner and evidence step mapped?

Send the current system and deadline. Yash replies with the service path, first proof artifact, and handoff owner.