Caddy server recipes

These recipes are for the Caddy web server version 2.

Create a Caddyfile consisting of the following single line:

import Caddyfile.d/*.conf

Create the directory Caddyfile.d/ next to the Caddyfile. Put your site configurations in Caddyfiles with the extension .conf in Caddyfile.d/. Note that with this setup one broken Caddyfile will prevent your server from starting.

Use the respond or the file_server directive in the error handler.

handle_errors {
	file_server {
		status 200
	}

    rewrite * /templates/index.html
}

This will only work with a single query parameter. The example is something useful for the Fossil SCM wiki. It prevents search engines and users from accessing your pages as both /wiki?name=foo and /wiki/foo. (The latter seems more search engine-friendly.)

@wiki_query {
	path_regexp ^/wiki(?:/*|)$
	query name=*
}

route @wiki_query {
	uri replace name= /
	redir * /wiki{query}
}

root * /opt/foo/static

@not_static {
	not {
		path /BingSiteAuth.xml /robots.txt
		path /favicon.ico
	}
}

reverse_proxy @not_static localhost:8100
file_server

@foo path /foo /foo/*

reverse_proxy @foo {
	to localhost:9000
	transport fastcgi
}

You can use the unofficial third-party scgi-transport module.

@foo path /foo /foo/*

reverse_proxy @foo {
	to localhost:9999
	transport scgi
}

See the Glowing Bear wiki page “Proxying WeeChat relay with a web server”.

weechat.example.com {
	reverse_proxy /weechat localhost:9001
}

Caddy can run CGI script either with a plugin or with a FastCGI-to-CGI proxy. See my caddy-cgi repository.

You can serve Markdown files as adequately good-looking minimal web pages. See my caddy-markdown-site repository.

This will give your server a human-readable, if not the most user-friendly, error page. It will tell the user an error has occurred and give them something to report to you. The page will contain a custom error message for the most common errors: 403, 404, 500. Otherwise the error message will be the description text of the HTTP status code.

handle_errors {
	rewrite * /error.html

	file_server
	templates
}

<!DOCTYPE html>
{{- $code := placeholder "http.error.status_code" -}}
{{- $text := placeholder "http.error.status_text" }}
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>{{ $code }} {{ $text }}</title>

    <link rel="icon" href="/favicon.ico" type="image/x-icon">
</head>

<body>
    <h1>Error {{ $code }}</h1>

    {{ if eq $code "403" -}}
    <p>You don't have permission to access this resource.</p>
    {{- else if eq $code "404" -}}
    <p>The requested URL was not found on this server.</p>
    {{- else if eq $code "500" -}}
    <p>An internal server error has occurred.</p>
    {{- else -}}
    <p>{{ $text }}.</p>
    {{- end}}

    <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#" onclick="javascript:history.back(); return false;">Back</a></li>
        <li>{{- /* The `href` does not preserve the fragment identifier; only the JavaScript does. */ -}}<a href="{{ .OriginalReq.URL }}" onclick="javascript:window.location.reload(); return false;">Reload</a></li>
    </ul>
</body>

</html>

This website has a dynamic license page feature inspired by mit-license.org. Pages like /mit-license/2025 are generated from a template. This allows you to link to a copy of your chosen software license with specified copyright years. However, it prevents arbitrary text injection. This is useful because you can add a free license to a short code snippet or a script, and it only takes up one line.

The following show how I have set it up with Caddy templates.

First, the Caddyfile. We match the path /mit-license and /mit-license/license.txt with an optional year or range of years.

Caddy only evaluates files with a certain Content-Type as templates. You can choose the particular types (text/html and text/plain by default), but Caddy must detect one. On FreeBSD, Caddy doesn’t simlly detect license.txt as text/plain. On a typical Debian or Ubuntu installation, it works as expected. See “Content-Type on FreeBSD”.

@mit_license {
	path_regexp license ^/mit-license(?:|/|/(?<years>\d{4}(?:-\d{4})?)/?)$
}
handle @mit_license {
	rewrite * /mit-license.html

	file_server
	templates
}

@mit_license_txt {
	path_regexp license ^/mit-license/(?:(?<years>\d{4}(?:-\d{4})?)/)?license.txt$
}
handle @mit_license_txt {
	rewrite * /mit-license/license.txt

	file_server
	templates
}

handle * {
	file_server
	try_files {path} {path}.html
}

This is the template part of the license webpage. The fallback year 2025 is inserted at build time by my static site generator. The page links to license.txt for the current range of years.

{{- $years := or (placeholder "http.regexp.license.years") "2025" -}}

<p><a href="/mit-license/{{ $years }}/license.txt">Download</a>.</p>
<pre class="text"><code>Copyright (c) {{ $years }} J. Random Hacker

[...]
</code></pre>
```

This is the downloadable plain-text license file. It has no fallback year inserted at build time because the page above always links to some particular year range.

Copyright (c) {{ or (placeholder "http.regexp.license.years") "20XX" }} D. Bohdan

[...]

This solution is an alternative to setting the Content-Type header manually. You need to create /usr/local/etc/mime.types on your FreeBSD system. You can download the latest version of the file in Apache from the GitHub mirror:

# As root:
fetch -o /usr/local/etc/mime.types https://raw.githubusercontent.com/apache/httpd/master/docs/conf/mime.types

Alteratively, ports like Apache 2.4 and Python provide the file. Copy or symlink one of the following files to /usr/local/etc/mime.types if you have them installed:

  • /usr/local/etc/apache24/mime.types
  • /usr/local/lib/python3.11/test/mime.types (older version than in Apache; lacks image/avif)