Web Server - Caddy 2

Stop, COVID-19

Caddy 2 is a whole new code base, written from scratch, to improve on Caddy 1. Caddy 2 is not backwards-compatible with Caddy 1. But don’t worry, for most basic setups, not much is different.

Download and Install

Download Caddy 2 from the Caddy Download Page and put it in your PATH. You can get Caddy for nearly any OS and architecture. Caddy’s download page is unique from other web servers: it lets you customize your build with plugins.

A. Debian,Ubuntu,Raspbian

Installing this package automatically starts and runs Caddy for you as a systemd service named caddy using official caddy.service unit file.

Method One

you can install it from apt command.

1
2
3
4
5
sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/gpg/gpg.155B6D79CA56EA34.key' | sudo apt-key add -
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/setup/config.deb.txt?distro=debian&version=any-version' | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list
sudo apt-get update
sudo apt-get install caddy

Method Two

you can download it from Caddy Download, and then put it in PATH(example:/usr/local/bin/).

1
2
3
:~$ curl -o ~/Downloads/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com%2Fmholt%2Fcaddy-webdav&idempotency=79680512439698"
:~$ sudo mv ~/Downloads/caddy /usr/local/bin
:~$ chmod +x /usr/local/bin/caddy

B. Docker

1
docker pull caddy

Run Caddy as a service

Manually install Caddy as a service on Linux with these instructions.

  • Requirements:
  • caddy binary that you downloaded
  • systemctl --version 232 or newer
  • sudo privileges

Move the caddy binary into your $PATH and test that it worked, for example

1
2
3
:~$ sudo mv caddy /usr/bin
:~$ caddy version
v2.3.0 h1:fnrqJLa3G5vfxcxmOH/+kJOcunPLhSBnjgIvjXV/QTA=

Create a group named caddy:

1
:~$ sudo groupadd --system caddy

Create a user named caddy, with a writeable home folder:

1
2
3
4
5
6
7
sudo useradd --system \
--gid caddy \
--create-home \
--home-dir /var/lib/caddy \
--shell /usr/sbin/nologin \
--comment "Caddy web server" \
caddy

Note: If using a config file, be sure it is readable by the caddy user you just created.

Next, create a systemd service file.

Github-Caddy.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
:~$ sudo cat > /lib/systemd/system/caddy.service << EOF
# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# caddy run command or use the caddy-api.service file instead.

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
EOF
:~$ sudo systemctl daemon-reload

finally, use command systemctl start caddy to start caddy.

1
:~$ sudo systemctl start caddy

When running with Caddy official service file, Caddy’s output will be redirected to journalctl:

1
:~$ sudo journalctl -u caddy --no-pager | less

Example of Caddyfile

PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -----------Global options---------------
# Note: This must be the first block of the Caddyfile
{
http_port 80
https_port 443
servers :443 {
protocol {
experimental_http3
}
}
servers :80 {
protocol {
allow_h2c
}
}
}

h5ai.shixuen.com {
root * /var/www/media
tls haven200@haven200.com
file_server
php_fastcgi unix//run/php/php7.4-fpm.sock

# redirect to index.php with all request
@redirected {
not path /_h5ai/*
path */
}
rewrite @redirected /_h5ai/public/index.php
}

php_fastcgi

An opinionated directive that proxies requests to a PHP FastCGI server such as php-fpm.

1
2
3
4
5
6
7
8
# Proxy all PHP requests to a FastCGI responder listening at 127.0.0.1:9000
php_fastcgi 127.0.0.1:9000

