Upgrade Guide
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. This guide will help you transition as easily as possible.
This guide won’t delve into the new features available — which are really cool, by the way, you should learn them — the goal here is to just get you up and running on Caddy 2 quickly.
Menu
High-order bits
- “Caddy 2” is still just called
caddy
. We may use “Caddy 2” to clarify which version to make the transition less confusing. - Most users will simply need to replace their
caddy
binary and their updatedCaddyfile
config (after testing that it works). - It might be best to go into Caddy 2 with no assumptions carried over from Caddy 1.
- You might not be able to perfectly replicate your niche v1 configuration in v2. Usually, there’s a good reason for that.
- The command line is no longer used for server configuration.
- Environment variables are no longer needed for configuration.
- The primary way to give Caddy 2 its configuration is through its API, but the
caddy
command can also be used. - You should know that Caddy 2’s native configuration language is JSON, and the Caddyfile is just another config adapter that converts to JSON for you. Extremely custom/advanced use cases may require JSON, as not every possible configuration can be expressed by the Caddyfile.
- The Caddyfile is mostly the same, but also much more powerful; directives have changed.
Steps
- Get familiar with Caddy 2 by doing our Getting Started tutorial.
- Do step 1 if you haven’t yet. Seriously — we can’t stress how important it is to at least know how to use Caddy 2. (It’s more fun!)
- Use the guide below to transition your
caddy
command(s). - Use the guide below to transition your Caddyfile.
- Test your new config locally or in staging.
- Test, test, test again
- Deploy and have fun!
HTTPS and ports
Caddy’s default port is no longer :2015
. Caddy 2’s default port is :443
or, if no hostname/IP is known, port :80
. You can always customize the ports in your config.
Caddy 2’s default protocol is always HTTPS if a hostname or IP is known. This is different from Caddy 1, where only public-looking domains used HTTPS by default. Now, every site uses HTTPS (unless you disable it by explicitly specifying port :80
or http://
).
IP addresses and localhost domains will be issued certificates from a locally-trusted, embedded CA. All other domains will use Let’s Encrypt. (This is all configurable.)
The storage structure of certificates and ACME resources has changed. Caddy 2 will probably obtain new certificates for your sites; but if you have a lot of certificates you can migrate them manually if it does not do it for you. See issues #2955 and #3124 for details.
Command line
The caddy
command is now caddy run
.
All command line flags are different. Remove them; all server config now exists within the actual config document (usually Caddyfile or JSON). You will probably find what you need in the JSON structure or in the Caddyfile global options to replace most of the command line flags from v1.
A command like caddy -conf ../Caddyfile
would become caddy run --config ../Caddyfile
.
As before, if your Caddyfile is in the current folder, Caddy will find and use it automatically; you don’t need to use the --config
flag in that case.
Signals are mostly the same, except USR1 and USR2 are no longer supported. Use the caddy reload
command or the API instead to load new configuration.
Running caddy
without any config used to run a simple file server. The equivalent in Caddy 2 is caddy file-server
.
Environment variables are no longer relevant, except for HOME
(and, optionally, any XDG_*
variables you set). The CADDYPATH
is replaced by OS conventions.
Caddyfile
The v2 Caddyfile is very similar to what you’re already familiar with. The main thing you’ll need to do is change your directives.
⚠️ Be sure to read into the new directives! Especially if your config is more advanced, there are many nuances to consider. These tips will get you mostly switched over pretty quickly, but please read the full documentation for each directive so you can understand the implications of the upgrade. And of course, always test your configs thoroughly before putting them into production.
Primary changes
If you are serving static files, you will need to add a
file_server
directive, since Caddy 2 does not assume this by default. Caddy 2 does not sniff MIME by default, either, for security reasons; if a Content-Type is missing you may need to set the header yourself using the header directive.In v1, you could only filter (or “match”) directives by request path. In v2, request matching is much more powerful. Any v2 directives which add a middleware to the HTTP handler chain or which manipulate the HTTP request/response in any way take advantage of this new matching functionality. Read more about v2 request matchers. You’ll need to know about them to make sense of the v2 Caddyfile.
Although many placeholders are the same, many have changed, and there are now many new ones, including shorthands for the Caddyfile.
Caddy 2 logs are all structured, and the default format is JSON. All log levels can simply go to the same log to be processed (but you can customize this if needed).
Where you matched requests by path prefix in Caddy 1, path matching is now exact by default in Caddy 2. If you want to match a prefix like
/foo/
, you’ll need/foo/*
in Caddy 2.
We’ll list some of the most common v1 directives here and describe how to convert them for use in the v2 Caddyfile.
⚠️ Just because a v1 directive is missing from this page does not mean v2 can’t do it! Some v1 directives aren’t needed, don’t translate well, or are fulfilled other ways in v2. For some advanced customization, you may need to drop down to the JSON to get what you want. Explore our documentation to find what you need!
basicauth
HTTP Basic Authentication is still configured with the basicauth
directive. However, Caddy 2 configuration does not accept plaintext passwords. You must hash them, which the caddy hash-password
can help with.
- v1:
basicauth /secret/ Bob hiccup
- v2:
basicauth /secret/* {
Bob JDJhJDEwJEVCNmdaNEg2Ti5iejRMYkF3MFZhZ3VtV3E1SzBWZEZ5Q3VWc0tzOEJwZE9TaFlZdEVkZDhX
}
browse
File browsing is now enabled through the file_server
directive.
- v1:
browse /subfolder/
- v2:
file_server /subfolder/* browse
errors
Custom error pages can be accomplished with handle_errors
.
- v1::
errors {
404 404.html
500 500.html
}
- v2::
handle_errors {
rewrite * /{http.error.status_code}.html
file_server
}
ext
Implied file extensions can be done with try_files
.
- v1:
ext .html
- v2:
try_files {path}.html {path}
fastcgi
Assuming you’re serving PHP, the v2 equivalent is php_fastcgi
.
- v1:
fastcgi / localhost:9005 php
- v2:
php_fastcgi localhost:9005
Note that the fastcgi
directive from v1 did a lot under the hood, including trying files on disk, rewriting requests, and even redirecting. The v2 php_fastcgi
directive also does these things for you, but the docs give its expanded form that you can modify if your requirements are different.
There is no php
preset needed in v2, since the php_fastcgi
directive assumes PHP by default. A line such as php_fastcgi 127.0.0.1:9000 php
will cause the reverse proxy to think that there is a second backend called php
, leading to connection errors.
The subdirectives are different in v2 — you probably will not need any for PHP.
gzip
A single directive encode
is now used for all response encodings, including multiple compression formats.
- v1:
gzip
- v2:
encode gzip
Fun fact: Caddy 2 also supports zstd
(but no browsers do yet).
header
Mostly unchanged, but now way more powerful since it can do substring replacements in v2.
- v1:
header / Strict-Transport-Security max-age=31536000;
- v2:
header Strict-Transport-Security max-age=31536000;
log
Enables access logging; the log
directive can still be used in v2, but all logs are structured, encoded as JSON, by default.
The recommended way to enable access logging is simply:
log
which emits structured logs to stderr. (You can also emit to a file or network socket; see docs.)
Although we recommend everyone use structured logging, you can still write Common Log Format (CLF) to a file, if you must:
- v1:
log access.log
- v2:
log {
output file access.log
format single_field common_log
}
But we recommend this only for transitioning while your legacy systems still require CLF.
proxy
The v2 equivalent is reverse_proxy
.
Notable subdirective changes are header_upstream
and header_downstream
have become header_up
and header_down
, respectively; and load-balancing-related subdirectives are prefixed with lb_
.
One other significant difference is that the v2 proxy passes all incoming headers thru by default (including the Host
header) and sets the X-Forwarded-For
header. In other words, v1’s “transparent” mode is basically the default in v2 (but if you need other headers like X-Real-IP you have to set those yourself). You can still override/customize the Host
header using the header_up
subdirective.
Websocket proxying “just works” in v2; there is no need to “enable” websockets like in v1.
The without
subdirective has been removed because rewrite hacks are no longer necessary in v2 thanks to improved matcher support.
- v1:
proxy / localhost:9005
- v2:
reverse_proxy localhost:9005
redir
Unchanged, except for a few details about the optional status code argument. Most configs won’t need to make any changes.
- v1:
redir https://example.com{uri}
- v2:
redir https://example.com{uri}
rewrite
The semantics of request rewriting (“internal redirecting”) has changed slightly. If you used a so-called “rewrite hack” in v1 as a way to match requests on something other than a simple path prefix, that is completely unnecessary in v2.
The new rewrite
directive is very simple but very powerful, as most of its complexity is handled by matchers in v2:
- v1:
rewrite {
if {>User-Agent} has mobile
to /mobile{uri}
}
- v2:
@mobile {
header User-Agent *mobile*
}
rewrite @mobile /mobile{uri}
Notice how we simply use Caddy 2’s usual matcher tokens; it’s no longer a special case for this directive.
Start by removing all rewrite hacks; turn them into named matchers instead. Evaluate each v1 rewrite
to see if it’s really needed in v2. Hint: A v1 Caddyfile that uses rewrite
to add a path prefix and then proxy
with without
to remove that same prefix is a rewrite hack, and can be eliminated.
You may find the new route
and handle
directives useful for having greater control over advanced routing logic.
root
Unchanged, but if your root path starts with /
, you’ll need to add a *
matcher token to distinguish it from a path matcher.
- v1:
root /var/www
- v2:
root * /var/www
Because it accepts a matcher in v2, this means you can also change the site root depending on the request.
status
The v2 equivalent is respond
, which can also write a response body.
- v1:
status 404 /secrets/
- v2:
respond /secrets/* 404
templates
The overall syntax of the templates
directive is unchanged, but the actual template actions/functions are different and much improved. For example, templates are capable of including files, rendering markdown, making internal sub-requests, parsing front matter, and more!
See the docs for details about the new functions.
- v1:
templates
- v2:
templates
tls
The fundamentals of the tls
directive have not changed, for example specifying your own cert and key:
- v1:
tls cert.pem key.pem
- v2:
tls cert.pem key.pem
But Caddy’s auto-HTTPS logic has changed, so be aware of that!
The cipher suite names have also changed.
A common configuration in Caddy 2 is to use tls internal
to have it serve a locally-trusted certificate for a dev hostname that isn’t localhost
or an IP address.
Most sites will not need this directive at all.
Service files
We recommend using one of our official service files for Caddy deployments.
If you need a custom service file, base it off of ours. It has been carefully tuned to what it is for good reasons! Be sure to customize yours if needed.
See install instructions for details.
Plugins
Plugins written for v1 are not automatically compatible with v2. Many v1 plugins are not even needed in v2. On the other hand, v2 is way more easily extensible and flexible than v1!
If you want to write a plugin for Caddy 2, learn how to write a Caddy module.
Building Caddy 2 with plugins
Caddy 2 does not (yet) have a public build server and interactive download page like v1 did. We’re working on it. In the meantime, our builder tool may be helpful. It simply automates the instructions in Caddy’s main.go file.
We’ll also be working on the new website some more so that plugins can be registered and indexed and easily found.
Getting help
If you’re struggling to get Caddy working, please take a look through our website for documentation first. Take time to try new things and understand what is going on - v2 is very different from v1 in a lot of ways (but it’s also very familiar)!
If you still need assistance, please be a part of our community! You may find that helping others is the best way to help yourself, too.