Skip to content

Latest commit

 

History

History
218 lines (140 loc) · 6.1 KB

File metadata and controls

218 lines (140 loc) · 6.1 KB

HTTPS for any local service using Caddy

Turn any service running on http://192.168.x.x:3000 into https://recall.lan — a real, trusted HTTPS domain that works on every device on your network. No public domain required, no Let's Encrypt, no cert files to manage.

Stack: Caddy (reverse proxy + local CA) · Pi-hole or AdGuard Home (local DNS)


Why run Caddy on the same machine as the service?

Most tutorials put the reverse proxy on a separate machine — a router, a Pi, a dedicated proxy box. This creates a gap:

Your service ──── unencrypted LAN ──── Caddy ──── HTTPS ──── your devices

The first hop travels the network unencrypted. Anyone on your LAN can read it.

Running Caddy on the same machine as the service closes that gap:

Your service ──── localhost ──── Caddy ──── HTTPS ──── your devices

The only unencrypted segment is localhost — it never leaves the machine. This is the correct architecture for a self-hosted service you actually care about securing.


Prerequisites

  • A Linux server running the service you want to expose (any port)
  • Caddy installed on the same machine as the service
  • Pi-hole or AdGuard Home on your network (recommended), or a router that allows custom DNS entries

Step 1 — Install Caddy

# Debian / Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

Verify:

caddy version
sudo systemctl status caddy

Step 2 — Write your Caddyfile

Edit /etc/caddy/Caddyfile:

recall.lan {
    tls internal
    reverse_proxy localhost:3000
}

Replace recall.lan with whatever domain you want and 3000 with your service's port.

tls internal is the key directive — Caddy generates its own local certificate authority and issues a cert automatically. No files to manage, no expiry to track, no external services contacted.

Multiple services work the same way, just add more blocks:

recall.lan {
    tls internal
    reverse_proxy localhost:3000
}

grafana.lan {
    tls internal
    reverse_proxy localhost:3001
}

home.lan {
    tls internal
    reverse_proxy localhost:8080
}

Reload Caddy after any change:

sudo systemctl reload caddy

Check logs if something doesn't come up:

sudo journalctl -u caddy -n 50

Step 3 — Trust the Caddy root CA on your devices

Caddy generates a root CA at first startup. You need to install it once on every device that should trust your .lan domains.

Find the cert on your server:

sudo cat /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt

Copy it to your local machine:

scp user@your-server-ip:/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt ~/caddy-root.crt

Then install it on each device:

macOS

sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain ~/caddy-root.crt

Windows

Double-click caddy-root.crtInstall CertificateLocal MachinePlace all certificates in the following storeTrusted Root Certification Authorities → Finish.

Linux (Debian / Ubuntu)

sudo cp caddy-root.crt /usr/local/share/ca-certificates/caddy-root.crt
sudo update-ca-certificates

Android

Transfer the file to the device, then: Settings → Security → Install from storage → select the .crt file.

On Android 14+: Settings → Security & Privacy → More security & privacy → Install from storage

iOS / iPadOS

  1. AirDrop caddy-root.crt to the device (or serve it via a local web server and open it in Safari)
  2. Settings → General → VPN & Device Management → tap the profile → Install
  3. Settings → General → About → Certificate Trust Settings → toggle full trust for the Caddy certificate

You only do this once per device. All future .lan domains you add will be trusted automatically because they all share the same root CA.


Step 4 — Point the domain to your server

Pick one method.

Option A: Pi-hole (recommended)

Local DNS → DNS Records → add:

Domain IP
recall.lan your server's LAN IP

Every device using Pi-hole as its DNS resolver will resolve the domain automatically — no per-device config.

Option B: AdGuard Home

Filters → DNS Rewrites → add recall.lan → your server's LAN IP.

Same result as Pi-hole.

Option C: Router custom DNS

Some routers (pfSense, OPNsense, OpenWRT, UniFi) support custom DNS entries in their DHCP/DNS settings. Check your router's documentation. This works network-wide like Pi-hole without needing a separate DNS server.

Option D: /etc/hosts (not recommended for multi-device setups)

Add to /etc/hosts on each device:

192.168.x.x  recall.lan

This works but requires manual updates on every device every time you add a new service. Use one of the above options instead if you can.


Done

Open https://recall.lan in your browser. You should see a padlock — cert issued by Caddy Local Authority, no warnings.

Every new service you add is two lines in the Caddyfile and a systemctl reload caddy.


Troubleshooting

Browser shows "Your connection is not private" The root CA isn't trusted on this device yet. Go back to Step 3.

recall.lan doesn't resolve DNS isn't set up yet, or your device isn't using the right DNS server. Check Step 4. You can verify with:

nslookup recall.lan

Caddy won't start Check the logs: sudo journalctl -u caddy -n 50. Common cause: another process is on port 80 or 443.

Service unreachable through Caddy but works directly Confirm the service is actually listening on localhost and not binding to a specific interface. Test with: curl http://localhost:3000