Skip to content

v2: Documentation

Matt Holt edited this page Aug 22, 2019 · 35 revisions

This page describes how to use Caddy 2, which is a work-in-progress on the v2 branch. See its README to know how to download and build Caddy 2 for now.

Features which are available in Caddy Enterprise are indicated with   🏢 Enterprise.


Command-line interface


$ caddy start
	[--config <path>]
	[--config-adapter <name>]

Starts the Caddy process, optionally bootstrapped with an initial config file. Blocks until server is successfully running (or fails to run), then returns. On Windows, the child process will remain attached to the terminal, so closing the window will forcefully stop Caddy. See run for more details.


$ caddy run
	[--config <path>]
	[--config-adapter <name>]

Same as start, but blocks indefinitely; i.e. runs Caddy in "daemon" mode. On Windows, this is recommended over caddy start when running Caddy manually since it will be more obvious that Caddy is still running and bound to the terminal window.

If a config file is specified, it will be applied immediately after the process is running. If the config file is not in Caddy's native JSON format, you can specify an adapter with --config-adapter to adapt the given config file to Caddy's native format. The config adapter must be a registered module. Any warnings will be printed to the log, but beware that any adaptation without errors will immediately be used. If you want to review the results of the adaptation first, use adapt-config.

As a special case, if the current working directory has a file called "Caddyfile" and the caddyfile config adapter is plugged in (default), then that file will be loaded and used to configure Caddy, even without any command line flags.

If --print-env is specified, the environment as seen by the Caddy process will be printed before starting. This is the same as the environ command but does not quit after printing.


$ caddy stop

Gracefully stops the running Caddy process. (Note: this will stop any process named the same as the executable.) On Windows, this stop is forceful and Caddy will not have an opportunity to clean up any active locks; for a graceful shutdown on Windows, use Ctrl+C or the /stop endpoint.


$ caddy reload
	--config <path>
	[--config-adapter <name>]
	[--address <interface>]

Gives the running Caddy instance a new configuration. This has the same effect as POSTing a document to the /load endpoint, but is convenient for simple workflows revolving around config files. Since the admin endpoint is configurable, the endpoint configuration is loaded from the --address flag if specified; otherwise it is loaded from the given config file; otherwise the default is assumed.


$ caddy version

Prints the version.


$ caddy list-modules

Prints the modules that are installed.


$ caddy environ

Prints the environment as seen by caddy. Can be useful when debugging init systems or process manager units like systemd.


$ caddy adapt-config
	--input <path>
	--adapter <name>

Adapts a configuration to Caddy's native JSON config structure and writes the output to stdout, along with any warnings to stderr. If --pretty is specified, the output will be formatted with indentation for human readability.


To adapt a Caddyfile that you can easily read and tweak manually later:

$ caddy adapt-config --input /path/to/Caddyfile --adapter caddyfile --pretty

Admin endpoint

Caddy is configured through an administration endpoint which is an HTTP listener with a REST API.

Default address: localhost:2019

POST /load

Sets Caddy's configuration to the JSON body. The Content-Type header must indicate a JSON payload, e.g. application/json. Configuration changes are very lightweight and efficient.

Note for Enterprise users: If you are using the /config/ endpoint to modify configuration instead, you MUST NOT use /load because it lacks the capabilities for partial configuration changes.


$ curl -X POST "http://localhost:2019/load" \
	-H "Content-Type: application/json"
	-d @caddy.json

POST /stop

Gracefully shuts down the server.


$ curl -X POST "http://localhost:2019/stop"

GET /config/[scope]

🏢 Enterprise

Exports Caddy's current configuration. Returns a JSON body. Any path appended to this endpoint will traverse the configuration and return only that scope.


$ curl "http://localhost:2019/config/"
$ curl "http://localhost:2019/config/apps/http/servers/myserver/listen"

POST /config/[scope]

🏢 Enterprise

Changes Caddy's configuration at the named scope to the JSON body of the request. If the named scope is an array, POST appends; if an object, it creates or replaces.


$ curl -X POST \
	-H "Content-Type: application/json" \
	-d '":2080"' \

PUT /config/[scope]

🏢 Enterprise

Changes Caddy's configuration at the named scope to the JSON body of the request. If the named scope is an array, PUT inserts; if an object, it strictly creates a new value.


$ curl -X PUT \
	-H "Content-Type: application/json" \
	-d '":2080"' \

PATCH /config/[scope]

🏢 Enterprise

Changes Caddy's configuration at the named scope to the JSON body of the request. PATCH strictly replaces an existing value or array element.


$ curl -X PUT \
	-H "Content-Type: application/json" \
	-d '":2080"' \

DELETE /config/[scope]

🏢 Enterprise

Changes Caddy's configuration at the named scope to the JSON body of the request. DELETE deletes the value at the named scope.


$ curl -X DELETE "http://localhost:2019/config/apps/http/servers/myserver"