# Same, but only for requests under /blog/
php_fastcgi /blog/* 127.0.0.1:9000

# When using php-fpm listening via a unix socket:
php_fastcgi unix//run/php/php7.4-fpm.sock

WebDav

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# -----------Global options---------------
# Note: This must be the first block of the Caddyfile
{
http_port 80
https_port 443
servers :443 {
protocol {
experimental_http3
}
}
servers :80 {
protocol {
allow_h2c
}
}
}
# Needed additional module http.handlers.webdav
webdav.shixuen.com {
root * /var/www/webdav
route {
webdav /* {
root /var/www/webdav
}
file_server
}
basicauth {
test JDJhJDE0JEloaFFCbzBMZG94d1J6ZUVaRWE2ZS5xUkQ2WmM0Y0VlV2lpbmxNRVhESVVvSkZZSTU2TTZL
}
}

The module http.handlers.webdav be needed here.

This modlue only supported read operation.

1
2
3
:~$ caddy list-modules | grep webdav
http.handlers.webdav
:~$

Caddyfile Docs

The Caddyfile is a convenient Caddy configuration format for humans. It is most people’s favorite way to use Caddy because it is easy to write, easy to understand, and expressive enough for most use cases.

Concepts

Caddy-Docs-Concepts

Structure

The Caddyfile’s structure can be described visually:

Caddyfile's structure

Key points:

  • An optional global options block can be the very first thing in the file.
  • Otherwise, the first line of the Caddyfile is always the address(es) of the site to serve.
  • All directives and matchers must go in a site block. There is no global scope or inheritence across site blocks.
  • If there is only one site block, its curly braces { } are optional.

Global Options

Caddy-Docs-Global Options

The Caddyfile has a way for you to specify options that apply globally. Some options act as default values, while others customize the behavior of the Caddyfile adapter.

The very top of your Caddyfile can be a global options block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
http_port 80
https_port 443
servers :443 {
protocol {
experimental_http3
}
}
servers :80 {
protocol {
allow_h2c
}
}
}

There can only be one at most, and it must be the first block of the Caddyfile.

  • http_port: The port for the server to use for HTTP. For internal use only; does not change the HTTP port for clients. Default: 80
  • https_port: The port for the server to use for HTTPS. For internal use only; does not change the HTTPS port for clients. Default: 443
  • server: Customizes HTTP servers with settings that potentially span multiple sites and thus can’t be rightly configured in site blocks.
  • protocol
  • allow_h2c: enables H2C (“Cleartext HTTP/2” or “H2 over TCP”) support, which will serve HTTP/2 over plaintext TCP connections if a client support it. This setting applies only to unencrypted HTTP listeners.
  • experimental_http3: enables experimental draft HTTP/3 support. Note that HTTP/3 is not a finished spec and client support is extremely limited. This option will go away in the future.

Request Matchers

Request matchers can be used to filter (or classify) requests by specific criteria.

In the Caddyfile, a matcher token immediately following the directive can limit that directive’s scope. The matcher token can be one of these forms:

  1. Wildcard matchers: * to match all requests (default).
  2. Path matchers: /path start with a forward slash to match a request path.
  3. Named matchers: @name to specify a named matcher.

Wildcard matchers

The wildcard (or “catch-all”) matcher * matches all requests, and is only needed if a matcher token is required.

1
2
3
4
5
6
# Set the site root to /var/www/mysite for all requests
root * /var/www/mysite


# Change the site root only for requests in /foo/*
root /foo/* /home/user/public_html/foo

Path matchers

Because matching by path is so common, a single path matcher can be inlined, like so:

1
redir /old.html /new.html

Path matcher tokens must start with a forward slash /.

Path matching is an exact match by default; you must append a * for a fast prefix match. Note that /foo* will match /foo and /foo/ as well as /foobar; you might actually want /foo/* instead.

Named matchers

All matchers that are not path or wildcard matchers must be named matchers.
Defining a matcher with a unique name gives you more flexibility, allowing you to combine any available matchers into a set:

1
2
3
4
5
6
7
# multi matchers
@name {
...
}

# Only one matcher
@name ...

For example:

1
2
3
4
5
@websockets {
header Connection *Upgrade*
header Upgrade websocket
}
reverse_proxy @websockets localhost:6001

This proxies only the requests that have a header field named “Connection” containing the word “Upgrade” and another field named “Upgrade” with a value of “websocket”.

Like directives, named matcher definitions must go inside the site blocks that use them.

A named matcher definition constitutes a matcher set. Matchers in a set are AND’ed together; i.e. all must match. For example, if you have both a header and path matcher in the set, both must match.

Standard matchers

  • expression
  • file
  • header
  • header_regexp
  • host
  • method
  • not
  • path
  • path_regexp
  • protocol
  • query
  • remote_ip
1
header <field> [<value>]

By request header fields.

  • <field> is the name of the HTTP header field to check.
  • If prefixed with !, the field must not exist to match (omit value arg).
  • <value> is the value the field must have to match.
  • If prefixed with *, it performs a fast suffix match.
  • If suffixed with *, it performs a fast prefix match.
  • If enclosed by *, it performs a fast substring match.
  • Otherwise, it is a fast exact match.

Different header fields within the same set are AND-ed. Multiple values per field are OR’ed.

Example One

Match requests with the Connection header containing Upgrade and server header is Caddy.

1
2
3
4
5
6
@foo {
header {
Connection *Upgrade*
server Caddy
}
}
Example Two

Match requests with the Foo header containing bar OR baz.

1
2
3
4
@foo {
header Foo bar
header Foo baz
}
Example Three

Match requests that do not have the Foo header field at all

1
2
3
@not_foo {
header !Foo
}

method

1
method <verbs...>

By the method (verb) of the HTTP request. Verbs should be uppercase, like POST. Can match one or many methods.

Multiple method matchers will be OR’ed together.

1
2
3
4
5
# Match requests with the GET method.
method GET

# Match requests with the PUT or DELETE methods.
method PUT DELETE

not

1
2
3
4
5
6
7
# OR
not <any other matcher>

# negate multiple matchers which get AND'ed
not {
<any other matcher>
}
Examples One

Match requests with paths that do NOT begin with /css/ OR /js/.

1
not path /css/* /js/*
Example Two

Match requests WITH NEITHER:

  • an /api/ path prefix, NOR
  • the POST request method
1
2
not path /api/*
not method POST
Example Three

Match requests WITHOUT BOTH

  • an /api/ path prefix, AND
  • the POST request method
1
2
3
4
not {
path /api/*
method POST
}

path

1
path <paths...>

By request path, meaning the path component of the request’s URI. Path matches are exact, but wildcards * may be used:

  • At the end, for a prefix match (/prefix/*)
  • At the beginning, for a suffix match (*.suffix)
  • On both sides, for a substring match (*/contains/*)
  • In the middle, for a globular match (/accounts/*/info)

