Caddy is a fast, multi-platform web server with automatic HTTPS.
https://github.com/caddyserver/caddy| Installer Source| Releases (json) (tab)
Caddy is a fast, multi-platform web server with automatic HTTPS.
https://github.com/caddyserver/caddy| Installer Source| Releases (json) (tab)
To update or switch versions, run webi caddy@stable (or @v2.7, @beta, etc).
These are the files / directories that are created and/or modified with this install:
~/.config/envman/PATH.env ~/.local/bin/caddy ~/.config/caddy/autosave.json ~/.config/caddy/env ~/.local/share/caddy/certificates/ <PROJECT-DIR>/Caddyfile 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:
if)launchd & launchctl)systemd & systemctl)libdns DNS Providerslego DNS Providerssystemd (VM, VPS)openrc (Container, Docker)mkdir -p ~/.config/caddy/ touch ~/.config/caddy/env caddy run --config ./Caddyfile --envfile ~/.config/caddy/env run runs in the foregroundstart starts a background service (daemon)Warning: ~/.config/caddy/autosave.json is overwritten each time caddy is run with a Caddyfile!
See also:
Using the convenience file-server command:
caddy file-server --browse --listen :4040 Using Caddyfile:
localhost { # ... handle /* { root * ./public/ file_server { browse } } } browse enables the built-in directory explorerSee also:
handle: https://caddyserver.com/docs/caddyfile/directives/handleroot: https://caddyserver.com/docs/caddyfile/directives/rootfile_server: https://caddyserver.com/docs/caddyfile/directives/file_serverCaddy 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:
localhost { handle /api/* { reverse_proxy localhost:3000 } handle /* { root * ./public/ file_server { # ... } } } caddy run --config ./Caddyfile See also:
handle: https://caddyserver.com/docs/caddyfile/directives/handleroot: https://caddyserver.com/docs/caddyfile/directives/rootfile_server:HTTPS redirects are automatic.
www redirects can be done like this:
# 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:
example.com { # log to stdout, which is captured by journalctl, etc log { output stdout format console } # ... } See also:
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 availableSee also:
encode: https://caddyserver.com/docs/caddyfile/directives/encoderoot: https://caddyserver.com/docs/caddyfile/directives/rootX-Forwarded-* are set by default:X-Forwarded-For (XFR) is the Request IPX-Forwarded-Proto (XFP) is set to http for plaintext or https for TLSX-Forwarded-Host (XFH) is the original Host header from the clienttrusted_proxies can be set to allow header pass thru from another proxyprivate_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 ::1X-Accel-Redirect can be set to allow static file passthru serving (also known as X-SendFile or X-LIGHTTPD-send-file){ 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: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#headerstrusted_proxies: https://caddyserver.com/docs/caddyfile/options#trusted-proxiesRather than reverse_proxy, this could just as well be handled by file_server.
handle_path eats the path, whereas handle matches without consuming.
example.com { # ... # {host}/api/oldpath/* => http://localhost:3000/api/newpath/* handle_path /api/oldpath/* { rewrite * /api/newpath{path} reverse_proxy localhost:3000 } } CORS comes in 3 basic varieties:
Origin and/or Authentication)Simple Requests are those that match:
GET, HEAD, or POSTAccept, Range and traditional Content-Types, which are: \application/x-www-form-urlencoded, multipart/form-data, text/plainTypical use cases include
# 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/ } } } Typical use cases for this are:
Authentication: Basic <base64(api:token)>Authentication: Bearer <token>POST forms with non-traditional Content-Typesusingapplication/jsonapplication/graphql+jsonImportant Notes:
* wildcards may NOT be used for authenticated API requestsAccess-Control-Expose-Headers exposes to JavaScript, not just the browser# 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 } # ... } Typical use cases for this are:
Important Notes:
* wildcards can be used for unauthenticated requests(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: https://caddyserver.com/docs/caddyfile/directives/importDNS Providers are required for
*.example.com)192.168.x.x)3000, 8443)Example with DuckDNS:
Put the credentials in your dotenv (the name is arbitrary): caddy.env:
MY_DUCKDNS_TOKEN=xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx Add the tls directive in the format of dns <provider> [documented params]:
# 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: https://caddyserver.com/docs/caddyfile/directives/tlsdns.providers)"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:
# 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:
# (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:
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 documentationSee also:
http: https://caddyserver.com/docs/json/apps/http/#docsfile: https://caddyserver.com/docs/json/apps/http/servers/routes/match/file/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:
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: https://caddyserver.com/docs/caddyfile/matchers#named-matchersHere's what a fairly basic, but comprehensive and complete Caddyfile looks like:
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 } } } To avoid the nitty-gritty details of launchd plist files, you can use serviceman to template out the plist file for you:
Install serviceman
webi serviceman Use Serviceman to create a launchd plist file
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)
Manage the service with launchctl
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:
serviceman add --name 'caddy' --workdir ./ --daemon -- \ caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile YOUR_USER in the script below and running it in cmd.exe as Administrator: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} servicemanwebi serviceman serviceman.exe add --name caddy -- \ caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile 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.
This will create a System Service using Caddyfile.
See the notes below to run as a User Service or use the JSON Config.
If you haven't already, create a non-root user. You can use ssh-adduser for this:
curl -fsS https://webi.sh/ssh-adduser | sh (this will follow the common industry convention of naming the user app)
Give caddy port-binding privileges. You can use setcap-netbind for this:
webi setcap-netbind setcap-netbind caddy (or you can use setcap directly)
my_caddy_path="$( command -v caddy )" my_caddy_absolute="$( readlink -f "${my_caddy_path}" )" sudo setcap cap_net_bind_service=+ep "${my_caddy_absolute}" Install serviceman to template a systemd service unit
webi serviceman Use Serviceman to create a systemd config file.
serviceman add --name 'caddy' --daemon -- \ caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile (this will create /etc/systemd/system/caddy.service)
Manage the service with systemctl and journalctl:
sudo systemctl restart caddy sudo journalctl -xefu caddy To create a User Service instead:
--agent when running serviceman:serviceman add --agent --name caddy -- \ caddy run --envfile ~/.config/caddy/env --config ./Caddyfile --adapter caddyfile (this will create ~/.config/systemd/user/)--user flag to manage services and logs:systemctl --user enable caddy systemctl --user restart caddy journalctl --user -xef -u caddy To use the JSON Config:
--resume rather than --config ./Caddyfilecaddy run --resume --envfile ~/.config/caddy/env touch ./config.env caddy run --resume --envfile ./caddy.env # (resumes from ~/.config/caddy/autosave.json) --resume overrides --config~/.config/caddy/autosave.jsonTo create and load the initial JSON Config, see the Caddyfile to JSON section below.
The best way to learn is to create a Caddyfile and
caddy adapt ./Caddyfile~/.config/caddy/autosave.json after any caddy runThen it's also helpful to read the general overview:
The key things you'll need to learn:
handle, routes)srv0) and which are pre-defined (group, match)Caddyfile conversion is messy)Both caddy fmt and caddy adapt can be used to lint.
caddy fmt --overwrite ./Caddyfile caddy adapt --config ./Caddyfile Shown with jq (yq also works well) because it makes the output readable.
caddy adapt --config ./Caddyfile | jq > ./caddy.json You can then load the JSON Config to a live server:
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.
VS Code and Vim / NeoVim are supported.
See https://github.com/abiosoft/caddy-json-schema.
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!
This will effectively gracefully restart caddy.
my_config="./caddy.json" curl -X POST "http://localhost:2019/load" \ -H "Content-Type: application/json" \ -d @"${my_config}" 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[].subjectsapps.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 expectPUT will NOT replace, but rather insert into a position... in a path, such as POST /config/my-config/... will append@id may exist as a special key on any object, but must globally uniqueGET /id/my_object directly accesses the object with "@id": "my_object"See also:
Caddy's --envfile ./caddy.env parser supports dotenvs in this format:
caddy.env:
FOO="one" BAR='two' BAZ=three They are accessed like {env.FOO} whether in Caddyfile or caddy.json:
example.com { file_server * { root {env.WWW_ROOT} } } { "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>/.envIt does NOT follow the dotenv spec, in particular:
export prefix" stringsConsider dotenv for better compatibility.
See also:
cat ./password.txt | caddy hash-password $2a$14$QYYeOtsv0RJixoNZ5frOwuPDiUWl8QBkeMEUBbmnkOHuErlVklzTm $s) caddy.env:BASIC_AUTH_USERNAME=my-username BASIC_AUTH_DIGEST='$2a$14$QYYeOtsv0RJixoNZ5frOwuPDiUWl8QBkeMEUBbmnkOHuErlVklzTm' {env.BASIC_AUTH_DIGEST} in the Caddyfile or caddy.jsonexample.com { handle /* { basicauth { {env.BASIC_AUTH_USERNAME} {env.BASIC_AUTH_DIGEST} } root * /home/app/srv/example.com/public/ file_server } } 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
robots.txt dev.example.com { header { Link "<https://production.example.com{http.request.orig_uri}>; rel=\"canonical\"" X-Robots-Tag noindex } # ... } See also:
You will need to use xcaddy to build caddy with DNS module support.
DNS Providers come in two flavors:
libdns instances (newer, fewer providers)dns.providers https://caddyserver.com/docs/modules/lego singletons (deprecated) You can only have ONE lego instance per process, whereas libdns can support multiple providers across multiple domains.
Look for your DNS provider in the official lists:
For this example we'll use DuckDNS (https://github.com/caddy-dns/duckdns).
Put the credentials in your dotenv (the name is arbitrary): caddy.env:
MY_DUCKDNS_TOKEN=xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx Add the tls directive in the format of dns <provider> [documented params]:
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.
If you can't find your DNS provider in the libdns list, check to see if it's available in the lego list:
For this example we'll use DNSimple (https://go-acme.github.io/lego/dns/dnsimple/).
Put the credentials in your dotenv (which MUST match the docs): caddy.env:
DNSIMPLE_OAUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Add the tls directive in the format of dns lego_deprecated <provider>:
example.com { tls { dns lego_deprecated dnsimple } # ... } *.example.com { tls { dns lego_deprecated dnsimple } # ... } You must use the http:// prefix AND specify a port number:
http://localhost:3080 { #... } http://example.com:3080, https://example.com:3443 { #... } You cannot get TLS certificates (HTTPS) on non-standard ports unless:
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:
Use setcap-netbind
webi setcap-netbind setcap-netbind caddy Use setcap directly
my_caddy_path="$( command -v caddy )" my_caddy_absolute="$( readlink -f "${my_caddy_path}" )" sudo setcap cap_net_bind_service=+ep "${my_caddy_absolute}" Use setcap through systemd
(see systemd instructions below)
Run as root (such as on single-user containers)
Run as app, but port-forward through the container
(you figure it out)
setcap-netbind must be run each time caddy is updated.
See also: https://caddyserver.com/docs/running
systemd is the init system used on most VPS-friendly Linuxes.
serviceman to create the systemd configwebi serviceman service file: \serviceman add --name 'caddy' --daemon -- \ caddy run --resume --envfile ./caddy.env serviceman add --name 'caddy' --daemon -- \ caddy run --config ./Caddyfile --envfile ./caddy.env systemd config files, the logging service (it may not be started on a new VPS), and caddysudo 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:
# 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:
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:
#!/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 }