Caddy for private network
Browsers use HTTP (port 80), an unencrypted connection exposing data, or HTTPS (port 443), which provides end-to-end encryption. HTTPS packets are unreadable in transit, securing sensitive data like passwords typed into forms. However, verifying the server's identity remains vital to ensure connection to the legitimate site (like your bank) and not a fraudulent one.
Let's Encrypt offers free, DNS-verified certificates. Caddy simplifies their automatic 90-day renewal. Typically using the port 80 HTTP-01 challenge, this method is unsuitable for private IPs. The alternative DNS challenge uses your registrar's API to set verification records. Caddy can meet these challenges programmatically via the API, bypassing the need for port 80 access.
In summary, when managing websites that are not public-facing (like internal tools, development sites, etc.) but still require security:
- These sites' security certificates, which are needed for HTTPS, still expire regularly, typically every 90 days. Manually replacing them this often is a frequent task.
- Automating this process is very important. If a certificate expires, web browsers will display alarming security warnings to users trying to access the site via HTTPS, which undermines trust and makes the site seem insecure.
- This is where Caddy is used as a reverse proxy. Think of a reverse proxy as a gatekeeper or a secure front door for your internal website. Instead of users on the internet connecting directly to your website server that's running inside your network, they connect to Caddy first. Caddy receives the incoming request and then securely forwards it to the actual internal website server.
- Because Caddy is the public-facing server that users connect to, it's the one that handles the HTTPS connection and uses the trusted certificate. This process assures the user's web browser that the domain they are connecting to (handled by Caddy) is indeed the correct server (assurance of server DNS identity).
- The key benefit here is that your internal website server does not need to be directly accessible from the internet. Caddy handles all the incoming public connections (like on ports 80 and 443), so you don't have to open those specific incoming ports on your firewall and point them directly at your internal website server. Caddy acts as the secure intermediary.
Guide
Get xcaddy and go
curl -LO https://github.com/caddyserver/xcaddy/releases/download/v0.4.4/xcaddy_0.4.4_linux_amd64.deb
dpkg -i xcaddy_0.4.4_linux_amd64.deb
curl -LO https://go.dev/dl/go1.24.2.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.24.2.linux-amd64.tar.gz
vi /etc/profile # append to end
export PATH=$PATH:/usr/local/go/bin
Compile with porkbun API support ( porkbun is our DNS registrar)
xcaddy build v2.9.1 --with github.com/caddy-dns/porkbun
mv caddy /usr/bin/
groupadd --system caddy
useradd --system \
--gid caddy \
--create-home \
--home-dir /var/lib/caddy \
--shell /usr/sbin/nologin \
--comment "Caddy web server" \
caddy
Setup caddy systemd unit
vi /etc/systemd/system/caddy.service.d
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config
/etc/caddy/caddy.json
ExecReload=/usr/bin/caddy reload --config /etc/
caddy/caddy.json --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_
SERVICE
[Install]
WantedBy=multi-user.target
systemctl edit caddy
[Service]
Environment="PORKBUN_API_KEY="
Environment="PORKBUN_API_SECRET_KEY="
mkdir /etc/caddy
vi /etc/caddy/caddy.json
{
"apps": {
"tls": {
"certificates": {
"automate": [
"git.beantip.ca"
]
},
"automation": {
"policies": [
{
"issuers": [
{
"module": "acme",
"challenges": {
"dns": {
"provider": {
"name": "porkbun",
"api_key": "{env.PORKBUN_API_KEY}",
"api_secret_key": "{env.PORKBUN_API_SECRET_KEY}"
}
}
}
}
]
}
]
}
},
"http": {
"servers": {
"my_https_server": {
"listen": [":443"],
"routes": [
{
"match": [
{
"host": ["git.beantip.ca"]
}
],
"handle": [
{
"handler": "reverse_proxy",
"upstreams": [
{
"dial": "localhost:3000"
}
]
}
]
}
]
}
}
}
}
}
allow caddy to run on port 443
setcap 'cap_net_bind_service=+ep' /usr/bin/caddy