Try   HackMD

Caddy Guide / Cheatsheet

Building Caddy with custom modules (extensions)

(1) Install XCaddy

To build Caddy with custom modules, first you'll want the XCaddy tool:  https://github.com/caddyserver/xcaddy/releases

For Linux (AMD64), the following commands can be ran to install XCaddy v0.1.5 (released 30 Jul 2020 - latest version as of 26 Oct 2020)

mkdir -p /tmp/xcaddy
cd /tmp/xcaddy

wget https://github.com/caddyserver/xcaddy/releases/download/v0.1.5/xcaddy_0.1.5_linux_amd64.tar.gz

tar xvzf xcaddy_0.1.5_linux_amd64.tar.gz

sudo install -v xcaddy /usr/local/bin/
cd -
rm -rvf /tmp/xcaddy

(2) Install the Golang development tools

To use XCaddy, you need the Golang (Go) development tools to compile Caddy (and the extra modules) from source code.

For Ubuntu 18.04 / 20.04 - it's usually best to install the dev tools via Ubuntu's snap system

sudo snap install --classic go

(3) Build a custom Caddy executable with the modules you'd like

In the following example, we'll build the latest Caddy with the following extra modules:

xcaddy dumps the build files into the current directory, so it's best to CD into a tempdir before using it

mkdir -p /tmp/custcaddy
cd /tmp/custcaddy

Use XCaddy to build Caddy from source with additional modules built-in

For each Caddy module, add a 'with' argument, pointing to the github repo URL, without the http(s):// at the start.

xcaddy build --with github.com/mholt/caddy-webdav \
            --with github.com/abiosoft/caddy-json-parse \
            --with github.com/caddy-dns/cloudflare \
            --with github.com/gamalan/caddy-tlsredis

# Assuming the building was successful, you'll now have the EXE 'caddy' in the current directory for you to install

sudo install -v caddy /usr/local/bin/

# Cleanup by removing the old folder we were building in

cd -
rm -rvf /tmp/custcaddy

(4) Update the SystemD service caddy.service to use your custom built Caddy

If you originally installed Caddy from package repos such as the fury.io repos for Ubuntu/Debian, then you'll want to update the systemd service for Caddy so that it points at /usr/local/bin/caddy (your custom built caddy with modules) instead of /usr/bin/caddy (original caddy from apt/yum)

Use systemctl to create a systemd overrides file, rather than editing the service file directly

sudo systemctl edit caddy

Inside of the overrides file editor (nano or vim), enter the following, which tells systemd to clear the original caddy's ExecStart / ExecReload lines, and replace them with appropriate start/reload commands which use the customised /usr/local/bin/caddy instead of /usr/bin/caddy

[Service]
ExecStart=
ExecReload=

ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile

Configuration

IP Filtering / IP Access List ( IP ACL ) / IP Whitelist

NOTE: Unfortunately, due to the design of Caddy, you'll need to copy the matcher into every domain block you want to use it in. You can't make a globally usable named matcher.

Option 1 - specify an 'xxxx_ips' matcher, and use two route directives, one matching @xxxx_ips, the other with no matcher (matches non-whitelisted IPs)

example.com {

    @privex_ips remote_ip 185.130.44.0/27 185.130.45.0/27 2a07:e00::/32 2a07:e01:5::/48

    route @privex_ips {
        reverse_proxy 127.0.0.1:8910
    }
    route {
        respond "Unauthorised" 403
    }
}

Option 2 - specify both a 'xxxx_ips' and 'not_ips' matcher, allowing for a clear "unauthorised" response to non-whitelisted IPs

example.com {

    @privex_ips remote_ip 185.130.44.0/27 185.130.45.0/27 2a07:e00::/32 2a07:e01:5::/48

    @not_privex not remote_ip 185.130.44.0/27 185.130.45.0/27 2a07:e00::/32 2a07:e01:5::/48

    reverse_proxy @privex_ips 127.0.0.1:8910
    respond @not_privex "Unauthorised" 403
}

Option 3 - Specify JUST the whitelist matcher - non-whitelisted IPs will be sent a completely blank HTTP response

example.com {

    @privex_ips remote_ip 185.130.44.0/27 185.130.45.0/27 2a07:e00::/32 2a07:e01:5::/48

    reverse_proxy @privex_ips 127.0.0.1:8910
}

