Pingclairfile
A Pingclairfile is Pingclair’s configuration file, written in the Caddyfile language: an optional global options block, then one block per site, each holding directives. A Caddyfile that uses only supported directives loads unchanged. This page describes the language; the directive reference describes what each directive does.
📌 This page describes v0.2.2.
🔤 Lexical rules
Section titled “🔤 Lexical rules”| Rule | Detail |
|---|---|
| Comments | # to the end of the line. |
| Quoting | A value containing spaces is quoted with ". Quotes are removed before the value is parsed. |
| Blocks | A { that opens a block ends its line. route { respond "hi" on one line is refused. |
| Durations | Written with a unit: 30s, 5m, 1h. A bare number is refused where a duration is expected. |
| Sizes | kb, mb, gb, and tb are powers of 1000; kib, mib, gib, and tib are powers of 1024. 10MB is 10,000,000 bytes. |
| Case | Directive and option names are lowercase. |
| Placeholders | {host}, {path}, {args[0]}, {block}, and the rest of the placeholder set are expanded where the directive documents them. |
🌐 Addresses
Section titled “🌐 Addresses”A site block is named by its address. The address decides which port the site listens on and whether it is served over HTTPS.
example.com { # HTTPS on 443 with a public certificate; 80 redirectsexample.com:8443 { # HTTPS on 8443: a host with a port is still HTTPSlocalhost:8080 { # HTTPS on 8080, from the internal authority:8080 { # plaintext HTTP on 8080, for any hosthttp://example.com { # plaintext HTTP on http_port (80 by default)http://[::1]:8080 { # plaintext HTTP for the host [::1]*.example.com { # one label: a.example.com, not a.b.example.com- A host with a port and no scheme is served over HTTPS, as in Caddy. Write
http://in front of the address to use plaintext HTTP on any port. - An address with a scheme and no port, such as
https://example.com, listens on the globalhttp_portorhttps_port. - A bracketed IPv6 address names a site, exactly as
127.0.0.1does. A bracket that does not hold an IPv6 address, or that is followed by anything other than:port, is refused. http://0.0.0.0:8080is a catch-all for the port, likehttp://:8080.- Host names are compared without regard to letter case and a trailing dot, so
Example.comandexample.com.reach the site namedexample.com.
🔌 One port is one listener
Section titled “🔌 One port is one listener”Sites that share a port share one socket, and the Host header selects
the site. A site with a specific address, such as http://127.0.0.1:8080, that
shares its port with a site listening on every interface is therefore reachable
on every interface by a client that sends its Host; give it a port of its own
if it must stay on loopback. Each such fold is logged when the configuration
loads.
The configuration is refused when sites on one port need different socket
policy: a site restricted by bind or default_bind beside one that listens
everywhere, a plaintext site beside a TLS site, or PROXY protocol on only one
of them.
🧭 Matchers
Section titled “🧭 Matchers”A matcher limits a directive to some requests. It is written inline, such as a
path like /api/*, or declared once as @name and referred to by that name.
example.com { @api path /api/* header @api Cache-Control "no-store"
handle /assets/* { file_server ./assets }}How a path pattern matches:
- Letter case is ignored for ASCII letters:
/Admin/*answers/admin/users. Usepath_regexpwhen case must decide. - Escapes are decoded once before the comparison:
/secret%21matchespath /secret!. A pattern written with an escape, such as/a%20b, must be written decoded ("/a b") to match. - A
*may appear anywhere. A leading*matches a suffix at any depth (*.phpmatches/x/y/index.php), a*at each end matches a substring (*/admin/*), and a*elsewhere stays inside one path segment (/a/*xmatches/a/bx, not/a/b/cx).?,[…], and\are literal characters. - Braces are literal.
/{id}matches only that path; usepath_regexpfor captures.
The addresses a request comes from are matched by two different matchers:
client_ipmatches the client aftertrusted_proxiesis applied: the forwarded address when the connection comes from a trusted proxy.remote_ipmatches the connection’s own peer, regardless of forwarding headers. Behind a trusted load balancer, that is the balancer.
Either matcher also takes Caddy’s private_ranges keyword in place of a list,
which expands at load to the same six ranges trusted_proxies static private_ranges uses: 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8,
127.0.0.1/8, fd00::/8 and ::1. So @local client_ip private_ranges
matches a client on a private or loopback address.
A range that does not parse, such as 10.0.0.0/33, is refused at load.
handle, handle_path, and route take at most one matcher token before
their block: *, a path that starts with /, or @name. Any other token is
refused.
🏷️ Placeholders for addresses
Section titled “🏷️ Placeholders for addresses”| Placeholder | Value |
|---|---|
{client_ip}, {http.request.client_ip} |
The client after trusted_proxies is applied. |
{remote_host}, {http.request.remote.host} |
The connection’s peer address. |
{remote_port}, {http.request.remote.port} |
The connection’s peer port. |
{remote}, {http.request.remote} |
The peer as host:port. |
{remote_ip} is not a placeholder and is refused at load, with a message that
names both replacements. Behind trusted_proxies, write
header_up X-Real-IP {client_ip} to forward the client’s address.
🧭 Which route answers
Section titled “🧭 Which route answers”A site’s routes are tried as one list, ordered the way Caddy orders them, and
the first route that matches answers. The order comes from the directive, not
from where the line is written: redir, handle, and route rank ahead of
respond, which ranks ahead of reverse_proxy, php_fastcgi, and
file_server. In the site below, /assets/a.txt receives hello instead of the
file:
example.com { root * /srv file_server /assets/* respond "hello" 200}Between routes of the same directive:
- The one whose single path is longer once a trailing
*is removed goes first:/foobar*before/foo. - For paths that differ only by the trailing
*, the exact path goes first:/foobefore/foo*. - Otherwise, file order decides. Caddy sorts two different paths of equal length alphabetically instead.
- A matcher with several paths, or none, goes after every single-path route.
A middleware directive written with a matcher, such as basic_auth /admin/* or
header @api …, applies authentication or header changes to every route that answers the requests it
matches, wherever the order puts that route.
To keep a narrower route in front, wrap the routes in handle blocks, move a
directive with the global order option (order file_server first), or list
them in a route block, which keeps the written order.
🧩 Snippets and imports
Section titled “🧩 Snippets and imports”Snippets are reusable fragments. A snippet declared as (name) { ... } is
included with import name, and can receive a block from its caller:
(proxied) { https://{args[0]} { encode zstd gzip {block} }}
import proxied example.com { reverse_proxy 127.0.0.1:3000}{args[0]} is the first argument after the snippet name, and {block} is the
block the caller supplies. When the caller supplies no block, {block} expands
to nothing and the snippet still compiles. Named sub-blocks are addressed as
{blocks.<name>}.
🧰 Command-line tooling
Section titled “🧰 Command-line tooling”Three commands help while writing a configuration:
pingclair validatecompiles the file, reads the certificate files it names, and names the first problem.pingclair adapt --prettyprints the JSON the file compiles to, after the same validation.pingclair fmtformats the file with one tab per level, and exits with status 1 when the file was not already formatted.
Command line lists every subcommand and flag.
🚫 What is not part of the language
Section titled “🚫 What is not part of the language”The Caddyfile language defines more directives and options than Pingclair implements. A name Pingclair recognizes but does not implement is refused when the file loads, with a message naming the missing feature, and the configuration is rejected. Project status lists those names.
