Runbook: Local DNS Record With Browser-Trusted HTTPS
Updated: 2026-06-22
Use this runbook when adding a private/internal hostname such as
nas.kh3group.com that should resolve locally and open in a browser without TLS
warnings.
Recommended Pattern
For internal services, prefer this pattern:
- Technitium serves the local hostname to clients.
- The hostname points to Caddy ingress at
192.168.2.3. - Caddy gets a public browser-trusted certificate using Cloudflare DNS-01.
- Caddy reverse proxies to the private service IP and port.
This avoids installing certificates directly on every appliance and works even when the service is not publicly reachable. ACME DNS-01 only needs permission to create temporary TXT records in Cloudflare DNS.
Example objective:
Visit https://nas.kh3group.com locally without browser certificate warnings.
Example design:
| Layer | Value |
|---|---|
| Internal DNS | nas.kh3group.com -> 192.168.2.3 |
| Caddy hostname | nas.kh3group.com |
| Caddy upstream | https://NAS_IP:NAS_PORT or http://NAS_IP:NAS_PORT |
| Certificate | Let’s Encrypt via Cloudflare DNS-01 |
Prerequisites
- CT
102 technitium-dnsis running at192.168.2.2. - CT
103 caddy-ingressis running at192.168.2.3. - Clients use Technitium for DNS, usually from DHCP option 6.
/opt/caddy/env/caddy.envcontains a validCLOUDFLARE_API_TOKEN.- The Cloudflare token can read the
kh3group.comzone and edit DNS records. - Caddy’s Cloudflare TLS block uses public resolvers:
(tls_cloudflare) {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
resolvers 1.1.1.1 1.0.0.1
}
}
Step 1: Pick the Hostname and Upstream
Choose a hostname under the real Cloudflare-managed domain:
nas.kh3group.com
Choose the upstream target. Examples:
http://192.168.0.50:5000
https://192.168.0.50:5001
If the upstream uses a self-signed certificate, Caddy may need an explicit
transport policy. Prefer proxying to the appliance’s HTTP port on a trusted LAN
if the network boundary allows it. If proxying to HTTPS with an untrusted
upstream certificate, use a scoped transport http block only for that host.
Step 2: Add the Internal DNS Record
In Technitium, add an exact A record:
nas.kh3group.com -> 192.168.2.3
Validate from CT 102:
ssh pve 'pct exec 102 -- dig +short @192.168.2.2 nas.kh3group.com A'
Expected:
192.168.2.3
Validate from a client:
dig nas.kh3group.com +short
Expected:
192.168.2.3
If the client still gets a public answer, renew DHCP and flush the client DNS cache.
Step 3: Add the Caddy Route
Edit CT 103:
ssh pve
pct enter 103
nano /opt/caddy/config/Caddyfile
Add a block like this for an HTTP upstream:
nas.kh3group.com {
import tls_cloudflare
encode zstd gzip
reverse_proxy 192.168.0.50:5000
}
For an HTTPS upstream with a valid upstream certificate:
nas.kh3group.com {
import tls_cloudflare
encode zstd gzip
reverse_proxy https://192.168.0.50:5001
}
For an HTTPS upstream with a self-signed certificate, use this only if accepting that upstream trust tradeoff is intentional:
nas.kh3group.com {
import tls_cloudflare
encode zstd gzip
reverse_proxy https://192.168.0.50:5001 {
transport http {
tls_insecure_skip_verify
}
}
}
Do not use tls_insecure_skip_verify for public or untrusted upstreams.
Step 4: Validate and Restart Caddy
Because Caddy admin is disabled, do not use reload. Validate and restart:
set -a
. /opt/caddy/env/caddy.env
set +a
/usr/local/bin/caddy validate --config /opt/caddy/config/Caddyfile
systemctl restart caddy
systemctl status caddy --no-pager
Check listeners:
ss -ltnp | grep -E ':80|:443'
Step 5: Watch Certificate Issuance
Check the journal without printing env files:
journalctl -u caddy --since "5 minutes ago" --no-pager
Good signs:
trying to solve challenge
authorization finalized
validations succeeded; finalizing order
certificate obtained successfully
If issuance fails with expected 1 zone, got 0, confirm the route imports
tls_cloudflare and that the TLS block has public resolvers.
If issuance fails with Invalid access token, rotate/fix the Cloudflare token.
Step 6: Validate the Browser Path
From CT 103, test the route locally with SNI:
curl -sS -o /dev/null -w "%{http_code} verify=%{ssl_verify_result}\n" --resolve "nas.kh3group.com:443:127.0.0.1" "https://nas.kh3group.com/"
Expected for a working service:
200 verify=0
From a client:
dig nas.kh3group.com +short
curl -Iv https://nas.kh3group.com
Expected:
- DNS returns
192.168.2.3. - TLS verification succeeds.
- Browser shows a normal trusted HTTPS connection.
When Not to Use This Pattern
Do not route through Caddy if the objective is to connect directly to the appliance IP while still using the appliance’s own certificate. In that case, the certificate must be installed or managed on the appliance itself.
Do not expose management consoles broadly. For admin services, add OIDC, VPN, or source-IP restrictions before making them reachable beyond approved admin clients.
Quick Request Template for Future Agents
Use this text when asking an agent to add a local trusted HTTPS service:
Using docs/current/agent-handoff-caddy-technitium.md and
docs/current/local-dns-certificate-runbook.md, add internal HTTPS for
HOSTNAME.kh3group.com. The internal DNS record should point to Caddy at
192.168.2.3, and Caddy should reverse proxy to UPSTREAM_URL. Do not print
secrets. Validate DNS, Caddy config, certificate issuance, and HTTPS status.