Python
Simplifying Web Services with Caddy
Running multiple web services doesn't have to be complicated. Here's how Caddy makes it simple by handling reverse proxying and HTTPS certificates automatically, plus a script I use to set up new services with a single command.
After my recent posts about We2.ee, Lone.Earth, Earth.Law and that pump calculator project, several folks asked about managing multiple websites without it becoming a huge time sink. The secret isn't complicated—it's a neat tool called Caddy that handles most of the tedious parts automatically.
Understanding Reverse Proxies
Traditionally, web servers were designed with a simple model: one server running a single service on ports 80 (HTTP) and—more recently as infosec awareness increased—443 (HTTPS). This made sense when most organizations ran just one website or application per server. The web server would directly handle incoming requests and serve the content.
But this model doesn't work well for self-hosting. Most of us want to run multiple services on a single machine - maybe a blog like this, a chat service like We2.ee, and a few microservices like that pump calculator. We can't dedicate an entire server to each service—that would be wasteful and expensive—and we can't run them all on ports 80/443 (only one service can use a port at a time).
This is where reverse proxies come in. They act as a traffic director for your web server. Instead of services competing for ports 80 and 443, each service runs on its own port, and the reverse proxy directs traffic for
- A blog to port 2368
- A Mastodon instance to port 3000
- An uptime tracking service to port 3001
- A code hub to port 3003
- An encrypted chat service to port 8448
- DNS-over-HTTPS filter and resolver on 8502
- A Peertube instance to port 9000
- An LLM API to port 11434
- ... etc.
When someone visits any of your websites, the reverse proxy looks at which domain they're trying to reach and routes them to the right service. That's really all there is to it—it's just routing traffic based on the requested domain.
Why Caddy Makes Life Easier
Caddy is a reverse proxy that manages this well and also happens to take care of one of the biggest headaches in web hosting: HTTPS certificates. Here's what my actual Caddy config looks like for this blog:
sij.law {
reverse_proxy localhost:2368
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}
This simple config tells Caddy to:
- Send all traffic for sij.law to the blog running on port 2368
- Automatically get HTTPS certificates from Let's Encrypt
- Renew those certificates before they expire
- Handle all the TLS/SSL security settings
If you've ever dealt with manual certificate management or complex web server configurations, you'll appreciate how much work these few lines are saving.
Making Domain Setup Even Easier
To streamline things further, I wrote a script that automates the whole domain setup process. When I was ready to launch that pump calculator I mentioned in my last post on the open web, I just ran:
cf pumpcalc.sij.ai --port 8901 --ip 100.64.64.11
One command and done—cf creates the DNS record on Cloudflare and points it to the IP of the server running Caddy, creates a Caddy configuration that reverse proxies pumpcalc.sij.ai to port 8901 on my testbench server (which has the Tailscale IP address 100.64.64.11
), and handles the HTTPS certification.
If you want to try this script out yourself, see the more detailed documentation at sij.ai/sij/cf, and by all means have a look at the Python code and see how it works under the hood.
Getting Started
- Start by installing Caddy on your server
- Create a config for just one website
- Let Caddy handle your HTTPS certificates
- Add more sites when you're ready
Start small, get comfortable with how it works, and expand when you need to. Ready to dig deeper? The Caddy documentation is excellent, or feel free to reach out with questions.
Thinking Like a Developer, Pt. 1
When our backup pump failed on the homestead, I built a calculator to figure out what we really needed. It’s a small, open-source tool born from necessity and a few iterations with self-hosted AI.
I live on a homestead in southern Oregon, surrounded by the vastness of the Umpqua National Forest in every direction. It’s the kind of place where nature dictates the rhythm of life. We rely on a natural mountain spring for water most days, but when that fails (as nature sometimes does), we turn to a small stream or a mile-long ditch. According to local lore, some intrepid homesteader dug that ditch in the early 1900s to water a single cow.
These water sources connect to a network of pipes, backup pumps, and an unoptimized system that could generously be described as "inventive." When our pump failed recently, we faced an immediate and critical question: how powerful does a replacement pump need to be?
From Problem to Solution
To answer that question, I did what any coder-lawyer-homesteader would do—I wrote a script. Specifically, a pump power calculator that factors in pipe diameter, distance, flow rate, pipe material, and other inputs to calculate the horsepower needed for a given setup. It factors in key considerations like friction head loss, flow velocity, and static head, ultimately providing recommendations with a built-in safety margin. For example, in our setup, with a 4000-foot run of 1" pipe that rises up around 120 feet and delivers around 7.5 gallons per minute, it calculated we needed at least 0.75 HP—but 1 HP if we want a 30% safety margin.
You can try it out for yourself at pumpcalc.sij.ai or embedded at the bottom of this post, and if you’re curious about the code, it’s open source at sij.ai/sij/pumpcalc. I built the calculator in Python using FastAPI for the backend and Jinja2 for templating—simple, reliable tools that get the job done without unnecessary complexity.
This wasn’t a solo endeavor. I leaned on the open-source AI tool Ollama and specifically QwQ, a powerful 32 billion parameter research model that rivals leading commercial AI systems like ChatGPT o1 in reasoning capabilities. QwQ particularly excels at technical problem-solving and mathematical tasks, making it perfect for engineering calculations like this.
The Iterative Process of Coding
Developing this script wasn’t a one-and-done affair. It took five back-and-forth sessions with the AI to:
- Factor in relevant variables like pipe roughness and flow rate.
- Exclude unnecessary inputs that made the interface clunky.
- Add some polish, like the Gruvbox Dark color scheme that now graces the app.
Each iteration made the calculator more useful and user-friendly. By the end, I had something functional, simple, and—dare I say—elegant.
Why Share This?
I’m sharing this as the first in a series of "Thinking Like a Developer" stories, because I believe coding isn’t as mystifying as it might seem. If a lawyer on a homestead with a temperamental water system can write a pump calculator, anyone can. The key is thinking like a developer: break the problem into smaller, solvable pieces, and don’t be afraid to consult tools or collaborators along the way.
This approach to problem-solving—breaking down complex challenges and leveraging coding tools—mirrors how I approach legal technology challenges. I frequently rely on Python and AI libraries to streamline legal work, from document analysis to case law research. Whether it's calculating pump requirements or processing legal documents, the fundamental thinking process remains the same. Who knows? You might find your next project hidden in a problem you didn’t even know you wanted to solve.