---
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/
<PROJECT-DIR>/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]: <https://caddy.community/c/wiki/13>

[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]:
  <https://caddyserver.com/docs/command-line#caddy-file-server>
- [`handle`][handle]: <https://caddyserver.com/docs/caddyfile/directives/handle>
- [`root`][root]: <https://caddyserver.com/docs/caddyfile/directives/root>
- [`file_server`][file_server]:
  <https://caddyserver.com/docs/caddyfile/directives/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]: <https://caddyserver.com/docs/caddyfile/directives/handle>
- [`root`][root]: <https://caddyserver.com/docs/caddyfile/directives/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]: <https://caddyserver.com/docs/caddyfile/directives/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]: <https://caddyserver.com/docs/caddyfile/directives/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]: <https://caddyserver.com/docs/caddyfile/directives/encode>
- [`root`][root]: <https://caddyserver.com/docs/caddyfile/directives/root>
- caniuse | zstd: <https://caniuse.com/?search=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]:
  <https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#headers>
- [`trusted_proxies`][trusted_proxies]:
  <https://caddyserver.com/docs/caddyfile/options#trusted-proxies>
- <https://github.com/caddyserver/caddy/pull/4021>
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For>
- <https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/>
- <https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/>
- <https://tn123.org/mod_xsendfile/>

### 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 <base64(api:token)>`
  - `Authentication: Bearer <token>`
- `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]: <https://caddyserver.com/docs/caddyfile/directives/import>
- <https://httptoolkit.com/will-it-cors/source-url>
- <https://gist.github.com/ryanburnette/d13575c9ced201e73f8169d3a793c1a3>
- <https://kalnytskyi.com/posts/setup-cors-caddy-2/>
- <https://caddyserver.com/docs/caddyfile/directives/import#examples>
- <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests>
- <https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request>

<!--
v1 only: https://enable-cors.org/server_caddy.html
-->

### 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 <provider> [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]: <https://caddyserver.com/docs/caddyfile/directives/tls>
- <https://github.com/caddy-dns>
- <https://caddyserver.com/docs/modules/> (search `dns.providers`)
- <https://github.com/go-acme/lego#dns-providers>
- <https://caddyserver.com/docs/modules/dns.providers.lego_deprecated>

### 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]:
  <https://caddyserver.com/docs/caddyfile/concepts#structure>
- [Placeholders][placeholders]:
  <https://caddyserver.com/docs/caddyfile/concepts#placeholders>
- [Snippets][snippets]:
  <https://caddyserver.com/docs/caddyfile/concepts#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]:
  <https://caddyserver.com/docs/caddyfile/concepts#placeholders>
- [Conventions: Placeholders][placeholders2]:
  <https://caddyserver.com/docs/conventions#placeholders>
- [`http`][http]: <https://caddyserver.com/docs/json/apps/http/#docs>
- [`file`][file]:
  <https://caddyserver.com/docs/json/apps/http/servers/routes/match/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):
  <https://caddyserver.com/docs/caddyfile/matchers#named-matchers>
- [CEL][cel]:
  <https://github.com/google/cel-spec/blob/master/doc/langdef.md#list-of-standard-definitions>

### 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 --agent --name 'caddy' --workdir ./ -- \
       caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile
   ```

   (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
serviceman add --name 'caddy' --workdir ./ --daemon -- \
   caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile
```

### 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 --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile
   ```
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
<https://caddyserver.com/docs/running#windows-service>.

### 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
   serviceman add --name 'caddy' --daemon -- \
       caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile
   ```
   (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:

- use `--agent` when running `serviceman`:
  ```sh
  serviceman add --agent --name caddy -- \
      caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile
  ```
  (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:

- <https://caddy.community/t/writing-a-caddy-json-config-from-scratch/7524>
- <https://caddyserver.com/docs/json/>

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 <https://github.com/abiosoft/caddy-json-schema>.

### 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:

- <https://caddyserver.com/docs/api>
- <https://caddyserver.com/docs/api-tutorial>

### 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`
- `<PROJECT-DIR>/caddy.env`
- `<PROJECT-DIR>/.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:

- <https://github.com/bkeepers/dotenv>
- <https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_03>

[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 "<https://production.example.com{http.request.orig_uri}>; rel=\"canonical\""
        X-Robots-Tag noindex
    }

    # ...
}
```

See also:

- <https://developers.google.com/search/docs/advanced/robots/intro>
- <https://developers.google.com/search/docs/advanced/crawling/block-indexing>
- <https://certificate.transparency.dev/>
- <https://crt.sh>

### 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 <https://github.com/caddy-dns>
   - search `dns.providers` <https://caddyserver.com/docs/modules/>
2. `lego` singletons (deprecated)
   - <https://github.com/go-acme/lego#dns-providers>
   - <https://caddyserver.com/docs/modules/dns.providers.lego_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:

- <https://github.com/caddy-dns>
- <https://caddyserver.com/docs/modules/>

For this example we'll use _DuckDNS_ (<https://github.com/caddy-dns/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 <provider> [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]:

- <https://github.com/go-acme/lego#dns-providers>

For this example we'll use _DNSimple_
(<https://go-acme.github.io/lego/dns/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 <provider>`:

   ```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: <https://caddyserver.com/docs/running>

`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
     serviceman add --name 'caddy' --daemon -- \
         caddy run --resume --envfile ./caddy.env
     ```
   - Caddyfile
     ```sh
     serviceman add --name 'caddy' --daemon -- \
         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:

- <https://github.com/caddyserver/dist/blob/master/init/caddy-api.service>
- <https://github.com/caddyserver/dist/blob/master/init/caddy.service>

### How to run with openrc

See also: <https://caddyserver.com/docs/running>

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