# Caddy server recipes These recipes are for the [Caddy](https://caddyserver.com/) web server version 2. ## Contents ## Multiple sites in separate files (Caddyfile.d) {#caddyfile.d} Create a Caddyfile consisting of the following single line: ```caddy 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. ## Overriding an error status code {#error-status-override} Use the [`respond`](https://caddyserver.com/docs/caddyfile/directives/respond) or [`file_server`](https://caddyserver.com/docs/caddyfile/directives/file_server) directive in the error handler. ```caddy handle_errors { file_server { status 200 } rewrite * /templates/index.html } ``` ## Redirecting a query parameter to a path {#redir-query-parameter} This will only work with a single query parameter. The example is something useful for the [Fossil SCM](https://fossil-scm.org/) 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.) ```caddy @wiki_query { path_regexp ^/wiki(?:/*|)$ query name=* } route @wiki_query { uri replace name= / redir * /wiki{query} } ``` ## Reverse proxy for... {#reverse-proxy} ### Everything except some static files {#everything-not-static} ```caddy root * /opt/foo/static @not_static { not { path /BingSiteAuth.xml /robots.txt path /favicon.ico } } reverse_proxy @not_static localhost:8100 file_server ``` ### FastCGI (generic, non-PHP) {#fastcgi} ```caddy @foo path /foo /foo/* reverse_proxy @foo { to localhost:9000 transport fastcgi } ``` ### SCGI {#scgi} You can use the unofficial third-party [scgi-transport](https://github.com/Elegant996/scgi-transport) module. ```caddy @foo path /foo /foo/* reverse_proxy @foo { to localhost:9999 transport scgi } ``` ### WeeChat {#weechat} See the Glowing Bear wiki page ["Proxying WeeChat relay with a web server"](https://github.com/glowing-bear/glowing-bear/wiki/Proxying-WeeChat-relay-with-a-web-server). ```caddy weechat.example.com { reverse_proxy /weechat localhost:9001 } ``` ## Running CGI scripts {#cgi} Caddy can run CGI scripts either with a plugin or with a FastCGI-to-CGI proxy. See my [caddy-cgi](https://github.com/dbohdan/caddy-cgi) repository. ## Serving Markdown files as pages {#markdown} You can serve Markdown files as adequately good-looking minimal web pages. See my [caddy-markdown-site](https://github.com/dbohdan/caddy-markdown-site) repository. ## Showing an error page on errors {#error-page} 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, and 500. Otherwise, the error message will be the description text of the HTTP status code. ### Caddyfile {#error-page-caddyfile} ```caddy handle_errors { rewrite * /error.html file_server templates } ``` ### error.html {#error-page-error.html} ```html {{- $code := placeholder "http.error.status_code" -}} {{- $text := placeholder "http.error.status_text" }} {{ $code }} {{ $text }}

Error {{ $code }}

{{ if eq $code "403" -}}

You don't have permission to access this resource.

{{- else if eq $code "404" -}}

The requested URL was not found on this server.

{{- else if eq $code "500" -}}

An internal server error has occurred.

{{- else -}}

{{ $text }}.

{{- end}} ``` ## Dynamic license pages {#licenses} This website has a dynamic license page feature inspired by [mit-license.org](https://mit-license.org/). Pages like [/mit-license/2026](/mit-license/2026) are generated from a template. This allows you to link to a copy of your chosen software license with specified copyright years while preventing 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 shows how I have set this up with Caddy templates. ### Caddyfile {#licenses-caddyfile} First, the Caddyfile. We match the paths `/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 detect `license.txt` as `text/plain` by default. On a typical Debian or Ubuntu installation, it works as expected. See ["Content-Type on FreeBSD"](#content-type-freebsd). ```caddy @mit_license { path_regexp license ^/mit-license(?:|/|/(?\d{4}(?:-\d{4})?(?:,\d{4}(?:-\d{4})?){0,100})/?)$ } handle @mit_license { rewrite * /mit-license.html file_server templates } @mit_license_txt { path_regexp license ^/mit-license/(?:(?\d{4}(?:-\d{4})?(?:,\d{4}(?:-\d{4})?){0,100})/)?license.txt$ } handle @mit_license_txt { rewrite * /mit-license/license.txt file_server templates } handle * { file_server try_files {path} {path}.html } ``` ### mit-license.html {#licenses-mit-license.html} This is the template part of the license webpage. The fallback year is based on server time. The page links to `license.txt` for the current range of years. ~~~html {{- $years := or (placeholder "http.regexp.license.years") (now | date "2006") -}}

Download.

Copyright (c) {{ $years }} J. Random Hacker

Permission is hereby granted, free of charge, to any person obtaining a copy
[...]
~~~ ### license.txt {#licenses-license.txt} This is the downloadable plain-text license file. The fallback year is based on server time, as above. ```plaintext Copyright (c) {{ or (placeholder "http.regexp.license.years" | replace "," ", ") (now | date "2006") }} J. Random Hacker Permission is hereby granted, free of charge, to any person obtaining a copy [...] ``` ## Content-Type on FreeBSD {#content-type-freebsd} When working on [dynamic license pages](#licenses), I found that Caddy on FreeBSD evaluated HTML but not plain-text files as templates. This contradicted Caddy documentation that said it evaluated both. I traced the problem to the files not having a `Content-Type: text/plain` header or any [`Content-Type` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type) like they had on Linux. This solution is an alternative to setting the `Content-Type` header manually. You need to create `/usr/local/etc/mime.types` on your system. You can download the latest version of the file from Apache's GitHub mirror: ```shell # As root: fetch -o /usr/local/etc/mime.types https://raw.githubusercontent.com/apache/httpd/master/docs/conf/mime.types ``` Alternatively, 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` in Python 3.11) ## Page metadata URL: Published 2020-12-16, updated 2025-12-03. Tags: - Caddy - configuration - how-to - Internet - sysadmin - web - web development