Caddy modules

Caddy modules are distinct from Go modules, although technically speaking, Caddy modules can be implemented by Go modules. When we talk about modules here, we mean Caddy modules.

Caddy modules are the evolution of "plugins" in Caddy 1, and although modules are much-improved over plugins, the idea is similar. Various parts of Caddy's functionality can be swapped out or extended by use of modules which are statically compiled in at build-time.

Each Caddy module has a name, scope/namespace, and ID:

  • A name looks like a.b.c.module_id
  • The namespace would be a.b.c
  • The module ID is module_id which must be unique in its namespace

When the context makes it obvious what the namespace is, we generally refer to the module ID as the name, or use the terms interchangably; and in conversation the namespace can be omitted.

Host modules (or parent modules) are modules which load/initialize other modules.

Guest modules (or child modules) are modules which get loaded or initialized. All modules are at least guest modules.

Config intro

At its core, Caddy 2 is configured with JSON via an API. There are no config files, but Caddy's command line interface can wrap the task of loading a config from a file and feeding it to the API for you. There are also alternatives to writing JSON by hand; see Config Adapters for more on that.

This section covers just a few things you will find helpful as you craft a Caddy config.

Making one from scratch

We have a separate page which walks you through an example of building up a config of a working static file server with automatic HTTPS from scratch.

Duration values

Any properties which expect a duration value are given as a string in the same format as Go's time.Duration type:

A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".

Valid examples: "30s", "15m", "1h25m30s", and "30us".

Placeholders (variables)

You can inject dynamic values into Caddy 2's configuration by using placeholders (aka variables). Placeholders can be used in properties which accept placeholder values; not all properties execute placeholders. Only string values can use placeholders.

Placeholders are enclosed in { } and namespaced to avoid collisions. Different placeholders are available in different contexts.

All contexts support the global placeholders:

	The system's hostname
	The system's OS
	The system's architecture
	The system's filepath separator
	Replace variable_name with any env variable (lowercase)

Config adapters

Caddy's native config language is JSON, but writing JSON by hand can be tedious and error-prone. That's why Caddy supports being configured with other means through config adapters. They make it possible to use config in a more comfortable or convenient format by outputting Caddy JSON for you.

Config adapters do their best to translate your input to Caddy JSON with the highest fidelity and correctness, but some features/behaviors don't translate well or are not yet programmed. Because this conversion process is not guaranteed to be complete and correct all the time, we don't call them "converters" or "translators". They are "adapters" since they will at least give you a good starting point to finish crafting your final JSON config.

That said, the Caddyfile format is specifically designed for Caddy, so any and all of the Caddyfile's features should translate to Caddy JSON correctly and completely. The Caddyfile adapter is a special case in this sense.

Generally, config adapters will output warnings when something cannot be adapted in a way that would be expected. if the behavior cannot be replicated perfectly in Caddy, or if the feature doesn't make sense in Caddy, or if the adapter simply doesn't cover all the possible use cases yet. However, warnings are not errors, and for this reason we advise against simply throwing your NGINX config into the NGINX config adapter using caddy run to immediately start your server without first verifying the adapter's output. You can use the adapt-config command for this.

Config adapters are an exciting and unique feature. We hope you will help contribute to the config adapters!

Caddyfile adapter

The Caddyfile is the #1 choice for most users to configure Caddy because it is extremely quick to learn, easy to comprehend, and very productive for developers. The Caddyfile syntax is documented on the Caddy website.

The Caddyfile is not as expressive as Caddy's native JSON, so it cannot describe all possible Caddy configurations. However, it is designed to be suitable for upwards of ~95% of use cases. Some odd web apps or other edge cases may necessitate using the Caddy JSON directly, but you can still use a config adapter such as the Caddyfile as a starting point. Caddy's config is the most flexible on the planet, even if the Caddyfile can't express all of the possibilities.

The Caddyfile is geared toward everyone, not just beginners. Rather than skill level, the decision about whether to use the Caddyfile should be more about what your technical and business requirements are, or if nothing else, what you personally prefer.

The Caddyfile can support different "server types" or syntaxes. The default is the HTTP server type, which we describe here. You may be familiar with others, such as the Corefile from CoreDNS, which is a different flavor of the same Caddyfile syntax.

The v2 Caddyfile is not backwards-compatible with v1; as such, you cannot bring your v1 Caddyfile and expect it to work in v2 without changes (although some will be compatible by chance).

At a high level, major changes from v1 include:

  • Some directives have changed, along with their syntax/structure.
  • The static file server is no longer enabled by default. (There was no way to turn it off in v1, which could be problematic in some situations.) There is a file_server directive to enable the static file server.
  • Request matching is much more powerful and not limited to just path prefixes anymore.
  • Directives need not be centrally registered on a list in the Caddy code base anymore. This means you can write a handler module for Caddy and publish it and use it right away, without needing to submit a PR to the Caddy code base to add your directive to the list and wait for the next Caddy release.

