Switching from nginx to caddy

a few notes on how to switch from nginx to caddy

update 2022-10-30:

A few days ago i decided to look into some new webservers that i stumble over from time to time, basically just to check out what they are about.
Namely, there were traefik and caddy.
And while traefik seems to be more like an “automagic” load balancer for kubernetes and other container management systems (which is currently not that interesting for me), caddy is more like a webserver for the letsencrypt-age:
It has really interesting features, like automatic ssl certificates, sane default configs, a configuration api, and even included markdown templating!

Its definitely worth giving it a try.
Whats following is a writeup of what i needed to do to convert my nginx setup to caddy.

The Caddyfile

Caddy is configured either via the api, or with a Caddyfile. Basically, you either put it in you working folder and run caddy, or, if you installed it with you package manager and it automatically starts as a systemd service, it should be at /etc/caddy/Caddyfile.

A simple Caddyfile could look like

example.com {
	respond "Hello, world!"

If you start (or systemctl reload caddy), this would automatically try to get a certificate for example.com, and start listening on port 80 and 443 - forwarding von http to https included.

Serving static files

The first thing i tried to migrate was static file hosting, the kind of thing you would want to have for a static website.

My nginx config looked somewhat like this:

server {
    listen [::]:80 ;
    listen 80 ;
    server_name example.com;
    include /etc/nginx/snippets/letsencrypt.conf;
    return 302 https://example.com$request_uri;

server {
    listen [::]:443 ssl;
    listen 443 ssl;
    server_name example.com;
    include /etc/nginx/snippets/letsencrypt.conf;
    # certs
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    location / {
        alias /var/www/html/;

In nginx this is not really trivial. You have to setup the server on port 80 first, to get a certificate, and then, when you have the cert files, reconfigure and set up the ssl part and forwarding to https.

For the same functionality in caddy, you Caddyfile would look like this:

example.com {
  root * /var/www/html/

And thats it. This gets a certificate for the domain, and starts a webserver that forwards to https.

Reverse proxy

A very similar setup is a reverse proxy. I spare you some scrolling and skip the nginx config for this, you get the idea.

example.com {
  reverse_proxy localhost:8080


Logging is pretty nice as well. Its not less typing than what you have in nginx, i guess, but instead of nginx combined logging format, caddy defaults to json.

To configure logging for the example above, you could do something like

example.com {
  log {
    output file /var/log/caddy/example.com.access.log
  reverse_proxy localhost:8080

This is completely configurable though.

I skip an example log here, but you can find an example in the documentation


In nginx, to set up htpasswd access for a website, you had to generate your htpasswd file, and setup basic auth:

auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;

In caddy, the approach is a little bit different. Instead of a htaccess file, you configure you users and the passwords directly in the Caddyfile.

example.com {
  basicauth * {
    some_username "$2a$14$rzRlAB5qcjtyAi[...]"
  root * /var/www/html/

The password hash is a bcrypt hash, generated with caddy itself: caddy hash-password. This will prompt for the password, and return the hash.

You can also supply the password via stdin:

> echo "123" | caddy hash-password

Certificate update hooks

Another thing that i needed was access to the certificates, some kind of event hook on cert renewal.

In the old nginx setup, i got my certificates with certbot, which has an easy way to do this. On cert creation, you can add a hook:

certbot certonly [...] -d example.com \
--post-hook 'echo "got new cert!"'

For caddy, it turns out, there is an experimental events module that looks is able to do that.

I ended up replacing the thing which needed the cert for, but if you want to dig into it, there is a pull request with some example code!

update: there is a follow-up post on this topic!

Organizing config files

As an “overall setup” i wanted something similar to the sites-enabled folder that nginx uses.

I just configured a wildcard include in the Caddyfile, importing all config files in a specific path, so whenever i want a new site, i just add it to sites-enabled and reload caddy. Same feeling as in nginx, but less overhead.

include /etc/caddy/sites-enabled/*
Last modified 2022.10.01