Caddy primer

Posted on Mar 13, 2023

I recently took the time to try out Caddy in my homelab, and kinda liked it. This is a short primer mostly for myself.

Serving this site

First of, this is my Caddyfile for this site:

{
    auto_https off
}

http://solitary-thunder-2257.fly.dev {
    redir https://www.monotux.tech{uri} permanent
}

http://monotux.tech {
    redir https://www.monotux.tech{uri} permanent
}

http://www.monotux.tech {
    root * /usr/share/caddy
    file_server
}

Turning off TLS is a quirk of using fly.io, otherwise this should be fairly easy to understand.

The Dockerfile in use:

FROM klakegg/hugo:0.101.0-ext-onbuild AS hugo
FROM caddy:2
COPY --from=hugo /target/ /usr/share/caddy
COPY ./Caddyfile /etc/caddy/Caddyfile

Limiting by IP

I have prometheus metrics enabled for some of my externally accessible services, but I don’t want anyone else to read my metrics. This is one way to limit /metrics to only internal addresses:

service.example.com {
  handle /metrics {
    @denied not remote_ip 192.0.2.0/24
    respond @denied "Nope" 403
    reverse_proxy 192.0.3.1:1234
  }

  handle {
    reverse_proxy 192.0.3.1:1234
  }
}

If you just want to block everyone for a vhost:

service.example.com {
  @denied not remote_ip 192.0.2.0/24
  respond @denied "Nope" 403
  reverse_proxy 192.0.3.1:1234
}

Redirecting

old.example.com {
  redir https://new.example.com{uri} permanent
}

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

Basic auth

Generate new hash with caddy hash-password.

example.com {
  basicauth /secret/* {
    Bob $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG
  }
}

TLS with self-signed certificate to backend

I’m using TLS (with a self-signed certificate) from my caddy server to my internal systems, due to reasons.

foobar.example.com {
  @denied not remote_ip 192.0.2.0/24
  respond @denied "Nope" 403
  reverse_proxy https://foobar.internal {
    header_up Host {upstream_hostport}
    transport http {
      tls
      tls_trusted_ca_certs /config/root_ca.crt
    }
  }
}

websockets / zigbee2mqtt

I’m not sure this is the easiest way to do this, but here I want to proxy all requests to /api to a websocket, and / over HTTP. In this case it’s a zigbee2mqtt container running in the same docker network as caddy. I’m also using my internal certificate authority in this example.

Not that this is without authentication.

{
  email me@example.com
  acme_ca https://ca.example.com/acme/acme/directory
  acme_ca_root /config/root_ca.crt
}

zigbee2mqtt.example.com {
  @websockets {
        header Connection *Upgrade*
        header Upgrade websocket
        path /api/*
  }
  reverse_proxy @websockets zigbee2mqtt:8080/api
  reverse_proxy zigbee2mqtt:8080
}

The docker-compose.yml file in question:

version: '3'
services:
  zigbee2mqtt:
    container_name: zigbee2mqtt
    restart: unless-stopped
    image: koenkk/zigbee2mqtt
    volumes:
      - /path/to/zigbee2mqtt/data:/app/data
      - /run/udev:/run/udev:ro
    environment:
      - TZ=Europe/Stockholm
    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0

  caddy:
    container_name: caddy
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - /path/to/caddy/Caddyfile:/etc/caddy/Caddyfile
      - /path/to/caddy/data:/data
      - /path/to/caddy/config:/config

End of transmission