How to set up a macOS laptop to consume internal services — the dashboards
and APIs served under *.rrchnm.internal behind heimdall-issued
(internal step-ca) TLS certs: kuvasz, grafana, forge, bao, Garage,
Zot, the Incus UIs, heimdall’s own ACME directory, etc.
This is the client/consumer side. The mirror-image server/issuer side —
getting an off-fleet Caddy (or any ACME client) to actually obtain a heimdall
cert — is acme-off-fleet-clients.md (operator note). CA chain
design + rotation: internal-cert-authority.md (operator note),
sop-ca-bundle-rotation.md (operator note). Internal zone authoring:
internal-dns.
Two things a Mac needs, and they’re independent: (1) DNS that resolves the
internal zone, and (2) trust of the RRCHNM internal root CA. A failure in
either looks different — diagnose them separately (the checklist at the bottom).
TL;DR
Terminal window
mkdir-p~/rrchnm
# 1. Root CA — extract from the repo (roots are public-by-design plaintext)
The internal zone is served by the CoreDNS resolver trio, not public DNS — so
the Mac must be on the campus network / VPN that points at those resolvers
(see internal-dns). Verify:
Terminal window
scutil--dns|grep-A3rrchnm# is there a resolver scoped to rrchnm.internal?
dig+shortheimdall.rrchnm.internal# heimdall per fleet.yaml (step_ca_servers class)
dig+shortkuvasz.rrchnm.internal# the internal HAProxy VIP (haproxy_internal_vips, VRRP-floated)
If these NXDOMAIN / time out, it’s a network/VPN problem, not a cert
problem — fix DNS first, the TLS error is downstream noise. No /etc/hosts
hacks; if a name doesn’t resolve it usually needs a dns_zones entry on the
resolver side (acme-off-fleet-clients.md §4, operator note),
not a client workaround.
2. Trust — the macOS split-brain gotcha
macOS has two trust stores and they don’t talk to each other:
System keychain — used by Safari, Chrome, and Apple frameworks
(Secure Transport).
OpenSSL/LibreSSL file bundle (/etc/ssl/cert.pem) — used by the
command-line curl, openssl, and most Homebrew CLI tools.
Installing the root into the keychain fixes browsers only. A curl whose
verbose output shows * CAfile: /etc/ssl/cert.pem is on the OpenSSL path and
ignores the keychain entirely — you must give it the cert as a file. This
is the #1 source of “I installed it but curl still fails.”
Safari / Chrome — pick it up from the keychain automatically.
Firefox — ships its own NSS store; ignores the keychain. Either set
security.enterprise_roots.enabled = true in about:config (makes Firefox
read the system store), or import root-ca.crt under Settings → Privacy &
Security → Certificates → View Certificates → Authorities → Import.
CLI tools (file bundle)
Don’t edit /etc/ssl/cert.pem directly — OS updates clobber it. Build a
combined bundle and point the standard env vars at it (already in the TL;DR):
Per-command override without the env vars: curl --cacert ~/rrchnm/root-ca.crt ….
Root alone is enough — when the server serves its chain. Caddy serves the
full ACME chain (leaf + RRCHNM Internal Intermediate CA), so trusting the
root lets the client build leaf → intermediate → root. You only need to add
the intermediate to your bundle if a server is misconfigured to serve the
leaf alone.
Python / Node don’t use either store. Python requests/httpx use
certifi; point them with REQUESTS_CA_BUNDLE / SSL_CERT_FILE or
certifi’s cafile. Node uses NODE_EXTRA_CA_CERTS=~/rrchnm/root-ca.crt.
Optional: the step CLI
Lets you fetch the root straight from heimdall (pinned by fingerprint, no
trust-on-first-use) and inspect issued certs:
s_client showed the leaf issued by Caddy Local Authority, and
docker compose logs caddy on the host showed
certificate obtained successfully {"issuer": "local"} with zero ACME
activity. Root cause: the cert_authority: heimdall switch
(host_vars/armin.yaml) was committed but the stack hadn’t been redeployed,
so the container was still in selfsigned mode (TLS_MODE=tls_selfsigned).
Fix was entirely server-side — redeploy from the toolbox container:
make deploy-docker-compose FQDN=kuvasz.rrchnm.internal, which flips
TLS_MODE=tls_heimdall and runs caddy-verify-cert. The lesson: a
Caddy Local Authority issuer is a deploy/issuance problem on the host,
never a trust problem on the Mac.
Renewals — nothing to do
heimdall leaf certs are short-lived (24h defaultTLSCertDuration); Caddy
auto-renews on its ~2/3-lifetime schedule, transparently, as long as the
ACME HTTP-01 path stays reachable server-side. The root you installed is valid
until 2036 — re-run §2 only on a root rotation
(sop-ca-bundle-rotation.md (operator note)), which is a
deliberate, announced event, not something teardown/make step-ca triggers.
Last updated: 2026-06-17. Written from the kuvasz off-fleet-trust debug
session — companion to acme-off-fleet-clients.md (operator note)
(server side).