diff --git a/caddy/README.md b/caddy/README.md index 89db15f..a54fa81 100644 --- a/caddy/README.md +++ b/caddy/README.md @@ -5,7 +5,7 @@ tagline: | Caddy is a fast, multi-platform web server with automatic HTTPS. --- -To update or switch versions, run `webi caddy@stable` (or `@v2.4`, `@beta`, +To update or switch versions, run `webi caddy@stable` (or `@v2.7`, `@beta`, etc). ### Files @@ -16,7 +16,9 @@ install: ```text ~/.config/envman/PATH.env ~/.local/bin/caddy + ~/.config/caddy/autosave.json +~/.config/caddy/env ~/.local/share/caddy/certificates/ /Caddyfile ``` @@ -27,25 +29,128 @@ install: > reverse proxy APIs and WebSockets to other apps - such as those written node, > Go, python, ruby, and PHP. -Here's the things we find most useful: +We've split what we find most useful into two categories: -- Simple File & Directory Server -- Reverse Proxy with www (and HTTPS) redirects -- Running as a system service on - - Linux - - MacOS - - Windows +- **Caddy for Developers** (Caddyfile) + - Serve Static Files & Directories + - Warning-free HTTPS on localhost + - Redirect (ex: www, https) + - Logging + - Compression + - Reverse Proxy + - Rewrite Paths + - CORS + - Wildcard Domain Example (with DuckDNS) + - TLS on Private DNS (192.168.x.x) + - Variables & Placeholders + - **Comprehensive Caddyfile Example** + - As a macOS service (`launchd` & `launchctl`) + - As a Windows service (starup item) + - As a Linux service (`systemd` & `systemctl`) +- **Caddy for DevOps** (JSON Config & API) + - JSON Config Overview + - fmt & lint the Caddyfile + - Caddyfile to JSON Config + - JSON Config Admin + - Code Editor autocomplete + - Backup + - Restore + - Manage & Update Config + - How to use ENVs + - HTTP Basic Authorization + - Prevent Dev Sites from Hijacking Production SEO + - Wildcard & Private IP Certs + - `libdns` DNS Providers + - `lego` DNS Providers + - Use HTTP _only_ (no TLS/HTTPS) + - Use Non-Standard Ports + - Permission to Use Ports 80 & 443 + - Run with `systemd` (VM, VPS) + - Run with `openrc` (Container, Docker) -### How to serve a directory +## Caddy for Developers + +```sh +mkdir -p ~/.config/caddy/ +touch ~/.config/caddy/env + +caddy run --config ./Caddyfile --envfile ~/.config/caddy/env +``` + +- `run` runs in the foreground +- `start` starts a background service (daemon) + +**Warning**: `~/.config/caddy/autosave.json` is _overwritten_ each time `caddy` +is run with a Caddyfile! + +See also: + +- [Wiki Guides][wiki]: + +[wiki]: https://caddy.community/c/wiki/13 +[concepts]: https://caddyserver.com/docs/caddyfile/concepts#structure +[encode]: https://caddyserver.com/docs/caddyfile/directives/encode +[file]: https://caddyserver.com/docs/json/apps/http/servers/routes/match/file/ +[file_server]: https://caddyserver.com/docs/caddyfile/directives/file_server +[file-server]: https://caddyserver.com/docs/command-line#caddy-file-server +[handle]: https://caddyserver.com/docs/caddyfile/directives/handle +[handle_path]: https://caddyserver.com/docs/caddyfile/directives/handle_path +[http]: https://caddyserver.com/docs/json/apps/http/#docs +[import]: https://caddyserver.com/docs/caddyfile/directives/import +[log]: https://caddyserver.com/docs/caddyfile/directives/log +[placeholders]: https://caddyserver.com/docs/caddyfile/concepts#placeholders +[placeholders2]: https://caddyserver.com/docs/conventions#placeholders +[snippets]: https://caddyserver.com/docs/caddyfile/concepts#snippets +[redir]: https://caddyserver.com/docs/caddyfile/directives/redir +[reverse_proxy]: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy +[rewrite]: https://caddyserver.com/docs/caddyfile/directives/rewrite +[root]: https://caddyserver.com/docs/caddyfile/directives/root +[tls]: https://caddyserver.com/docs/caddyfile/directives/tls +[trusted_proxies]: + https://caddyserver.com/docs/caddyfile/options#trusted-proxies + +### How to Serve Files & Directories + +Using the convenience `file-server` command: ```sh caddy file-server --browse --listen :4040 ``` +Using `Caddyfile`: + +```Caddyfile +localhost { + # ... + + handle /* { + root * ./public/ + file_server { + browse + } + } +} +``` + +- `browse` enables the built-in directory explorer + +See also: + +- [CLI: file-server][file-server]: + +- [`handle`][handle]: +- [`root`][root]: +- [`file_server`][file_server]: + + ### How to serve HTTPS on localhost Caddy can be used to test with https on localhost. +It's fully _automatic_ and works in your local browser **without warnings**, +assuming you accept the prompt to add the temporary root certificate to your OS +keychain. + `Caddyfile`: ```Caddyfile @@ -56,18 +161,456 @@ localhost { handle /* { root * ./public/ - file_server + file_server { + # ... + } } } ``` ```sh -caddyfile run --config ./Caddyfile +caddy run --config ./Caddyfile ``` -### How to redirect and reverse proxy +See also: -Here's what a fairly basic `Caddyfile` looks like: +- [`handle`][handle]: +- [`root`][root]: +- [`file_server`][file_server]: + +### How to Redirect www and HTTPS + +HTTPS redirects are _automatic_. + +www redirects can be done like this: + +```Caddyfile +# redirect www to apex domain +www.example.com { + redir https://example.com{uri} permanent +} + +example.com { + # ... +} +``` + +If you have legacy systems that require the reverse, perhaps to deal with legacy +cookie policies, you can do that too. + +See also: + +- [`redir`][redir]: + +### How to Log to System Logger + +```Caddyfile +example.com { + # log to stdout, which is captured by journalctl, etc + log { + output stdout + format console + } + + # ... +} +``` + +See also: + +- [`log`][log]: + +### How to Enable Compression + +```Caddyfile +example.com { + + # enable streaming compression + encode gzip zstd + + handle /* { + file_server { + root /srv/example.com/public/ + + # enable static compression + precompressed br,gzip + } + } + + # ... +} +``` + +- `precompressed` will serve `index.html.br` (or `index.html.gz`) instead of + `index.html`, when available + - Why [not zstd][caniusezstd]? + - [brotli is best][caniusebr] for precompressed (static files) + - [gzip is best][caniusegz] for streaming (JSON API). + +[caniusebr]: https://caniuse.com/?search=brotli +[caniusegz]: https://caniuse.com/?search=gzip +[caniusezstd]: https://caniuse.com/?search=zstd + +See also: + +- [`encode`][encode]: +- [`root`][root]: +- caniuse | zstd: + +### How to Reverse Proxy + +- `X-Forwarded-*` are set by default: + - `X-Forwarded-For` (XFR) is the _Request IP_ + - `X-Forwarded-Proto` (XFP) is set to `http` for plaintext or `https` for TLS + - `X-Forwarded-Host` (XFH) is the original `Host` header from the client +- `trusted_proxies` can be set to allow header pass thru from another proxy + - `private_ranges` is a built-in alias for \ + `192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 127.0.0.1/8 fd00::/8 ::1` +- `X-Accel-Redirect` can be set to allow static file passthru serving (also + known as `X-SendFile` or `X-LIGHTTPD-send-file`) + +```Caddyfile +{ + servers { + trusted_proxies static private_ranges + } +} + +example.com { + # ... + + handle /api/* { + reverse_proxy localhost:3000 { + + @accel header X-Accel-Redirect * + handle_response @sendfile { + root * /srv/assets + rewrite * {http.response.header.X-Accel-Redirect} + file_server + } + + } + } +} +``` + +See also: + +- [`reverse_proxy#headers`][reverse_proxy]: + +- [`trusted_proxies`][trusted_proxies]: + +- +- +- +- +- + +### How to Rewrite Paths + +Rather than `reverse_proxy`, this could just as well be handled by +`file_server`. + +`handle_path` _eats_ the path, whereas `handle` _matches_ without consuming. + +```Caddyfile +example.com { + # ... + + # {host}/api/oldpath/* => http://localhost:3000/api/newpath/* + handle_path /api/oldpath/* { + rewrite * /api/newpath{path} + reverse_proxy localhost:3000 + } +} +``` + +### How to handle CORS Preflight + Request + +For static files: + +```Caddyfile +(cors) { + @cors_preflight{args.0} method OPTIONS + @cors{args.0} header Origin {args.0} + + handle @cors_preflight{args.0} { + header { + Access-Control-Allow-Origin "{args.0}" + Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" + Access-Control-Allow-Headers * + Access-Control-Max-Age "3600" + defer + } + respond "" 204 + } + + handle @cors{args.0} { + header { + Access-Control-Allow-Origin "{args.0}" + Access-Control-Expose-Headers * + defer + } + } +} + +example.com { + root * /srv/public/ + file_server + import cors https://member.example.com + import cors https://whatever.com +} +``` + +For an API: + +- `*` wildcards may NOT be used for authenticated API requests +- `Access-Control-Expose-Headers` exposes to _JavaScript_, not just the browser + +```Caddyfile +(cors-api) { + @cors-preflight-wild method OPTIONS + @cors-wild header Origin {http.request.host} + + handle @cors-preflight-wild { + header { + Access-Control-Allow-Origin "{http.request.host}" + Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" + Access-Control-Allow-Headers "Origin, Accept, Authorization, Content-Type, X-Requested-With" + Access-Control-Allow-Credentials "true" + Access-Control-Max-Age "3600" + defer + } + respond "" 204 + } + + handle @cors-wild { + header { + Access-Control-Allow-Origin "{http.request.host}" + Access-Control-Allow-Credentials "true" + Access-Control-Expose-Headers "Content-Encoding" + Vary "Accept-Encoding, Origin" + defer + } + } +} + +api.example.com { + handle /api/* { + import cors-api + reverse_proxy localhost:3000 + } +} +``` + +See also: + +- [`import`][import]: +- +- +- +- +- +- + + + +### How to Wildcards & Private DNS + +DNS Providers are required for + +- wildcards (`*.example.com`) +- Private IPs / Private DNS (`192.168.x.x`) +- Running Caddy directly on non-standard ports (`3000`, `8443`) + +Example with DuckDNS: + +1. Put the credentials in your dotenv (the name is arbitrary): \ + `caddy.env`: + ```sh + MY_DUCKDNS_TOKEN=xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx + ``` +2. Add the `tls` directive in the format of + `dns [documented params]`: + + ```Caddyfile + # a wildcard domain + *.example.duckdns.org { + tls { + dns duckdns {env.MY_DUCKDNS_TOKEN} + } + + # ... + } + + # an intranet domain (on a private network, such as 192.168.x.x) + local.example.duckdns.org { + tls { + dns duckdns {env.MY_DUCKDNS_TOKEN} + } + + # ... + } + ``` + +For more information see **How to use libdns providers** below in the DevOps +section. + +See also: + +- [`tls`][tls]: +- +- (search `dns.providers`) +- +- + +### How to use Caddyfile Meta Variables + +- "Placeholders" and "Shorthand" are the variables that look like: + - `{http.request.uri}` + - `{request.uri}` + - `{uri}` + - `{path}` + - `{host}` + - `{http.response.header}` + - `{args[0]}` +- Environment Variables come in Parse-time and Runtime variety: + - `{$DUCKDNS_API_TOKEN}`, `{$BASIC_AUTH_DIGEST}` (parse-time) + - `{env.DUCKDNS_API_TOKEN}`, `{env.BASIC_AUTH_DIGEST}` (runtime) +- "Named Matchers" can substitute paths in most places: + + ```diff + # match this secret path to find hidden treasures + handle_path /easter-eggs/* { + root * /srv/my-eggs + file_server + } + + # match this secret header to find hidden treasures + @my-easter-egg { + header X-Magic-Word "Easter-Egg" + } + handle @my-easter-egg { + root * /srv/my-eggs + file_server + } + ``` + +- "Imports" and "Snippets" are the templates that look like: + + ```Caddyfile + # (template-name) + (my-no-plaintext) { + + # @matcher-name + @my-plaintext { + protocol http + } + + # use of matcher + redir @my-plaintext https://{host}{uri} + } + + example.com { + # import the snippet + import my-no-plaintext + } + ``` + +See also: + +- [Overview][concepts]: + +- [Placeholders][placeholders]: + +- [Snippets][snippets]: + + +### Placeholder Hierarchy + +```text +Path # Shorthand +├── args[] # in snippets (template functions) +├── env.* +├── http +│ ├── error.+ # {err.+} +│ ├── matchers +│ │ ├── file.+ # {file_match.+} +│ │ ├── header_regexp.? +│ │ ├── path_regexp.? +│ │ └── vars_regexp.? +│ ├── regexp.*[] # {re.*.1} +│ ├── request +│ │ ├── cookie.* # {cookie.*} +│ │ ├── header.* # {header.*} +│ │ ├── host +│ │ │ ├── labels[] # {labels.0} (as rDNS: com.example) +│ │ │ ├── port # {hostport} +│ │ │ ├── host # {host} +│ │ │ ├── method # {method} +│ │ │ ├── port # {port} +│ │ │ ├── remote # {remote} +│ │ │ │ ├── host # {remote_host} +│ │ │ │ └── port # {remote_port} +│ │ │ ├── scheme # {scheme} +│ │ │ ├── tls +│ │ │ │ ├── cipher_suite # {tls_cipher} +│ │ │ │ ├── client +│ │ │ │ │ ├── certificate_der_base64 # {tls_client_certificate_der_base64} +│ │ │ │ │ ├── certificate_pem # {tls_client_certificate_pem} +│ │ │ │ │ ├── fingerprint # {tls_client_fingerprint} +│ │ │ │ │ ├── issuer # {tls_client_issuer} +│ │ │ │ │ ├── serial # {tls_client_serial} +│ │ │ │ │ └── subject # {tls_client_subject} +│ │ │ │ └── version # {tls_version} +│ │ ├── uri # {uri} +│ │ │ ├── path.+ # {path.+} +│ │ │ │ ├── dir # {dir} +│ │ │ │ └── file.+ # {file} +│ │ │ │ ├── base # {file.base} +│ │ │ │ └── ext # {file.ext} +│ │ │ └── query.* # {query.*} +│ ├── reverse_proxy.+ # {rp.+} +│ │ └── upstream # {upstream} +│ │ │ └── hostport # {upstream_hostport} +│ └── vars.* # {vars.*} +│ └── client_ip # {client_ip} +├── system +│ ├── hostname +│ ├── slash +│ ├── os +│ ├── arch +│ └── wd +└── time + └── now + ├── common_log + ├── http + ├── unix + ├── unix_ms + └── year +``` + +- `[]` signifies a list accessible by index, such as `labels.0` +- `.+` signifies more pre-defined keys, see docs (linked below) for specifics +- `.*` signifies that the keys are arbitrary per the config or the request +- `.?` signifies that we didn't understand the documentation + +See also: + +- [Concepts: Placeholders][placeholders]: + +- [Conventions: Placeholders][placeholders2]: + +- [`http`][http]: +- [`file`][file]: + + +### Putting it All Together + +Here's what a fairly basic, but comprehensive and complete `Caddyfile` looks +like: `Caddyfile`: @@ -129,157 +672,702 @@ example.com { # serve static files handle /* { root * /srv/example.com/public/ - file_server + file_server { + precompressed br,gzip + } } } ``` +### How to run Caddy as a macOS Service + +To avoid the nitty-gritty details of `launchd` plist files, you can use +[`serviceman`](../serviceman/) to template out the plist file for you: + +1. Install [`serviceman`](../serviceman/) + ```sh + webi serviceman + ``` +2. Use Serviceman to create a _launchd_ plist file + + ```sh + my_username="$( id -u -n )" + + serviceman add --user --name caddy -- \ + caddy run --config ./Caddyfile --envfile ~/.config/caddy/env + ``` + + (this will create `~/Library/LaunchAgents/caddy.plist`) + +3. Manage the service with `launchctl` + ```sh + launchctl unload -w ~/Library/LaunchAgents/caddy.plist + launchctl load -w ~/Library/LaunchAgents/caddy.plist + ``` + +This process creates a _User-Level_ service in `~/Library/LaunchAgents`. To +create a _System-Level_ service in `/Library/LaunchDaemons/` instead: + ```sh -caddyfile run --config ./Caddyfile +sudo serviceman add --system --name caddy -- \ + caddy run --config ./Caddyfile --envfile ~/.config/caddy/env ``` -- [`log`](https://caddyserver.com/docs/caddyfile/directives/log) -- [`encode`](https://caddyserver.com/docs/caddyfile/directives/encode) -- [`handle`](https://caddyserver.com/docs/caddyfile/directives/handle) -- [`handle_path`](https://caddyserver.com/docs/caddyfile/directives/handle_path) -- [`root`](https://caddyserver.com/docs/caddyfile/directives/root) -- [`file_server`](https://caddyserver.com/docs/caddyfile/directives/file_server) +### How to run Caddy as a Windows Service -### How to rewrite and reverse proxy +1. Set a **Windows Firewall Rule** to allow traffic to Caddy. \ + You can do this with _PowerShell_ by changing `YOUR_USER` in the script below + and running it in `cmd.exe` as Administrator: + ```pwsh + powershell.exe -WindowStyle Hidden -Command $r = Get-NetFirewallRule -DisplayName 'Caddy Web Server' 2> $null; if ($r) {write-host 'found rule';} else {New-NetFirewallRule -DisplayName 'Caddy Web Server' -Direction Inbound C:\\Users\\YOUR_USER\\.local\\bin\\caddy.exe -Action Allow} + ``` +2. Install [`serviceman`](../serviceman/) + ```sh + webi.bat serviceman + ``` +3. Create a **Startup Registry Entry** with Serviceman. + ```sh + serviceman.exe add --name caddy -- \ + caddy run --config ./Caddyfile --envfile ~/.config/caddy/env + ``` +4. You can manage the service directly with Serviceman. For example: + ```sh + serviceman stop caddy + serviceman start caddy + ``` + +This will run caddy as a _Startup Item_. To run as a true system service see +. + +### How to run Caddy as a Linux service + +This will create a **System Service** using `Caddyfile`. \ +See the notes below to run as a **User Service** or use the JSON Config. + +1. If you haven't already, create **a non-root user**. You can use `ssh-adduser` + for this: + ```sh + curl -fsS https://webi.sh/ssh-adduser | sh + ``` + (this will follow the common industry convention of naming the user `app`) +2. Give `caddy` **port-binding privileges**. You can use + [`setcap-netbind`](../setcap-netbind/) for this: + + ```sh + webi setcap-netbind + setcap-netbind caddy + ``` + + (or you can use `setcap` directly) + + ```sh + my_caddy_path="$( command -v caddy )" + my_caddy_absolute="$( readlink -f "${my_caddy_path}" )" + + sudo setcap cap_net_bind_service=+ep "${my_caddy_absolute}" + ``` + +3. Install [`serviceman`](../serviceman/) to template a **systemd service unit** + ```sh + webi serviceman + ``` +4. Use Serviceman to create a _systemd_ config file. + ```sh + my_username="$( id -u -n )" + sudo env PATH="$PATH" \ + serviceman add --system --username "${my_username}" --name caddy -- \ + caddy run --config ./Caddyfile --envfile ~/.config/caddy/env + ``` + (this will create `/etc/systemd/system/caddy.service`) +5. Manage the service with `systemctl` and `journalctl`: + ```sh + sudo systemctl restart caddy + sudo journalctl -xefu caddy + ``` + +To create a **User Service** instead: + +- don't use `sudo`, but do use `--user` when running `serviceman`: + ```sh + serviceman add --user --name caddy -- \ + caddy run --config ./Caddyfile --envfile ~/.config/caddy/env + ``` + (this will create `~/.config/systemd/user/`) +- user the `--user` flag to manage services and logs: + ```sh + systemctl --user enable caddy + systemctl --user restart caddy + journalctl --user -xef -u caddy + ``` + +To use the **JSON Config**: + +- use `--resume` rather than `--config ./Caddyfile` + ```sh + caddy run --resume --envfile ~/.config/caddy/env + ``` + +## Caddy for DevOps + +```sh +touch ./config.env + +caddy run --resume --envfile ./caddy.env +# (resumes from ~/.config/caddy/autosave.json) +``` + +- `--resume` overrides `--config` +- the save file is hard coded to `~/.config/caddy/autosave.json` +- only a single API-enabled instance can resumed at a time \ + (the workaround is to not use resume, but replace the config file and restart) + +To create and load the initial JSON Config, see the _**Caddyfile to JSON**_ +section below. + +### Where to learn about the JSON config + +The best way to learn is to create a `Caddyfile` and + +- run `caddy adapt ./Caddyfile` +- or see `~/.config/caddy/autosave.json` after _any_ `caddy run` + +Then it's also helpful to read the general overview: + +- +- + +The key things you'll need to learn: + +- which modules can be nested within others (`handle`, `routes`) +- which keys are arbitrary (`srv0`) and which are pre-defined (`group`, `match`) +- which structures are core to caddy vs which are specific to a module +- which structures you can **eliminate or deneste** (`Caddyfile` conversion is + messy) + +### How to fmt & lint Caddyfiles + +Both `caddy fmt` and `caddy adapt` can be used to lint. + +```sh +caddy fmt --overwrite ./Caddyfile +``` + +```sh +caddy adapt --config ./Caddyfile +``` + +### How to convert Caddyfile to JSON + +Shown with [`jq`](../jq/) ([`yq`](../yq/) also works well) because it makes the +output readable. + +```sh +caddy adapt --config ./Caddyfile | + jq > ./caddy.json +``` + +You can then load the JSON Config to a live server: + +```sh +my_config="./caddy.json" + +curl -X POST "http://localhost:2019/load" \ + -H "Content-Type: application/json" \ + -d @"${my_config}" +``` + +This will immediately overwrite `~/.config/caddy/autosave.json`. + +### Code Editor support for Caddy's JSON API + +VS Code and Vim / NeoVim are supported. + +See . + +### How to Backup the JSON config + +```sh +my_date="$( date '+%F_%H.%M.%S' )" + +curl "http://localhost:2019/config" -o ./caddy."${my_date}".json +``` + +Or copy from `~/.config/caddy/autosave.json` + +**Warning**: `~/.config/caddy/autosave.json` is _overwritten_ each time `caddy` +is run with a Caddyfile! + +### How to Restore via the API + +This will effectively gracefully restart caddy. + +```sh +my_config="./caddy.json" + +curl -X POST "http://localhost:2019/load" \ + -H "Content-Type: application/json" \ + -d @"${my_config}" +``` + +### How to Update via the API + +It will probably be best (and simplest) to write a new config file +programmatically and then upload it whole. + +Currently, there is no API to provide idempotent updates ("upsert" or "set"), +and many changes that are logically a single unit (such as adding a new site), +require updates among a few different structures, such as: + +- `apps.https.servers["srv0"].routes[]` +- `apps.tls.automation.policies[].subjects` +- `apps.tls.certificates.automate[]` + +However, very, very large config files may benefit from the extra work required +to do smaller updates rather than reload the whole config. + +Here are some important notes: + +- `PATCH` will **replace**, not modify / merge as you would traditionally expect +- `PUT` will **NOT** replace, but rather _insert_ into a position +- A literal `...` in a path, such as `POST /config/my-config/...` will _append_ +- `@id` may exist as a special key on _any_ object, but must _globally_ unique +- `GET /id/my_object` directly accesses the object with `"@id": "my_object"` + +See also: + +- +- + +### How to use ENVs + +Caddy's `--envfile ./caddy.env` parser supports dotenvs in this format: + +`caddy.env`: + +```text +FOO="one" +BAR='two' +BAZ=three +``` + +They are accessed like `{env.FOO}` whether in `Caddyfile` or `caddy.json`: ```Caddyfile example.com { - # ... - - # reverse proxy /api/new/ to http://localhost:3100/api/ - handle_path /api/new/* { - rewrite * /api{path} - reverse_proxy localhost:3100 + file_server * { + root {env.WWW_ROOT} } } ``` -### How to run caddy +```json +{ + "apps": { + "http": { + "servers": { + "my-srv0": { + "listen": [":443"], + "routes": [ + { + "match": [{ "host": ["example.com"] }], + "handle": [ + { + "handler": "file_server", + "root": "{env.WWW_ROOT}" + } + ], + "terminal": true + } + ] + } + } + } + } +} +``` + +Conventionally, the dotenv file should be placed in one of the following +locations: + +- `~/.config/caddy/env` +- `/caddy.env` +- `/.env` + +It does _NOT_ follow the [dotenv][dotenv-rb] [spec][posix-vars], in particular: + +- does not support `export ` prefix +- does not interpolate variables in double-quoted `"` strings + +Consider [dotenv](../dotenv) for better compatibility. + +See also: + +- +- + +[dotenv-rb]: https://github.com/bkeepers/dotenv +[posix-vars]: + https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_03 + +### How to add HTTP Basic Authorization + +1. Digest a password with random salt + ```sh + cat ./password.txt | + caddy hash-password + ``` + ```text + $2a$14$QYYeOtsv0RJixoNZ5frOwuPDiUWl8QBkeMEUBbmnkOHuErlVklzTm + ``` +2. Put the digest into an env file with **single quotes** (to escape the `$`s) \ + `caddy.env`: + ```sh + BASIC_AUTH_USERNAME=my-username + BASIC_AUTH_DIGEST='$2a$14$QYYeOtsv0RJixoNZ5frOwuPDiUWl8QBkeMEUBbmnkOHuErlVklzTm' + ``` +3. Reference `{env.BASIC_AUTH_DIGEST}` in the `Caddyfile` or `caddy.json` + ```Caddyfile + example.com { + handle /* { + basicauth { + {env.BASIC_AUTH_USERNAME} {env.BASIC_AUTH_DIGEST} + } + root * /home/app/srv/example.com/public/ + file_server + } + } + ``` + +### How to Prevent Dev Sites from Hijacking Prod + +Not `caddy` specific, but... + +**By default**, dev sites on dev domains will **hijack the SEO** and **damage +the reputation** of your production domains. + +Allowing non-production sites to be indexed may even cause browsers to issue +**Suspicious Site Blocking** on your primary domain. + +To prevent search engine and browser confusion + +- delist your _demo_, _staging_, _beta_, & _development_ from indexing +- promote your primary domain as canonical +- _DO NOT_ prevent crawling via `robots.txt` \ + (counter-intuitive, but pages _must_ be crawled for links to _NOT_ be indexed) +- _all_ domains using public TLS certs _will_ be indexed by default \ + (they are all linked to and crawled from various Certificate Transparency reports) +- follow these guidelines even if the dev sites use HTTP Basic Auth + +```Caddyfile +dev.example.com { + header { + Link "; rel=\"canonical\"" + X-Robots-Tag noindex + } + + # ... +} +``` + +See also: + +- +- +- +- + +### How to DNS Providers for Wildcard Certs + +You will need to use [xcaddy](../xcaddy) to **build `caddy` with DNS** module +support. + +DNS Providers come in two flavors: + +1. `libdns` instances (newer, fewer providers) + - see + - search `dns.providers` +2. `lego` singletons (deprecated) + - + - + +You can only have **ONE** `lego` instance per process, whereas `libdns` can +support multiple providers across multiple domains. + +### How to use libdns providers + +Look for your DNS provider in the official lists: + +- +- + +For this example we'll use _DuckDNS_ (). + +1. Put the credentials in your dotenv (the name is arbitrary): \ + `caddy.env`: + ```sh + MY_DUCKDNS_TOKEN=xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx + ``` +2. Add the `tls` directive in the format of + `dns [documented params]`: + + ```Caddyfile + example.duckdns.org { + tls { + dns duckdns {env.MY_DUCKDNS_TOKEN} + } + + # ... + } + + *.example.duckdns.org { + tls { + dns duckdns {env.MY_DUCKDNS_TOKEN} + } + + # ... + } + ``` + +When using the **JSON config** the `token` key is instead named `api_token`! + +You can see this by running `caddy adapt ./Caddyfile` on the example above. + +### How to use lego providers + +If you can't find your DNS provider in the `libdns` list, check to see if it's +available in the [`lego` list][lego-providers]: + +- + +For this example we'll use _DNSimple_ +(). + +1. Put the credentials in your dotenv (which MUST match the docs): \ + `caddy.env`: + ```sh + DNSIMPLE_OAUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ``` +2. Add the `tls` directive in the format of `dns lego_deprecated `: + + ```Caddyfile + example.com { + tls { + dns lego_deprecated dnsimple + } + + # ... + } + + *.example.com { + tls { + dns lego_deprecated dnsimple + } + + # ... + } + ``` + +[lego-providers]: https://github.com/go-acme/lego#dns-providers + +### How to run caddy on HTTP only (no TLS) + +You **_must_** use the `http://` prefix AND specify a port number: + +```Caddyfile +http://localhost:3080 { + #... +} +``` + +### How to run caddy on non-standard ports + +```Caddyfile +http://example.com:3080, https://example.com:3443 { + #... +} +``` + +You cannot get TLS certificates (HTTPS) on non-standard ports unless: + +- you use a DNS Provider (see the _Private IP_ / _Wildcard_ section) +- or you have some sort of special proxy in place + +### How to allow caddy to bind on 80 & 443 + +On macOS all programs are allowed to use privileged ports by default. + +On Linux there are several ways to add network _capabilities_ for privileged +ports: + +1. Use `setcap-netbind` + ```sh + webi setcap-netbind + setcap-netbind caddy + ``` +2. Use `setcap` directly + + ```sh + my_caddy_path="$( command -v caddy )" + my_caddy_absolute="$( readlink -f "${my_caddy_path}" )" + + sudo setcap cap_net_bind_service=+ep "${my_caddy_absolute}" + ``` + +3. Use `setcap` through systemd \ + (see systemd instructions below) + +4. Run as `root` (such as on single-user containers) +5. Run as `app`, but port-forward through the container \ + (you figure it out) + +`setcap-netbind` **must** be run each time caddy is updated. + +### How to run with systemd + +See also: + +`systemd` is the `init` system used on most VPS-friendly Linuxes. + +1. Install `serviceman` to create the `systemd` config + ```sh + webi serviceman + ``` +2. Generate the `service` file: \ + - JSON Config + ```sh + my_app_user="$( id -u -n )" + sudo env PATH="${PATH}" \ + serviceman add --system --cap-net-bind \ + --username "${my_app_user}" --name caddy -- \ + caddy run --resume --envfile ./caddy.env + ``` + - Caddyfile + ```sh + my_app_user="$( id -u -n )" + sudo env PATH="${PATH}" \ + serviceman add --system --cap-net-bind \ + --username "${my_app_user}" --name caddy -- \ + caddy run --config ./Caddyfile --envfile ./caddy.env + ``` +3. Reload `systemd` config files, the logging service (it may not be started on + a new VPS), and caddy + ```sh + sudo systemctl daemon-reload + sudo systemctl restart systemd-journald + sudo systemctl restart caddy + ``` + +If you prefer to create the `service` file manually, it should look something +like this: + +`/etc/systemd/system/caddy.service`: + +```ini +# Generated for serviceman. Edit as you wish, but leave this line. +# Pre-req +# sudo mkdir -p ~/srv/ /var/log/caddy/ +# sudo chown -R app:app /var/log/caddy +# Post-install +# sudo journalctl -xefu caddy + +[Unit] +Description=caddy +After=network-online.target +Wants=network-online.target systemd-networkd-wait-online.service + +[Service] +# Restart on crash (bad signal), but not on 'clean' failure (error exit code) +# Allow up to 3 restarts within 10 seconds +# (it's unlikely that a user or properly-running script will do this) +Restart=always +StartLimitInterval=10 +StartLimitBurst=3 + +# User and group the process will run as +User=app +Group=app + +WorkingDirectory=/home/app/srv/ +ExecStart=/home/app/.local/bin/caddy run --resume --envfile /home/app/srv/caddy.env +TimeoutStopSec=5s +LimitNOFILE=1048576 +LimitNPROC=512 +PrivateTmp=true +ProtectSystem=full + +# These directives allow the service to gain root-like networking privileges. +# Note that you may have to add capabilities required by any plugins in use. +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +NoNewPrivileges=true + +# Caveat: Some features may need additional capabilities. +# For example an "upload" may need CAP_LEASE +; CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_LEASE +; AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_LEASE +; NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target +``` + +See also: + +- +- + +### How to run with openrc + +See also: + +`openrc` is the `init` system on Alpine and other Docker and +_container-friendly_ Linuxes. + +`/etc/init.d/caddy`: ```sh -caddy run --config ./Caddyfile -``` - -Note: `run` runs in the foreground, `start` starts a service (daemon) in the -background. - -### How to start Caddy as a Linux service - -Here are the 3 things you need to do to start Caddy as a system service: - -**a non-root user** - -If you don't have a non-root user, consider adding the `app` user with -[`ssh-adduser`](https://webinstall.dev/ssh-adduser). - -Using a user named `app` to run your services is common industry convention. - -**port-binding privileges** - -You can use `setcap` to allow Caddy to use privileged ports. - -```sh -sudo setcap cap_net_bind_service=+ep $(readlink -f $(command -v caddy)) -``` - -**systemd config** - -You can use [`serviceman`](https://webinstall.dev/serviceman) to create and -start the appropriate systemd launcher for Linux. - -Install Serviceman with Webi: - -```sh -webi serviceman -``` - -Use Serviceman to create a _systemd_ config file. - -```sh -sudo env PATH="$PATH" \ - serviceman add --system --username $(whoami) --name caddy -- \ - caddy run --config ./Caddyfile -``` - -This will create `/etc/systemd/system/caddy.service`, which can be managed with -`systemctl`. For example: - -```sh -sudo systemctl restart caddy -``` - -### How to start Caddy as a MacOS Service - -**Port-Binding Permission** - -Caddy must run as the `root` user in order to bind to ports 80 and 443. - -**launchd plist** - -You can use [`serviceman`](https://webinstall.dev/serviceman) to create and -start the appropriate service launcher file for MacOS. - -Install Serviceman with Webi: - -```sh -webi serviceman -``` - -Use Serviceman to create a _launchd_ plist file. - -```sh -serviceman add --username $(whoami) --name caddy -- \ - caddy run --config ./Caddyfile -``` - -This will create `~//Library/LaunchAgents/caddy.plist`, which can be managed -with `launchctl`. For example: - -```sh -launchctl unload -w "$HOME/Library/LaunchAgents/caddy.plist" -launchctl load -w "$HOME/Library/LaunchAgents/caddy.plist" -``` - -### How to start Caddy as a Windows Service - -You may need to update the Windows Firewall to allow traffic through to Caddy. -You'll also need to create a Startup entry in the registry, which can be done -with Serviceman. - -**Windows Firewall** - -You can use PowerShell to update the firewall, which looks something like this: - -```pwsh -powershell.exe -WindowStyle Hidden -Command $r = Get-NetFirewallRule -DisplayName 'Caddy Web Server' 2> $null; if ($r) {write-host 'found rule';} else {New-NetFirewallRule -DisplayName 'Go Web Server' -Direction Inbound C:\\Users\\YOUR_USER\\.local\\bin\\caddy.exe -Action Allow} -``` - -**Startup Registry** - -You can use [Serviceman](https://webinstall.dev/serviceman) to create and start -the appropriate service launcher for Windows. - -Install Serviceman with Webi: - -```sh -webi.bat serviceman -``` - -Use Serviceman to create a Startup entry in the Windows Registry: - -```sh -serviceman.exe add --name caddy -- \ - caddy run --config ./Caddyfile -``` - -You can manage the service directly with Serviceman. For example: - -```sh -serviceman stop caddy -serviceman start caddy +#!/sbin/openrc-run +supervisor=supervise-daemon + +name="Caddy web server" +description="Fast, multi-platform web server with automatic HTTPS" +description_checkconfig="Check configuration" +description_reload="Reload configuration without downtime" + +# for JSON Config +: ${caddy_opts:="--envfile /root/.config/caddy/env --resume"} + +# for Caddyfile +#: ${caddy_opts:="--envfile /root/.config/caddy/env --config /root/srv/caddy/Caddyfile"} + +command=/root/bin/caddy +command_args="run $caddy_opts" +command_user=root:root +extra_commands="checkconfig" +extra_started_commands="reload" +output_log=/var/log/caddy.log +error_log=/var/log/caddy.err + +depend() { + need net localmount + after firewall +} + +checkconfig() { + ebegin "Checking configuration for $name" + su ${command_user%:*} -s /bin/sh -c "$command validate $caddy_opts" + eend $? +} + +reload() { + ebegin "Reloading $name" + su ${command_user%:*} -s /bin/sh -c "$command reload $caddy_opts" + eend $? +} + +stop_pre() { + if [ "$RC_CMD" = restart ]; then + checkconfig || return $? + fi +} ```