Multiple path matchers will be OR’ed together.

Directives

The following directives come standard with Caddy, and can be used in the HTTP Caddyfile:

Directive Description
acme_server An embedded ACME server
basicauth Enforces HTTP Basic Authentication
bind Customize the server’s socket address
encode Encodes (usually compresses) responses
file_server Serve files from disk
handle A mutually-exclusive group of directives
handle_errors Defines routes for handling errors
handle_path Like handle, but strips path prefix
header Sets or removes response headers
import Include snippets or files
log Enables access/request logging
map Maps an input value to one or more outputs
metrics Configures the Prometheus metrics exposition endpoint
php_fastcgi Serve PHP sites over FastCGI
push Push content to the client using HTTP/2 server push
redir Issues an HTTP redirect to the client
request_body Manipulates request body
request_header Manipulates request headers
respond Writes a hard-coded response to the client
reverse_proxy A powerful and extensible reverse proxy
rewrite Rewrites the request internally
root Set the path to the site root
route A group of directives treated literally as single unit
templates Execute templates on the response
tls Customize TLS settings
try_files Rewrite that depends on file existence
uri Manipulate the URI

Directive order

Many directives manipulate the HTTP handler chain. The order in which those directives are evaluated matters, so a default ordering is hard-coded into Caddy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
map
root

header
request_body

redir
rewrite

uri
try_files

basicauth
request_header
encode
templates

handle
handle_path
route
push

respond
metrics
reverse_proxy
php_fastcgi
file_server
acme_server

You can override/customize this ordering by using the order global option or the route directive.

log

Enables and configures HTTP request logging (also known as access logs).

The log directive applies to the host/port of the site block it appears in, not any other part of the site address (e.g. path).

1
2
3
4
5
log {
output <writer_module> ...
format <encoder_module> ...
level <level>
}
  • output: configures a where to write the logs to. Default: stderr
  • stderr: Standard error (console, default).
  • stdout: Standard output (console).
  • discard: No output.
  • file: A file. By default, log files are rotated (“rolled”) to prevent disk space exhaustion.
  • net: A network socket.
  • format: describes how to encode, or format, the logs. Default: console if stdout is detected to be a terminal, json otherwise.
  • console: The console encoder formats the log entry for human readability while preserving some structure.
  • json: Formats each log entry as a JSON object.
  • single_field: Writes only a single field from the structure log entry. Useful if one of the fields has all the information you need.
  • filter: Wraps another encoder module, allowing per-field filtering.
  • delete: Marks a field to be skipped from being encoded.
  • ip_mask: Masks IP addresses in the field using a CIDR mask.
  • level: is the minimum entry level to log. Default: INFO

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Use common log format
example.com {
root /var/www/
...
log {
format single_field common_log
}
}

