# 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)
```sh
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
```sh
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:
- https://github.com/mholt/caddy-webdav - WebDAV module for Caddy, to support the WebDAV file management protocol
- https://github.com/abiosoft/caddy-json-parse - Allows parsing a JSON request body, so that it's contents may be accessed via variables in directives etc.
- https://github.com/caddy-dns/cloudflare - Cloudflare DNS module - for LetsEncrypt domain verification via DNS records using Cloudflare's DNS API
- https://github.com/gamalan/caddy-tlsredis - Caddy Storage Backend using Redis replicas. Can be used for managing TLS certificates across a cluster of Caddy servers.
### xcaddy dumps the build files into the current directory, so it's best to CD into a tempdir before using it
```sh
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.
```sh
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
```sh
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`
```c
[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)
```cs
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
```cs
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
```cs
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)
```cs
{
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
```cs
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
```cs
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:
```json
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
```sh
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
```cs
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
```cs
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:
```cs
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:
```cs
*.example.com, example.com {
root * /var/www/example.com
file_server
tls {
on_demand
}
}
```
## Listen on a port and allow any domain, without SSL
```cs
: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)
```perl
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
}
}
```