Didn’t I say that this was a production server? I’m only lying to myself.
I’ve been using Nginx Proxy Manager for a while now and it has a few quirks. Perhaps my problems could have been solved by using custom Nginx configurations and reading documentation, but I’ve been hearing good things about Caddy for a while now and I thought that it would be a good time to see if I could migrate my existing setup. How hard could it be?
THE GOAL
Security has been at the back of my mind recently and Docker opens ports rather willy-nilly. Currently, much of the traffic itself is routed through Cloudflare (ick) which means that a direct scan of the website itself will not reveal any open ports beyond the standard 80 & 443. Normally this is good enough and nobody will see the original IP, but WordPress is particularly egregious in that it doesn’t care when or how articles are published and reveals your IP in plain text when the site is initially set up. I have no intention of getting pwned if (when) the next WordPress CVE is exposed. It would be best if I restricted the attack surface of this website even further – previously, it was possible to extract the server IP simply by following some links on WordPress, scan for open ports, and interface with the WordPress application directly. I would much rather close all these ports and force any connection to be made through a reverse proxy.
THE CONFIG
Previously, NPM was initialized with Docker alongside WordPress and other containerized services, which makes it resilient to outside changes. Unfortunately, this makes networking and closing external port access significantly more difficult. For NPM to see the other containers, it needs to either be part of the same Docker network (which is takes away from the convenience of being able to kill and resurrect different parts of this website on a whim) or running on host (which defeats half the point of it being in Docker) or go through convoluted bridge networking (which I no longer have the patience for).
THE CHANGES
So, I took the plunge to switch over to Caddy. What is the first rule of making changes? Make backups! If I somehow bricked this website though some crazy misconfiguration or through complete ineptitude, I have a feasible way to nuke the VPS and regenerate a working server from scratch.
Downloading and installing Caddy was quite straightforward, add the keyrings and the repository, update, and install. We kill the NPM container and unbind ports 80 & 443 so that Caddy has priority, make a simple Caddyfile for our configuration, and… whoops.
The entire site is now dead in the water – too many redirects or an incorrect SSL termination. What happened? Caddy’s claim to fame is the fully automated SSL activation – which failed the ACME challenge because the original server is behind a proxy. I added a Caddy module called “dns.providers.cloudflare” to help with this, which uses a Cloudflare API token to complete a DNS challenge. The documentation suggests installing Go, xcaddy and manually building the application with the needed modules yourself, but if you only need a single module, the “caddy add-package” command is the easier option.
All that was left was to prepend variations of “localhost:” to all my Docker container ports and write the reverse proxy configurations. Everything is stable with minimal effort, notifications and pings just work without the need to manually configure web-hooks or forward transfers. I gave up on running Caddy within Docker and decided to deal with internal bridge networking later.
This whole experience of writing a glorified config file has convinced me that I will eventually see the light and decide that production means disabling server-side code all together and serving a mile long static HTML page like my CS professors. There is something to be said about bell curves… later.