# Delete the Authorization request header from the logs
example.com {
log {
output file /var/log/caddy.log {
roll_size 100mb
roll_keep 5
roll_keep_for 720h
}
format filter {
wrap console
fields {
request>headers>Authorization delete
}
}
}
}

Basicauth

basicauth, Enables HTTP Basic Authentication, which can be used to protect directories and files with a username and hashed password. Note that basic auth is not secure over plain HTTP.

Caddy configuration does not accept plaintext passwords; we MUST hash them before putting them into the configuration. The caddy hash-password command can help with this.

1
2
3
4
:~$ caddy hash-password
Enter password: test
Confirm password: test
JDJhJDE0JEloaFFCbzBMZG94d1J6ZUVaRWE2ZS5xUkQ2WmM0Y0VlV2lpbmxNRVhESVVvSkZZSTU2TTZL

Example:

1
2
3
4
5
6
haven200.com {
...
basicauth /secret/* {
test JDJhJDE0JEloaFFCbzBMZG94d1J6ZUVaRWE2ZS5xUkQ2WmM0Y0VlV2lpbmxNRVhESVVvSkZZSTU2TTZL
}
}

encode

Encodes responses using the configured encoding(s). A typical use for encoding is compression.

1
2
3
4
5
haven200.com {
...
# Enable Zstandard and Gzip compression
encode zstd gzip
}

file_server

A static file server. It works by appending the request’s URI path to the site’s root path. By default, it enforces canonical URIs; if necessary, requests to directories will be redirected to have a trailing forward slash, and requests to files will be redirected to strip the trailing slash.

Most often, the file_server directive is paired with the root directive to set file root for the whole site.

1
2
3
4
5
6
file_server [<matcher>] [browse] {
root <path>
hide <files...>
index <filenames...>
browse [<template_file>]
}
  • browse enables file listings for requests to directories that do not have an index file.
  • root sets the path to the site root for just this file server instance, overriding any other. Default: {http.vars.root} or the current working directory. Note: This subdirective only changes the root for this directive. For other directives (like try_files or templates) to know the same site root, use the root directive, not this subdirective.
  • hide is a list of files or folders to hide; if requested, the file server will pretend they do not exist. Accepts placeholders and glob patterns. Note that these are file system paths, NOT request paths.
  • index is a list of filenames to look for as index files. Default: index.html index.txt
  • <template_file> is an optional custom template file to use for directory listings.

Example One

The file_server directive is usually paired with the root directive to set the root path from which to serve files:

1
2
3
4
haven200.com {
root * /var/www/
file_server
}

Example Two

A static file server out of the /var/www directory With file listings enabled, and hide all .git folders and their contents

1
2
3
4
5
6
haven200.com {
root * /var/www/
file_server browse {
hide .git
}
}

header

Manipulates HTTP header fields on the response. It can set, add, and delete header values, or perform replacements using regular expressions.

By default, header operations are performed immediately unless any of the headers are being deleted, in which case the header operations are automatically deferred until the time they are being written to the client.

1
2
3
4
5
6
7
header [<matcher>] [[+|-|?]<field> [<value>|<find>] [<replace>]] {
<field> <find> <replace>
[+]<field> <value>
-<field>
?<field> <default_value>
[defer]
}
  • <field> is the name of the header field. By default, will overwrite any existing field of the same name. Prefix with + to add the field instead of replace, or prefix with - to remove the field.
  • <value> is the header field value, if adding or setting a field.
  • <default_value> is the header field value that will be set only if the header does not already exist.
  • <find> is the substring or regular expression to search for.
  • <replace> is the replacement value; required if performing a search-and-replace.
  • defer will force the header operations to be deferred until the response is written out to the client. This is automatically enabled if any of the header fields are being deleted.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Set a custom header field on all requests
header Custom-Header "My value"

# remove "Hidden" header field
header -Hidden

# Replace http:// with https:// in any Location header
header Location http:// https://

# Set security headers on all pages
header {
# enable HSTS
Strict-Transport-Security max-age=31536000;

# disable clients from sniffing the media type
X-Content-Type-Options nosniff

# clickjacking protection
X-Frame-Options DENY

# keep referrer data off of HTTP connections
Referrer-Policy no-referrer-when-downgrade
}

# Set a default cache expiration if upstream doesn't define one
header ?Cache-Control "max-age=3600"
reverse_proxy upstream:443

root

Sets the root path of the site, used by various matchers and directives that access the file system. If unset, the default site root is the current working directory.

Specifically, this directive sets the {http.vars.root} placeholder.

This directive does not automatically enable serving static files, so it is often used in conjunction with the [file_server directive][toc_haven200_file_server] or the [php_fastcgi directive][toc_haven200_php_fastcgi].

1
2
3
4
5
# Set the site root to /home/user/public_html for all requests
root * /var/www/

# Change the site root only for requests in /foo/*
root /foo/* /var/www/foo

php_fastcgi

An opinionated directive that proxies requests to a PHP FastCGI server such as php-fpm.

Caddy’s reverse_proxy is capable of serving any FastCGI application, but this directive is tailored specifically for PHP apps. This directive is actually just a convenient way to use a longer, more common configuration (below).

1
2
3
4
5
6
7
8
9
10
11
12
php_fastcgi [<matcher>] <php-fpm_gateways...> {
root <path>
split <substrings...>
env [<key> <value>]
index <filename>
resolve_root_symlink
dial_timeout <duration>
read_timeout <duration>
write_timeout <duration>

<any other reverse_proxy subdirectives...>
}
  • <php-fpm_gateways...> are the addresses of the FastCGI servers.
  • root sets the root folder to the site. Default: root directive.
  • split sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the “path info” from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the CGI script to use. Default: .php
  • env sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
  • index specifies the filename to treat as the directory index file. This affects the file matcher in the expanded form. Default: index.php
  • resolve_root_symlink enables resolving the root directory to its actual value by evaluating a symbolic link, if one exists.
  • dial_timeout is how long to wait when connecting to the upstream socket. Accepts duration values. Default: no timeout.
  • read_timeout is how long to wait when reading from the FastCGI server. Accepts duration values. Default: no timeout.
  • write_timeout is how long to wait when sending to the FastCGI server. Accepts duration values. Default: no timeout.

Expanded form

The php_fastcgi directive is the same as the following configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
route {
# Add trailing slash for directory requests
@canonicalPath {
file {
try_files {path}/index.php
}
not path */
}
redir @canonicalPath {path}/ 308