Matcher tokens

Request matching is one of the most important parts of configuring a web server. For directives that inject handlers into the middleware chain, you can specify which requests that handler applies to with matchers.

A matcher token comes immediately after the directive token and can be one of three forms:

  1. * to match all requests.
  2. /path starting with a forward slash to match path prefix.
  3. match:name to specify a matcher by name.

If a matcher token is omitted, it is the same as a wildcard matcher. The wildcard matcher is only needed if a matcher token is required; for example, if the argument you want to give a directive is itself a path, it would be ambiguous with a path matcher, so you use a wildcard token: root * /home/www/mysite

Path matching is the most common, so it is inlined for convenience: redir /old-article.html /new-article.html

Specifying a matcher by name gives you more flexibility. To define a matcher by name, use the matcher directive:

matcher name {

See the Caddyfile directives below for more details about matchers.

Shorthand placeholders

The Caddyfile is supposed to be easy to write, so it also provides shorter placeholder variable names for you to use. Instead of {http.request.uri.path} for example, you could just write {path}. Again, this loses some of the flexibility of the full JSON config because these shorthand variables are not properly namespaced. They are optional, however, so you are welcome to use the full name.

Shorthand Replaces
{uri} {http.request.uri}
{path} {http.request.uri.path}
{host} {}
{hostport} {http.request.hostport}
{method} {http.request.method}
{scheme} {http.request.scheme}
{file} {http.request.uri.path.file}
{dir} {http.request.uri.path.dir}
{query} {http.request.uri.query}

Global config

The Caddyfile has a special place for specific settings that apply to the whole process rather than specifically per-site. This allows us to replace an arbitrary and growing number of CLI flags and environment variables, which constituted a large portion of hidden configuration in version 1.

Simply create a server block with no keys to specify such global options. The following options are allowed:

	http_port     <port>
	https_port    <port>
	handler_order appearance|<directives...> {

Note that this feature is not a "defaults" block which all other sites inherit. The Caddyfile does not have inheritence. To share repeated directives across many site definitions, use snippets.

Caddyfile directives


Specifies the site root, usable by other directives that need a root path.

root [<matcher>] <path>

Creates a matcher. Multiple matchers are AND'ed.

matcher <name> {
	host <hosts...>
	path <paths...>
	path_regexp <regexp>
	method <methods...>
	query <key=val...>
	header <field> <value>
	header_regexp <field> <regexp>
	protocol http|https|grpc
	remote_ip <ranges...>
	file {
		root <paths>
		try_files <files>
		try_policy first_exist|smallest_size|largest_size|most_recent_modified

Enables static file server, optionally with directory browsing.

file_server [<matcher>] [browse] {
	hide <files...>
	index <files...>
	browse [<template_file>]
	root <path>

Encodes HTTP responses. Used for compression.

encode [<matcher>] <formats...> {
	gzip [<level>]
	brotli [<quality>]

Manipulates HTTP response headers.

headers [<matcher>] [[+|-]<field> <value>] {
	[+][<field>] [<value>]

Reverse proxy (very much WIP).

reverse_proxy [<matcher>] <to...>

Issue an HTTP redirect.

redir [<matcher>] to [<code>|permanent|temporary]

Rewrite the request URI.

rewrite [<matcher>] to

Execute the HTTP response body as a template.

templates [<matcher>] {
	mime <types...>
	between <open_delim> <close_delim>
	root <path>

Respond with a hard-coded HTTP response.

static_response [<matcher>] <status> {
	body <text>
tls (directive)

Configure TLS for the site in which the directive appears. (Still WIP)

tls [off|<cert> <key>] {
	load <folders...>
	protocols <min> [<max>]
	ciphers <list...>
	curves <list...>
	alpn <list...>

Rewrites a request based on the existence of the listed files on disk. Because this is a fairly common rewrite, this directive simply combines a rewrite handler with a file matcher for convenience in writing.

try_files <files...>

Config structure

Caddy configuration is a JSON document.

Generally, object keys are optional, as most of the "required" settings have sane defaults. If something is actually explicitly required for a successful config load, the docs will say so.

At the top level, you'll find properties needed for Caddy's basic functionality:

	"admin": {},
	"storage": {},
	"apps": {}


Configures the administration endpoint.

	"listen": "localhost:2019"
  • listen: the address to which the admin endpoint's listener should bind itself. At least one listener is required.


Configures Caddy's default storage module. A storage module defines how and where Caddy stores assets (such as TLS certificates).

Default: The local file system ( If XDG_DATA_HOME is set, then $XDG_DATA_HOME/caddy is the folder. Otherwise, $HOME/.local/share/caddy is the folder.

	"module": "file_system",
	"root": "/var/caddy_storage"
  • module: The ID of the storage module.
  • root: The base path in which things should be stored.


Each app to initialize should be keyed by its name (i.e. module ID).

Example which initializes the tls and http apps:

	"apps": {
		"tls": { ... },
		"http": { ... }


The http app implements an HTTP server with automatic HTTPS:

	"http_port": 80,
	"https_port": 443,
	"grace_period": "",
	"servers": {}
  • http_port: The port to use for HTTP (optional; used for automatic HTTPS).
  • https_port: The port to use for HTTPS (optional; used for automatic HTTPS).
  • grace_period: How long to allow servers to shut down gracefully before forcing them to stop. Duration values follow Go's time.Duration format, e.g. "10s" or "1m30s".
  • servers: Server configurations, keyed by unique names you choose. A server is a set of listeners and routes which make sense to group together. At this time, servers cannot have overlapping listeners.


A map of server ID (of your choice) to its configuration object. Each server is an object with the following structure:

	"listen": [],
	"read_timeout": "",
	"read_header_timeout": "",
	"write_timeout": "",
	"idle_timeout": "",
	"max_header_bytes": 0,
	"routes": [],
	"errors": {},
	"tls_connection_policies": [],
	"automatic_https": {},
	"max_rehandles": 3,
	"strict_sni_host": false
  • listen: The list of listener addresses to bind to.
  • read_timeout: How long to allow reading an HTTP request.
  • read_header_timeout: How long to allow reading an HTTP request header.
  • write_timeout: How long to allow writing an HTTP response.
  • idle_timeout: How long to keep idle connections open.
  • max_header_bytes: How many bytes to allow an HTTP request header to be read.
  • routes: The list of routes.
  • errors: Configures how to handle errors during HTTP requests.
  • tls_connection_policies: List of TLS connection policies.
  • automatic_https: Customize or disable automatic HTTPS.
  • max_rehandles: How many rehandles to allow; prevents infinite looping.
  • strict_sni_host: If true, enforce that an HTTP Host header matches the connection's ServerName (SNI) value from the TLS handshake. Important when using TLS client authentication.


Listener addresses take on the following form:


For example:



Routes are not your typical notion of routes (i.e. "GET /foo/bar" -> someFunc). Routes in Caddy 2 are much more powerful and dynamic. They are given in an ordered list, and each route has two parts:

  1. match (optional)
  2. handle

You can think of "match-handle" like "if-then" if that helps (but Caddy config is not really imperative or procedural).

For each request, route matchers are evaluated in the order they are given in the list. Each route that matches the request is compiled into the composite route, which ends up becoming the HTTP handler for the request. All matchers in the routes list are evaluated before the request is sent down the handler chain.

In Caddy 1, HTTP handlers were chained in a hard-coded order you had no control over. Unlike Caddy 1, handlers in Caddy 2 are chained in the order you specify in the routes. So, the order of routes matters.

In some cases, a request can be rehandled, meaning that it is virtually processed through the top-level handler again. This is useful if changes were made to a request (such as rewriting part of it) and you want to process it as if it originally came in like that. Other web servers call this an internal redirect. Upon rehandling, a new composite route will be compiled and the request will be processed again.

The structure for a route is:

	"group": "",
	"match": [],
	"handle": [],
	"terminal": false
  • group: If this route belongs to a group, specify its name here. Groups are used for mutual exclusion. Only the first matching route from a group is compiled into the composite route. (Think like radio buttons on an HTML form.) Groups can be used to implement "else" and "else-if" logic with matchers.
  • match: The "if" statement of the route. This specifies an ordered list of matcher sets.
  • handle: The "then" statement of the route. This specifies an ordered list of handlers to chain together. Handlers are chained from first to last, so requests flow from the first handler to the last.
  • terminal: If true, no additional routes will be considered.
HTTP Placeholders

Within an HTTP route, you can use additional placeholders:

	The Host header of the request (including port, if given)
	Same as hostport but with port stripped
	N is the number; i.e. for "":
		0 = "com"
		1 = "example"
		2 = "foo"
	The port of the request.
	The scheme of the request (http/https usually)
	The full request URI
	The path component of the request URI
	The filename in the path, excluding directory
	The directory, excluding leaf filename
	A specific query string parameter given by PARAM
	Request header field name (lower-cased)
	Request cookie name (lower-cased)
	Response header field name (lower-cased)

If you are using regexp matchers, capture groups (both named and numeric) are available as well:



  • PATH_REGEXP with the name of the matcher that has the regular expression.
  • PATTERN_NAME with the lower-cased name you gave the pattern.
  • CAPTURE_GROUP_NAME with the name or index number of the capture group in the regular expression.

This will allow you to access capture groups from anywhere in your HTTP route.


Each route takes a list of matcher sets. A matcher set is comprised of matchers of various types. A matcher may be comprised of multiple values.


		"host": [""]
		"host": ["", ""],
		"path": ["/foo/bar"]

Here you can see there are 2 matcher sets. The first one has one matcher (host), and the second one has two (host and path).

The boolean logic of request matching goes like this:

  • Matcher sets are OR'ed (first matching matcher set is sufficient).
  • Matchers within a set are AND'ed (all matchers in the set must match).
  • Values within a specific matcher are OR'ed.

This design enables moderately complex logic such as:

IF (Host = "")
	OR (Host = "" AND Path = "/foo/bar")

The expressions in parentheses are matcher sets. This expression has 2 matcher sets. This expression uses two matchers: Host and Path, which are AND'ed together within their set.

Even more advanced logic can be expressed through the Starlark expression matcher at virtually no loss in performance.

A route may omit matchers entirely to match all requests.

All matchers within the same route list are evaluated before the requst is handled and do not modify the request, so all the matchers will see the same version of the request.

Matches requests by Host header.

"host": []

Host values can contain wildcards to substitute for one label of the domain name.


Matches requests by request path.

"path": []

Paths may contain globular patterns.


Matches requests by request path using a regular expression.

"path_regexp": {
	"name": "",
	"pattern": "",
  • name: A name for the regular expression so you can access its capture groups in placeholders.
  • pattern: The regular expression in Go's regexp syntax.

Matches requests by the request method.

"method": []

Methods should be upper-cased.


Matches requests by query string parameters.

"query": {
	"param": ["values"]

The object keys should be query string parameters, with the values to match in an array, as strings.


Matches requests by request headers.

"header": {
	"Field": ["values"]

The object keys should be header field names, with the values to match in an array.


Matches requests by request headers using a regular expression.

"header_regexp": {
	"Field": {
		"name": "",
		"pattern": ""

The object keys should be header fields, then:

  • name: A name for the regular expression so you can access its capture groups in placeholders.
  • pattern: The regular expression in Go's regexp syntax.

Matches requests by the protocol being used.

"protocol": ""

Possible values are http, https, or grpc.


Matches requests by negating a matcher set.

"not": {}

Its value should be a matcher set. The result of the matcher set will be negated.


Matches requests by the client IP address.

"remote_ip": {
	"ranges": []
  • ranges: A list of IP addresses or CIDR ranges to match.

Matches requests by evaluating a Starlark expression. This provides a great deal of flexibility with regards to boolean logic, and is a fine fit for advanced matching needs.

"starlark_expr": ""

The value should be a Starlark expression. (TODO: examples and docs)


Matches requests based on files on disk.

"file": {
	"root": "",
	"try_files": [],
	"try_policy": []
  • root: The base path with which relative paths will be rooted. Default is current working directory.
  • try_files: A list of root-relative file paths to "try" matching (similar to nginx's try_files). A file will be selected based on try_policy.
  • try_policy: When trying files listed in try_files, use this policy to choose one.
    • first_exist (default): Choose the first file that exists.
    • smallest_size: Choose the file with the smallest size.
    • largest_size: Choose the file with the largest size.
    • most_recent_modified: Choose the file that was most recently modified.

If a file is matched, two new placeholders will be made available:

  • {http.matchers.file.relative}: The root-relative path of the file. This is often useful when rewriting requests.
  • {http.matchers.file.absolute}: The absolute path of the file.


Handlers are modules which handle HTTP requests. They are chained together in a middleware fashion: requests flow from the first handler to the last (top of the config to the bottom), with the possibility that any handler could abort the chain and/or return an error. Responses flow back up the chain as they are written out to the client.

Not all handlers call the next handler in the chain. For example, the file_server handler always serves a file from disk or returns an error. Thus, configuring handlers after file_server in your route is illogical, since they would never be executed. You will want to put handlers which originate the response at the very end of your route(s). The documentation for a module should state whether it invokes the next handler, but sometimes it is common sense.

Some handlers manipulate the response. Remember that requests flow down, and responses flow up. For example, if you wanted to use both templates and encode handlers, you would need to put templates after encode in your route, because responses flow up. Thus, templates will be able to parse and execute the plain-text response as a template, and then return it up to the encode handler which will then compress it into a binary format.

If templates came before encode, then encode would write a compressed, binary-encoded response to templates which would not be able to parse the response properly.

The correct order, then, is this:

		"handler": "encode"
		"handler": "templates"
		"handler": "file_server"

The request flows ⬇️ DOWN (encode -> templates -> file_server).

  1. First, encode will choose how to encode the response and wrap the response.
  2. Then, templates will wrap the response with a buffer.
  3. Finally, file_server will originate the content from a file.

The response flows ⬆️ UP (file_server -> templates -> encode):

  1. First, file_server will write the file to the response.
  2. That write will be buffered and then executed by templates.
  3. Lastly, the write from templates will flow into encode which will compress the stream.

If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes.


Modifies request or response headers.

Changes to headers are applied immediately, except for the response headers when deferred is true or when required is set. In those cases, the changes are applied when the headers are written to the response. Note that deferred changes do not take effect if an error occurs later in the middleware chain.

Properties in this module accept placeholders.

Response header operations can be conditioned upon response status code and/or other header values.

	"handler": "headers",
	"request": {
		"set": {
			"Field": ["overwrites"]
		"add": {
			"Field": ["appends"]
		"delete": ["Goodbye-Field"]
	"response": {
		"set": {
			"Field": ["overwrites"]
		"add": {
			"Field": ["appends"]
		"delete": ["Goodbye-Field"],
		"deferred": true,
		"require": {
			"status_code": [2, 301],
			"headers": {
				"Foo": ["bar"]
  • request: Request headers to set, add, or delete.
  • response: Response headers to set, add, or delete.
  • response.deferred: If true, changes will be applied when the response headers are written instead of as the request is being processed (immediately). Not necessary if require is set.
  • response.require: Defer response header operations until they are written, and only if these criteria are met.
  • response.require.status_code: Apply response header changes if the status code matches one of these values. Can be a single digit to apply to all statuses in that class (i.e. 2 means all 2xx status codes) or a full status code (e.g. 404).
  • response.require.headers: Apply response header changes if the given response headers have the given value(s).

Changes the request's URI or method.

Properties in this module accept placeholders.

	"handler": "rewrite",
	"method": "FOO",
	"uri": "/new/path?param=val",
	"rehandle": false
  • method: Changes the request's HTTP verb.
  • uri: Changes the request's URI.
  • rehandle: If true, the request will sent for rehandling after rewriting.

Currently, does almost nothing: very plain/simple markdown rendering using Blackfriday. Will be configurable. But unlike Caddy 1, this already allows rendering any response body as Markdown, whether it be from a proxied upstream or a static file server. This needs a lot more testing and development.

	"handler": "markdown"

Change or limit the request body.

	"handler": "request_body",
	"max_size": 0
  • max_size: The maximum number of bytes to allow reading from the body by a later handler.

Compresses responses on-the-fly.

	"handler": "encode",
	"encodings": {
		"gzip": {"level": 5},
		"zstd": {},
		"brotli": {"quality": 6}
	"prefer": "",
	"minimum_length": 512
  • encodings: Selection of compression algorithms to choose from. The best one will be chosen based on the client's Accept-Encoding header. Available options are gzip, zstd, and brotli. Note that brotli is currently very slow and it is recommended to pre-compress static content instead.
  • prefer: If the client has no strong preference, choose this encoding. TODO: Not yet implemented
  • minimum_length: Only encode responses that are at least this many bytes long.

Interprets the response as a template body, then executes the template and writes the response to the client. There are functions to include other files, make sub-requests (virtual HTTP requests), render Markdown, manipulate headers, access the request fields, manipulate strings, do math, work with data structures, and more.

	"handler": "templates",
	"include_root": "",
	"mime_types": ["text/html", "text/plain", "text/markdown"],
	"delimiters": ["{{", "}}"]
  • include_root: The root path from which to load files. Required if template functions accessing the file system are used (such as .Include).
  • mime_types: The MIME types for which to render templates. It is important to use this if the route matchers do not exclude images or other binary files.
  • delimiters: The template action delimiters.

See all available template actions.


Responds to the request with a static (hard-coded) response. Does not call the next handler in the chain.

This is a great way to do HTTP redirects.

	"handler": "static_response",
	"status_code": 307,
	"headers": {
		"Location": [""]
	"body": "Response body",
	"close": true
  • status_code: The HTTP status code to respond with. Can be an integer or, if needing to use a placeholder, a string.
  • headers: Header fields to set on the response.
  • body: The response body.
  • close: If true, the server will close the client's connection after writing the response.

A powerful, flexible static file server.

In general, the request URI's path will be joined to the file server's root to get the file path to serve.

This module executes placeholders in most of its properties. It does not call the next handler in the chain.

	"handler": "file_server",
	"root": "",
	"hide": [],
	"index_names": [],
	"browse": {
		"template_file": ""
  • root: The path to the root of the site.
  • hide: A list of files or folders to hide; the file server will pretend as if they don't exist. Accepts globular patterns like "*.hidden" or "/foo/*/bar".
  • index_names: The names of files to try as index files if a folder is requested.
  • browse: Enables browsing if a directory was requested.
  • browse.template_file: Use this template file instead of the default browse template.

Reverse proxy. Still a WIP. Does not call the next handler in the chain.


	"handler": "reverse_proxy",
	"try_interval": "20s",
	"load_balance_type": "round_robin",
	"upstreams": [
			"host": "http://localhost:8080",
			"fast_health_check_dur": "100ms",
			"health_check_dur": "10s"
			"host": "http://localhost:8081",
			"health_check_dur": "2s"
			"host": "http://localhost:8082",
			"health_check_path": "health"
			"host": "http://localhost:8083",
			"circuit_breaker": {
				"type": "status_ratio",
				"threshold": 0.5

Creates a group of routes that are compiled and executed when this handler is invoked. This is useful if the evaluation of matchers needs to be deferred, like if they depend on placeholders created by other matchers that need to be evaluated first.

This handler does not invoke the next handler in the chain. Instead, it compiles and executes the chain defined in routes.

	"handler": "subroute",
	"routes": []
  • routes: The list of routes to compile and execute.

This handler returns an error value, but does not write a response. This is useful when you want the server to act as if an error occurred; for example, to invoke your custom error handling logic.

Since this handler does not write a response, the error information is for use by the server or other handlers to know how to handle the error.

	"handler": "error",
	"status_code": 404,
	"error": ""
  • status_code: The recommended HTTP status code. Can be either an integer or a string if placeholders are needed. Optional. Default is 500.
  • error: The error message. Optional. Default is no error message.


Specifies how to handle errors returned from the HTTP handlers.

If a handler chain returns an error, the error along with its recommended status code are bubbled back to the HTTP server which executes a separate error route, specified using this property. The error routes work exactly like the normal routes.

	"routes": []
  • routes: The routes to execute on error. Works just like normal HTTP routes.

In an error route, extra placeholders are available:

	The recommended HTTP status code
	The status text associated with the recommended status code
	The error message
	The origin of the error
	A short, human-conveyable ID for the error


An ordered list of policies which specify how to complete TLS handshakes when establishing an HTTPS connection. The first matching policy will be used.

Most users will not need this; it is an advanced configuration feature.

	"match": {},
	"alpn": [],
	"cipher_suites": [],
	"certificate_selection": {}
  • match: Configures how to match this policy with a TLS ClientHello. If the policy matches, it will be used.
  • alpn: The ALPN value(s) to set.
  • cipher_suites: The list of cipher suites to support.
  • certificate_selection: Certificate selection module, which configures how to choose a certificate if more than one match the given ServerName (SNI) value.

🏢 Enterprise

Provides advanced certificate selection capabilities, when multiple certificates may satisfy a ClientHello's SNI value.

All fields which are specified will be used to match certificates in the cache. If any specified field does not match the certificate, the certificate will not be chosen to complete the handshake.

	"policy": "enterprise",
	"serial_number": "",
	"subject_organization": "",
	"public_key_algorithm": "",
	"tag": ""
  • serial_number: The certificate's serial number.
  • subject_organization: The X.509 Subject.Organization field.
  • public_key_algorithm: The kind of public key the certificate has.
  • tag: A tag added to the certificate when it was loaded into the cache. Tags may be added in the load_files or load_pem certificate loaders.


Configures automatic HTTPS for this server. Automatic HTTPS consists of:

  • Provisioning certificates
  • Renewing certificates
  • Configuring TLS listeners
  • Redirecting HTTP to HTTPS

Generally you will not need to change these settings; they are for advanced use.

	"disable": false,
	"disable_redirects": false,
	"skip": [],
	"skip_certificates": []
  • disable: If true, automatic HTTPS will be completely disabled.
  • disable_redirects: If true, automatic HTTP->HTTPS redirects will be disabled, but automated certificate management will still be enabled.
  • skip: A list of hosts (domain names) to not include in automatic HTTPS.
  • skip_certificates: A list of hosts (domain names) to still enable automatic HTTPS for, except for managing certificates.


Caddy's TLS app is an immensely powerful way to configure your server's security and privacy policies with regards to network connections. It enables you to load TLS certificates into the cache so they can be used to complete TLS handshakes. You can customize how certificates are managed or automated, and you can even configure how TLS session tickets are handled.

Most users will not need to configure the TLS app at all, since HTTPS is automatic and on by default.

The app is structured like this:

	"certificates": {},
	"automation": {},
	"session_tickets": {}
  • certificates: Caches certificates in memory for quick use during TLS handshakes. Each key is the name of a certificate loader module.
  • automation: Configures certificate automation.
  • session_tickets: Configures session ticket ephemeral keys (STEKs)


Configures how certificates are loaded into memory.

All loaded certificates get pooled into the same cache and may be used to complete TLS handshakes for the relevant server names (SNI). Certificates loaded manually (anything other than "automate") are not automatically managed and will have to be refreshed manually before they expire.

There are several loader modules available.


Loads certificates and their keys from files. Is a list of objects which specify how to load each cert+key pair.

"load_files": [
		"certificate": "",
		"key": "",
		"format": "pem",
		"tags": []
  • certificate: The certificate file.
  • key: The private key file.
  • format: The format of the certificate and key. Values: pem
  • tags: Optionally associate this certificate with tags ( as strings) to keep track of them. Useful for advanced certificate selection.

Load all certificates and keys that can be found in the specified folders. Certificates and key pairs should be bundled in the same .pem files. This is a quick way to load a bunch of certificates at once.

"load_folders": ["/folder1", "folder2"]
  • Simply specify the folder paths in the array.

A special case, this instructs the TLS app to automate certificates for the specified host/domain names. Certificates will be automated according to their matching automation policy.

"automate": ["", ""]
  • Specify the names to automate certificates for in the array.

🏢 Enterprise

Loads certificate and key pairs directly as presented in the config, without needing to access disk. This allows you to securely transmit private keys without having to persist them to storage; you can keep them entirely in memory.

		"certificate": "",
		"key": "",
		"tags": []
  • certificate: The PEM encoding of the certificate.
  • key: The PEM encoding of the private key.
  • tags: Optionally associate this certificate with tags ( as strings) to keep track of them. Useful for advanced certificate selection.


Configures TLS certificate automation.

	"policies": [],
	"on_demand": {
		"rate_limit": {
			"interval": "",
			"burst": 0
		"ask": ""
  • policies: An ordered list of automation policies.
  • on_demand: The configuration On-Demand TLS, when needed. On-Demand TLS defers certificate operations to the time they are needed, e.g. during a TLS handshake. Because it is possible to abuse this feature, usage controls are configurable.
  • on_demand.rate_limit: Configures a rate limit for getting certificates with On-Demand TLS.
  • on_demand.rate_limit.interval: A duration value. A certificate may be obtained burst times during this interval.
  • on_demand.rate_limit.burst: How many times during an interval a certificate can be obtained.
  • on_demand.ask: A URL which will be queried to check if Caddy should be allowed to try to get a certificate for a hostname. The name will be passed in a query string parameter like so: ? The endpoint must return a 200 OK if a certificate is allowed; anything else will cause it to be denied. Redirects are not followed.


Automation policies are a very powerful way to describe how Caddy should manage certificates for certain names.

The first matching policy will be used. Policies are matched by hostname (aka "domain name").

	"hosts": [],
	"management": {}
  • hosts: The list of host names for which this policy should be applied. Omitting this field matches all hostnames. An empty list matches none.
  • management: The module to use for management.

This module uses ACME to manage TLS certificates.

	"module": "acme",
	"ca": "",
	"email": "",
	"key_type": "",
	"acme_timeout": "",
	"must_staple": false,
	"challenges": {
		"http": {
			"disabled": false,
			"alternate_port": 0
		"tls-alpn": {
			"disabled": false,
			"alternate_port": 0
		"dns": {}
	"on_demand": false,
	"storage": {}
  • ca: The ACME CA's directory endpoint.
  • email: Your email address, so the CA can contact you if necessary. Not required but strongly recommended to provide one so you can be reached if there is a problem.
  • key_type: The type of key to generate for the certificate. Supported values: rsa2048, rsa4096, p256, p384
  • acme_timeout: Duration to wait before timing out an ACME operation.
  • must_staple: If true, the certificate will have MustStaple set.
  • challenges: Configures the various ACME challenge types.
  • challenges.http: Configures the ACME HTTP challenge type.
  • challenges.http.disabled: Disables the HTTP challenge.
  • challenges.http.alternate_port: Use this port for the HTTP challenge. Since the ACME spec requires port 80, you must use port forwarding to this alternate port.
  • challenges.tls-alpn: Configures the ACME TLS-ALPN challenge type.
  • challenges.tls-alpn.disabled: Disables the TLS-ALPN challenge.
  • challenges.tls-alpn.alternate_port: Use this port for the TLS-ALPN challenge. Since the ACME spec requires port 443, you must use port forwarding to this alternate port.
  • challenges.dns: Configures the ACME DNS challenge. Doing so disables the other challenge types. This challenge type must be configured using a DNS challenge module.
  • on_demand: If true, certificates will be managed "on demand", that is, during TLS handshakes or when needed, as opposed to at startup.
  • storage: Optionally configure a separate storage module associated with this manager, instead of using Caddy's global/default-configured storage.


Configures Caddy's TLS session tickets. By default, Caddy generates several keys and rotates them automatically on a regular basis, preserving forward secrecy in TLS 1.2. (Caddy is the only web server to do this by default.) You can customize this behavior.

	"disabled": false,
	"max_keys": 4,
	"key_source": {},
	"disable_rotation": false,
	"rotation_interval": "12h"
  • disabled: Disables session ticket keys.
  • max_keys: How many keys to keep in memory.
  • key_source: A STEK provider module that produces keys.
  • disable_rotation: Disables STEK rotation. This is discouraged.
  • rotation_interval: How often to rotate STEKs.

Caddy's standard STEK provider module. It generates cyrptographically-secure keys in memory. There is nothing to configure, and it does not need to be specified in a config.


🏢 Enterprise

A distributed STEK provider module. This allows Caddy to share STEKs among other instances in its cluster (those which use the same storage configuration), increasing performance and reducing connection latency when behind a load balancer.

	"provider": "distributed",
	"storage": {}
  • storage: The storage module to use. All instances in the cluster should use the same storage configuration.
You can’t perform that action at this time.