Turning a Raspberry Pi into a DIY Caddy Reverse Proxy for Home‑Hosted Services
I’ve run a handful of services on a Pi for years—Plex, Nextcloud, Home Assistant—so the idea of putting a single, tidy entry point on top of them is pretty appealing. Caddy is the go‑to for this because it hands you a working HTTPS server, auto‑issues Let’s Encrypt certs, and runs nicely on ARM. Below is a hands‑on walk‑through that keeps all the commands and links intact, but reads like a coffee‑chat with a fellow sysadmin.
Why bother with a reverse proxy?
- One hostname – instead of juggling
http://192.168.1.5:32400,http://192.168.1.5:8080, etc., you hithttps://home.example.comand let Caddy route the rest. - HTTPS everywhere – Caddy pulls certificates for you, so you never have to remember to renew or configure
certbot. - Future‑proof – if you ever spin up a second Plex or add a new service, you just tweak the Caddyfile.
- Centralised logs – one place to see who’s hitting what.
If you’re already deep into Nginx or Traefik, Caddy is a lighter alternative that still covers the essentials.
1. Prepare the Pi
1.1 Update the system
sudo apt update && sudo apt upgrade -y
1.2 Install required packages
Caddy only needs curl and systemd, but I’ll pull in git for later steps.
sudo apt install -y curl git
1.3 Create a dedicated user
Running everything as root is a no‑no. A separate caddy user keeps the attack surface tight.
sudo useradd -r -s /usr/sbin/nologin caddy
sudo mkdir /etc/caddy
sudo chown caddy:caddy /etc/caddy
2. Install Caddy
Grab the ARM build straight from the official download endpoint.
curl -sL https://caddyserver.com/download/arm64 | sudo tee /usr/local/bin/caddy > /dev/null
sudo chmod +x /usr/local/bin/caddy
Quick sanity check:
caddy version
You should see something like caddy 2.7.4.
3. Configure Caddy
Caddy’s Caddyfile lives in /etc/caddy/Caddyfile. Open it up:
sudo nano /etc/caddy/Caddyfile
Add a minimal skeleton:
{
# Global options
email [email protected]
acme_ca https://acme-v02.api.letsencrypt.org/directory
}
home.example.com {
reverse_proxy /plex 127.0.0.1:32400
reverse_proxy /nextcloud 127.0.0.1:8080
reverse_proxy /homeassistant 127.0.0.1:8123
}
3.1 Path‑based routing
The reverse_proxy /plex line tells Caddy to forward any request that starts with /plex to the local Plex instance. If you prefer subdomains, just replace the prefix with a domain:
plex.home.example.com {
reverse_proxy 127.0.0.1:32400
}
3.2 TLS and automatic renewal
Caddy will automatically fetch and renew certs. Make sure your DNS A record points to the Pi’s public IP (or a dynamic DNS provider). The email field is where renewal notices land.
4. Set up a systemd unit
Create /etc/systemd/system/caddy.service:
[Unit]
Description=Caddy HTTP/2 web server
After=network.target
[Service]
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile
KillMode=mixed
Restart=on-failure
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
Reload systemd and enable the service:
sudo systemctl daemon-reload
sudo systemctl enable --now caddy
Check that it’s running:
sudo systemctl status caddy
You should see “active (running)”.
5. Firewall considerations
If you’re using ufw, allow only the ports Caddy needs:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
If you expose other services directly, add those rules too.
6. Test the setup
Open a browser and hit:
https://home.example.com/plex– should bring up Plex’s web UI.https://home.example.com/nextcloud– should show the Nextcloud login page.https://home.example.com/homeassistant– should load Home Assistant.
If something’s off, tail the logs:
journalctl -u caddy -f
Common hiccups:
| Symptom | Likely cause | Fix |
|---|---|---|
| 502 Bad Gateway | Backend not listening on the port you told Caddy | Verify the service is running (`sudo netstat -tulpn |
| 404 Not Found | Path mismatch | Make sure the URL matches the reverse_proxy prefix |
| TLS error | DNS not pointing to the Pi | Update the record or wait for propagation |
7. Advanced tweaks
7.1 Rate limiting
If you want to guard against abuse, add a simple limiter:
home.example.com {
@plex {
path /plex
}
route @plex {
limit_req zone=plex burst=10 nodelay
reverse_proxy 127.0.0.1:32400
}
}
7.2 Basic authentication
For a quick password guard:
home.example.com {
reverse_proxy /homeassistant 127.0.0.1:8123 {
basicauth {
admin JDJhJDEwJH...
}
}
}
Generate the hash with htpasswd -nbB admin.
7.3 Logging format
If you prefer JSON logs for easier parsing:
log {
output file /var/log/caddy/access.log {
roll_size 10MiB
roll_keep 5
roll_keep_for 72h
}
format json
}
8. Security hardening
| Measure | Why it matters | How to enable |
|---|---|---|
| Disable unused services | Shrinks the attack surface | sudo systemctl disable <service> |
| Use strong passwords | Stops brute‑force | passwd for local accounts |
| Keep Caddy updated | Fixes vulnerabilities | sudo apt update && sudo apt upgrade |
| Monitor logs | Spot anomalies early | journalctl -u caddy -f or ship to syslog |
Caddy’s TLS handling already takes care of many web‑app pitfalls, but keep the Pi’s OS up to date.
9. Troubleshooting checklist
- Caddy not starting –
journalctl -u caddy -xe; common culprits: missingCaddyfile, wrong permissions, or thecaddyuser not existing. - Certificate not issued – Verify DNS, ensure ports 80/443 are reachable, and check Let’s Encrypt rate limits.
- Service unreachable – Double‑check the backend port and that it accepts connections from
127.0.0.1. - Slow responses – Look for timeouts in the logs; add
header_upstreamif the backend needs the client IP.
10. Scaling beyond a single Pi
If you later add more Pis or a small server cluster, Caddy can load‑balance:
home.example.com {
reverse_proxy /plex {
to 192.168.1.10:32400 192.168.1.11:32400
lb_policy round_robin
}
}
Add a health check (health_path /health) to skip nodes that are down.
11. Final thoughts
Putting Caddy on a Raspberry Pi gives you a clean, secure, and low‑power gateway to all your home‑hosted services. The configuration is straightforward, TLS is handled automatically, and the small footprint leaves room for other tasks. With the steps above, you’ll have a hardened reverse proxy up and running in under an hour—no fancy tooling required.
See also
- Why My Custom Systemd Timer Keeps Firing Twice and How to Fix It
- Using find + xargs to purge old log files without risking accidental deletes
- Why I Swapped Traefik for Nginx Ingress in My Home Reverse‑Proxy Stack.
- Why I Switched from Nginx to Caddy for My Home Reverse Proxy and What I Learned
- When Nginx Reverse‑Proxy Returns 502 on Your Home‑Lab HTTPS Site: A Step‑by‑Step Fix for Certificate and Path Errors