# 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 } } ```