--- title: Caddy homepage: https://github.com/caddyserver/caddy tagline: | Caddy is a fast, multi-platform web server with automatic HTTPS. --- To update or switch versions, run `webi caddy@stable` (or `@v2.7`, `@beta`, etc). ### Files These are the files / directories that are created and/or modified with this install: ```text ~/.config/envman/PATH.env ~/.local/bin/caddy ~/.config/caddy/autosave.json ~/.config/caddy/env ~/.local/share/caddy/certificates/ /Caddyfile ``` ## Cheat Sheet > Caddy makes it easy to use Let's Encrypt to handle HTTPS (TLS/SSL) and to > reverse proxy APIs and WebSockets to other apps - such as those written node, > Go, python, ruby, and PHP. We've split what we find most useful into two categories: - **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, Macros, Snippets - Conditional Logic (`if`) - **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) ## 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 [cel]: https://github.com/google/cel-spec/blob/master/doc/langdef.md#list-of-standard-definitions [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 [matchers]: https://caddyserver.com/docs/caddyfile/matchers#named-matchers [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 localhost { handle /api/* { reverse_proxy localhost:3000 } handle /* { root * ./public/ file_server { # ... } } } ``` ```sh caddy run --config ./Caddyfile ``` See also: - [`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 CORS comes in 3 basic varieties: - Simple Requests - Preflight Requests - Credentialed Requests \ (by `Origin` and/or `Authentication`) #### "Simple Requests" [Simple Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) are those that match: - `GET`, `HEAD`, or `POST` - `Accept`, `Range` and traditional `Content-Type`s, which are: \ - `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain` Typical use cases include - Static Files - Public Assets - Contact Request Forms ```Caddyfile # CORS "Simple Request" # (for Static Files & Form Posts) (cors-simple) { @match-cors-request-simple { not header Origin "{http.request.scheme}://{http.request.host}" header Origin "{http.request.header.origin}" method GET HEAD POST } handle @match-cors-request-simple { header { Access-Control-Allow-Origin "*" Access-Control-Expose-Headers * defer } } } example.com { # ex: POST to unauthenticated forms handle /api/public/* { import cors-simple reverse_proxy localhost:3000 } # ex: GET, HEAD static assets handle /* { import cors-simple file_server { /srv/public/ } } } ``` #### API Requests Typical use cases for this are: - Fully Public APIs - APIs Authenticated by Token or username - `Authentication: Basic ` - `Authentication: Bearer ` - `POST` forms with non-traditional `Content-Types`using - `application/json` - `application/graphql+json` - etc Important Notes: - `*` wildcards may NOT be used for authenticated API requests - `Access-Control-Expose-Headers` exposes to _JavaScript_, not just the browser ```Caddyfile # CORS Preflight (OPTIONS) + Request (GET, POST, PATCH, PUT, DELETE) (cors-api) { @match-cors-api-preflight { not header Origin "{http.request.scheme}://{http.request.host}" header Origin "{http.request.header.origin}" method OPTIONS } handle @match-cors-api-preflight { header { Access-Control-Allow-Origin "{http.request.header.origin}" 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 } @match-cors-api-request { not header Origin "{http.request.scheme}://{http.request.host}" header Origin "{http.request.header.origin}" not method OPTIONS } handle @match-cors-api-request { header { Access-Control-Allow-Origin "{http.request.header.origin}" Access-Control-Allow-Credentials "true" Access-Control-Max-Age "3600" defer } } } api.example.com { handle /api/* { import cors-api reverse_proxy localhost:3000 } # ... } ``` #### Restricted by Origin Typical use cases for this are: - Allow access to partners or sister domains Important Notes: - `*` wildcards can be used for unauthenticated requests ```Caddyfile (cors-origin) { @match-cors-preflight-{args.0} { header Origin "{args.0}" method OPTIONS } handle @match-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 } @match-cors-request-{args.0} { header Origin "{args.0}" not method OPTIONS } handle @match-cors-request-{args.0} { header { Access-Control-Allow-Origin "{http.request.header.origin}" Access-Control-Expose-Headers * defer } } } partners.example.com { import cors-origin https://member.example.com import cors-origin https://whatever.com file_server { root /srv/public/ } } ``` 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 macro 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) │ │ ├── hostport # {hostport} │ │ ├── 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]: ### How to Conditional ENVs There is no `if` in Caddy, but a _matcher_ with "CEL" does the same thing. Ex: I only want to enforce HTTP Basic Auth if it's enabled: ```Caddyfile localhost { @match-enforce-auth `"{$HTTP_BASIC_AUTH_ENABLED}".size() > 0` basicauth @match-enforce-auth { {$HTTP_BASIC_AUTH_USERNAME} {$HTTP_BASIC_AUTH_PASSWORD_DIGEST} } # ... } ``` You can do slightly more complex expressions on the variety of variables (_placeholders_), but you'd have to look up the [CEL docs](). However, you can only do these expressions in things that have a _matcher_. See also: - [`matchers`](matchers): - [CEL][cel]: ### Putting it All Together Here's what a fairly basic, but comprehensive and complete `Caddyfile` looks like: `Caddyfile`: ```Caddyfile # redirect www to bare domain www.example.com { redir https://example.com{uri} permanent } example.com { ########### # Logging # ########### # log to stdout, which is captured by journalctl log { output stdout format console } ############### # Compression # ############### # turn on standard streaming compression encode gzip zstd #################### # Reverse Proxying # #################### # reverse proxy /api to :3000 handle /api/* { reverse_proxy localhost:3000 } # reverse proxy some "well known" APIs handle /.well-known/openid-configuration { reverse_proxy localhost:3000 } handle /.well-known/jwks.json { reverse_proxy localhost:3000 } ################## # Path Rewriting # ################## # reverse proxy and rewrite path /api/oldpath/* => /api/newpath/* handle_path /api/oldpath/* { rewrite * /api/newpath{path} reverse_proxy localhost:3000 } ############### # File Server # ############### # serve static files handle /* { root * /srv/example.com/public/ 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 sudo serviceman add --system --name caddy -- \ caddy run --config ./Caddyfile --envfile ~/.config/caddy/env ``` ### How to run Caddy as a Windows Service 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 $HOME\\.local\\bin\\caddy.exe -Action Allow} ``` 2. Install [`serviceman`](../serviceman/) ```sh webi 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 { file_server * { root {env.WWW_ROOT} } } ``` ```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 #!/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 } ```