Skip to content

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.

For internal services, prefer this pattern:

  1. Technitium serves the local hostname to clients.
  2. The hostname points to Caddy ingress at 192.168.2.3.
  3. Caddy gets a public browser-trusted certificate using Cloudflare DNS-01.
  4. 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-dns is running at 192.168.2.2.
  • CT 103 caddy-ingress is running at 192.168.2.3.
  • Clients use Technitium for DNS, usually from DHCP option 6.
  • /opt/caddy/env/caddy.env contains a valid CLOUDFLARE_API_TOKEN.
  • The Cloudflare token can read the kh3group.com zone 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.