Using LetsEncrypt (Certbot) Staging / Testing Server

To avoid hitting certificate request rate limits, it's a good idea to test you can get SSL certs from staging first.

Staging LetsEncrypt must be configured in the root matcher block (inside curly braces before any other matchers)

{
    acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

Forwarding incoming IP address to proxied application

The variable 'remote_host' contains the IPv4 / IPv6 address of the client connecting to the HTTP server

example.com {
    # Proxy to 127.0.0.1:8001 - and pass the incoming IP
    reverse_proxy 127.0.0.1:8001 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
    }
}

Passing various information about the connection / client / site config to the backend

Full http.xxx variable list: https://caddyserver.com/docs/modules/http#docs

testdom.net.someguy123.com:80 {
    reverse_proxy 127.0.0.1:5000 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Test-Scheme {http.request.scheme}
        header_up X-Test-QueryString {http.request.uri.query}
        header_up X-Test-URI {http.request.uri}
    }
}

Example of headers the backend would receive when running:

curl -fsSL http://testdom.net.someguy123.com/\?hello\=world

{
  "Accept": "*/*",
  "Accept-Encoding": "gzip",
  "Host": "testdom.net.someguy123.com",
  "User-Agent": "curl/7.54.0",
  "X-Forwarded-For": "2a07:e01:5::1337",
  "X-Forwarded-Proto": "http",
  "X-Real-Ip": "2a07:e01:5::1337",
  "X-Test-Querystring": "hello=world",
  "X-Test-Scheme": "http",
  "X-Test-Uri": "/?hello=world"
}

Handling static files / Route URI stripping

netbox.privex.bz {

    @privex_ips remote_ip 185.130.44.0/27 185.130.45.0/27 2a07:e00::/32 2a07:e01:5::/48

    # Static files are in this folder
    root * /home/netbox/netbox/netbox/static
    # Serve static files under netbox.privex.bz/static/
    route /static/* {

        # Strip /static/ from the URI to ensure only the path after /static/ is passed to file_server

        uri strip_prefix /static

        # Requests for this route should then be served by the static file server

        file_server
    }

    # Normal non-static-file requests should be checked against the privex_ips ACL

    # and then proxied to the real application behind the scenes
    route @privex_ips {
        reverse_proxy 127.0.0.1:8001 {
            header_up X-Real-IP {remote_host}
            header_up X-Forwarded-For {remote_host}
        }
    }
}

Access Logs

example.com {
    root * /var/www/example.com
    file_server
    log {
        output file /var/log/caddy/example.com.access.log {
            roll_size 100mb
            roll_keep 5
            # 720 hrs = 30 days
            roll_keep_for 720h
        }
        format logfmt
    }
}

Redirects

example.com {
    route / {
        redir https://example.org/hello/world
    }
}

Multiple Domains / Sub-domains per block

Simple specify each domain separated by a comma and a space:

test.example.com, example.com, example.org {
    root * /var/www/example.com
    file_server
}

Wildcard SSL without DNS using TLS on demand (SSL/TLS On-Demand)

Using TLS on-demand, Caddy will automatically obtain an SSL certificate for each sub-domain only when a sub-domain is requested for the first time.

This allows you to avoid having to configure Caddy to have access to your domain's DNS provider to use wildcard DNS:

*.example.com, example.com {
    root * /var/www/example.com
    file_server
    tls {
        on_demand
    }
}

Listen on a port and allow any domain, without SSL

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

:8181 {
    root * /var/www/test
    file_server
}

URL Re-writing (like .htaccess or index.php query strings)

nms.privex.bz {
    root * /var/www/librenms/html
    @api {
        path_regexp ^/api/v0(.*)
    }
    @index {
        path_regexp ^/(.*)$
    }

    route @privex_ips {
        file_server /svg/*
        file_server /js/*
        file_server /css/*
        file_server /images/*
        file_server /fonts/*
        file_server /svg/*
        rewrite @api /api_v0.php?{1}
        rewrite @index /index.php/{1}
        php_fastcgi unix//var/run/php/php7.2-fpm.sock
        file_server
    }
    route {
        respond "Unauthorized" 403
    }
}