Switching from nginx to caddy
21 March, 2025 - Categories: homelab
Yesterday I finally switched my reverse proxy for my homeloab from nginx to caddy.
Nginx has worked well for many years, once I figured out how to configure the reverse proxy stuff it was easy enough to add more services and point them at the right virtual machine or later container.
However, the configuration was always a bit annoying to deal with. Especially with certbot in the mix for https, my process looked like this:
- ssh into the webserver vm
- sudo -i to become root
- copy a template configuration containing the basics of reverse proxying
- chage the url and ip/port values
- symlink it from sites-available to sites-enabled (this is debian's fault, nginx doesn't usually need this and i think it's dumb)
- nginx -t to test the config
- restart nginx if i didn't make any mistakes
- run a massive certbot command that extends my https certificate with the new subdomain (might paste it here later)
- restart nginx again to serve the new certificate (don't forget to do automatic restarts of nginx so automatic cert renewals work too)
i kept meaning to set up an ansible playbook for this but... it doesn't happen quite often enough to make it worthwhile, especially as a bunch of services required special little additions to the config to actually get them to work. i also never decided on a single source of truth for my homelab...
Anyway, now with caddy it's much simpler:
- ssh into the server caddy runs on (ssh as root is enabled... no password login though so i consider it safe enough)
- add a tiny little 2 line config to the caddyfile
- caddy validate
- restart caddy
that's it. https works out of the box (if dns is set up properly, but adding a dns entry for a subdomain is the same in both setups) and the actual configuration is WAY smaller.
In fact, when changing the server initially, i copied some of the configuration over for setting certain headers and handling websockets, mainly for Jitsi Meet, and that broke things. Removing the extra config and simply using the reverse_proxy directive in the caddyfile seems to have made everything work perfectly fine.
in fact, here's some examples. the certbot command (still containing some unused domains):
NOTE: sorry formatting will be fixed soon
certbot certonly --webroot -w /var/www/html -d shinymail.ch,shiny.space,meet.shiny.space,forge.shiny.space,trilium.shiny.space,caldav.shiny.space,wiki.shiny.space,pics.shiny.space,photos.shiny.space,nocobase.shiny.space,rocketchat.shiny.space,play.shiny.space,jellyfin.shiny.space,grafana.shiny.space,api.shiny.space,blog.shiny.space,www.shiny.space --expand
fun and readable, right? i'm sure making a script or putting it in a config file or whatever would be better, but... yeah, with caddy you just don't even have to think about it.
here's a snippet for the old blog:
server { listen 443 ssl;
server_name blog.shiny.space;
location /.well-known {
root /var/www/html;
try_files $uri $uri/ = 404;
}
location / {
proxy_pass http://10.0.0.30:57420;
}
}
the well-known stuff is necessary to make certbot work - ssl/tls is terminated at the reverse proxy, so the requests for these files should not be proxied.
here's the config for the blog you're reading now (as of 2025):
blog.shiny.space { reverse_proxy 10.0.0.30:57420 }
yes. that's it. let's look at jitsi meet:
´´´server { listen 443 ssl;
server_name meet.shiny.space;
client_max_body_size 500M;
location /.well-known {
root /var/www/html;
try_files $uri $uri/ = 404;
}
location /xmpp-websocket {
proxy_pass http://10.0.0.30:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /colibri-ws {
proxy_pass http://10.0.0.30:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /http-bind {
proxy_pass http://10.0.0.30:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass http://10.0.0.30:8000;
}
} ´´´
and here's caddy's version:
meet.shiny.space { request_body { max_size 500MB }
reverse_proxy 10.0.0.30:8000
}