# If the requested file does not exist, try index files
@indexFiles {
file {
try_files {path} {path}/index.php index.php
split_path .php
}
}
rewrite @indexFiles {http.matchers.file.relative}

# Proxy PHP files to the FastCGI responder
@phpFiles {
path *.php
}
reverse_proxy @phpFiles <php-fpm_gateway> {
transport fastcgi {
split .php
}
}
}

Most modern PHP apps work well with this preset. If yours does not, feel free to borrow from this and customize it as needed instead of using the php_fastcgi shortcut.

Examples

1
2
3
4
5
6
7
8
#Proxy all PHP requests to a FastCGI responder listening at 127.0.0.1:9000:
php_fastcgi 127.0.0.1:9000

#Same, but only for requests under /blog/:
php_fastcgi /blog/* 127.0.0.1:9000

#When using php-fpm listening via a unix socket:
php_fastcgi unix//run/php/php7.4-fpm.sock

rewrite

Rewrites the request internally. A rewrite changes some or all of the request URI.

The rewrite directive implies the intent to accept the request, but with modifications. It is mutually exclusive to other rewrite directives in the same block, so it is safe to define rewrites that would otherwise cascade into each other; only the first matching rewrite will be executed.

Because rewrite essentially performs an internal redirect, the Caddyfile adapter will not fold any subsequent, adjacent handlers into the same route if their matchers happen to be exactly the same. This allows the matchers of the next handlers to be deferred until after the rewrite. In other words, a matcher that matches a request before the rewrite might not match the same request after the rewrite. If you want your rewrite to share a route with other handlers, use the route or handle directives.

1
rewrite [<matcher>] <to>
  • is the URI to set on the request. Only designated parts will be replaced. The URI path is any substring that comes before ?. If ? is omitted, then the whole token is considered to be the path.

Examples

1
2
3
4
5
6
7
8
# Rewrite all requests to foo.html, leaving any query string unchanged:
rewrite * /foo.html

# Replace the query string on API requests with a=b, leaving the path unchanged:
rewrite /api/* ?a=b

# Preserve the existing query string and add a key-value pair:
rewrite /api/* ?{query}&a=b

Preference: