When exposing web applications, Docker containers, or internal services to the public internet, you need a reverse proxy. While Nginx is the legacy powerhouse, Caddy has become the modern standard due to its built-in, fully automated HTTPS and incredibly readable configuration files.
This guide will walk you through a proper installation, a basic setup that is more than enough for most solo developers, and an advanced modular setup designed for multi-user server environments.
Phase 1: The Proper Installation
Do not use the default Ubuntu package manager (apt) to install Caddy right away. The default repositories often host significantly outdated versions. Instead, we will add Caddy's official Cloudsmith repository to ensure we get the latest stable release with current security patches.
1. Install prerequisite tools:
sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
2. Download and add the official Caddy GPG keys:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
3. Add the Caddy repository to your system:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
4. Install Caddy:
sudo apt update
sudo apt install caddy
Phase 2: The Basic Setup (Enough for Most Users)
If you are running a single server with just one or two applications (like a personal portfolio and a single backend API), this basic setup is completely sufficient. You do not need to overcomplicate your architecture.
Caddy's entire routing logic lives in a single file called the Caddyfile.
1. Open the Caddyfile for editing:
sudo nano /etc/caddy/Caddyfile
2. Replace the default text with your routing blocks:
Delete whatever is in there and write your site configuration. Here is an example of routing a domain to a Next.js app running locally on port 3000:
yourdomain.com {
reverse_proxy 127.0.0.1:3000
}
api.yourdomain.com {
reverse_proxy 127.0.0.1:8080
}
3. Save the file and reload Caddy:
Press CTRL + O, then Enter to save, and CTRL + X to exit Nano. Then apply the changes:
sudo systemctl reload caddy
Caddy will instantly request SSL certificates for your domains and securely route your traffic. If you are a solo developer with a simple setup, stop here. You are done.
Phase 3: The Modular conf.d Setup
If you are hosting dozens of services, running a complex homelab, or collaborating with a team, cramming everything into one massive Caddyfile becomes messy and dangerous. A single typo could take down every site on the server.
Instead, we split our routes into a modular directory architecture.
1. Create a directory for individual configurations:
sudo mkdir -p /etc/caddy/conf.d
2. Edit the main Caddyfile to act as an importer:
sudo nano /etc/caddy/Caddyfile
Replace the contents with this exact structure:
{
# Global administrative settings can go here
# email admin@yourdomain.com
}
# Import all individual site configurations
import conf.d/*.caddy
Save and exit.
3. Create dedicated files for each service:
Now, whenever you add a new app, give it its own file. For example, to add code-server:
sudo nano /etc/caddy/conf.d/code-server.caddy
code.yourdomain.com {
reverse_proxy 127.0.0.1:8080
}
Reload Caddy (sudo systemctl reload caddy). If you ever make a mistake in code-server.caddy, it won't break your other applications.
Phase 4: Scoped User Permissions (Zero-Downtime Reloads)
In a multi-user environment, you might have standard user accounts (like a CodexSandboxUsers group or a secondary developer account) that frequently deploy new apps. They need the ability to reload Caddy to apply their new .caddy files, but giving them full sudo access to the server is a massive security risk.
We can grant a specific user or group the exact permission to reload Caddy—and nothing else.
1. Create a dedicated sudoers rule:
[!info] Why visudo?
We usevisudoinstead ofnanohere because if you make a syntax error in a sudoers file, you can permanently lock yourself out of administrative access.visudochecks your work before saving.
sudo visudo -f /etc/sudoers.d/caddy-reload
2. Add the scoped privilege rule:
Add this line to allow the standard ubuntu user to reload Caddy without a password (adjust the username as needed for your specific setup):
ubuntu ALL=(root) NOPASSWD: /usr/bin/systemctl reload caddy
Save and exit (CTRL + O, Enter, CTRL + X).
Now, when your standard user deploys a new application, they can simply type sudo systemctl reload caddy. It will execute flawlessly without prompting for a password, and without giving them the keys to your entire infrastructure.
Top comments (0)