Troubleshoot a certificate the browser will not trust¶
Each section is a symptom. Start with the one that matches what you are seeing.
Chrome trusts it but Firefox shows a warning¶
Firefox does not read the OS trust store. It reads its own NSS database, and localca
writes to that only when certutil is available.
Check what happened:
state, err := authority.Installed(ctx)
fmt.Println(state.System, state.NSS) // true false → system worked, NSS did not
If NSS is false, install the NSS tools and install again:
sudo apt-get install -y libnss3-tools # Debian, Ubuntu
sudo dnf install -y nss-tools # Fedora, RHEL
brew install nss # macOS
Restart Firefox afterwards. It caches trust decisions for the lifetime of the process.
Installed reports NSS: false even though Firefox trusts it¶
The NSS state check reports true only when the root is present in every NSS profile
found — every Firefox profile directory, plus ~/.pki/nssdb on Linux. A profile created
after you installed drags the answer back to false.
Run Install(ctx, localca.StoreNSS) again; it is idempotent and will fill in the profiles
that are missing it.
The install returns ErrElevationUnavailable¶
localca refuses to start a password prompt that nothing can answer. You get this error
when the process is not root, and either there is no sudo on PATH or standard input is
not an interactive terminal — a systemd service, a CI job, a GUI application, a process
with its input piped from somewhere.
Options, in rough order of preference:
- Run the install once from a terminal. Trust installation is a one-off; the long-running
service does not need to do it. Give your tool a command that calls
Installand tell the user to run it once. - Install into NSS only, which needs no elevation:
authority.Install(ctx, localca.StoreNSS). Firefox and Chromium-on-Linux trust the result; Chrome-on-macOS, Safari, Edge and Go's HTTP client do not. - Run as root, which the pre-flight accepts without prompting. Reasonable in a container, not on a developer's machine.
- Fall back to an untrusted certificate and let the user click through, if the workflow survives that.
Detect it with errors.Is(err, localca.ErrElevationUnavailable); it survives wrapping.
Nothing appeared in the terminal and the install just failed¶
Attach a logger. localca defaults to a discard handler, so by default it says nothing at
all — including the line explaining why a password prompt is about to appear and the warning
that your root CA is expiring.
Note that sudo, certutil and security write straight to the terminal rather than
through your logger, so their messages will not appear in structured log output either way.
certutil is unavailable and I cannot install packages¶
certutil is a small CLI over libnss3, and libnss3 is usually already present on any
machine with a browser. On a Debian-family box you can unpack just the binary, without root
and without installing anything:
apt-get download libnss3-tools
dpkg -x libnss3-tools_*.deb ./nsstools
export PATH="$PWD/nsstools/usr/bin:$PATH"
Anything localca runs from that shell now finds certutil and the NSS install works.
If that is not possible either, nothing breaks: the system store is what gates provisioning,
so EnsureServed succeeds and only Firefox is left untrusting.
The browser trusted it yesterday and does not today¶
Two likely causes.
The leaf expired, because nothing renewed it. Leaves last 90 days by default and are
re-minted within seven days of expiry — but only when EnsureServed runs, which is normally
once at start-up. A process that has been up for longer than the leaf's lifetime is still
serving the certificate it loaded on the day it started, expired or not. Restart it, and
call EnsureServed again on start-up if you are not already.
The root was re-minted. If the data directory was deleted, moved, or pointed at a temporary path, a new root was created and the old one — still in your trust store — no longer matches. Confirm by comparing the stored root against what your trust store holds:
Then install the new root, and remove the stale entries by hand. They are named
<AppName>-localca-<serial>.
I purged and provisioned again, and now nothing trusts the certificate¶
Purge() deletes the root but leaves the cached leaf, and the next EnsureServed reuses
that leaf under a brand-new root. The served chain is broken until the leaf ages out.
Delete the leaf files and provision again:
I removed the root from Keychain or ca-certificates and localca will not reinstall it¶
localca reads system-store membership from trust-install.json, which records its own
installs. It does not notice a removal made any other way, so it believes the root is still
trusted and skips the install.
Delete the marker and provision again:
Every EnsureServed call writes a new certificate¶
You have set LeafTTL to seven days or less. A cached leaf is renewed once it is within
seven days of expiry, and that window is fixed — so a leaf that short is already due for
renewal the moment it is minted. Raise LeafTTL above seven days; the default of 90 is
usually right.
WARN root CA expiring soon on every start-up¶
The root has less than one leaf lifetime left, so every leaf is being shortened to end when the root does. Renew the root:
Then call EnsureServed again. It mints a fresh root and prompts for elevation once more.
A stale .rootCA-key.pem.*.tmp is sitting in the data directory¶
A write was interrupted before its cleanup ran. localca writes every file to a temporary
name and renames it into place, so the real files are intact. Delete the temporary file.
localca: root cert present but key missing¶
rootCA.pem exists without rootCA-key.pem. localca will not silently replace a root
that may still be installed in your trust store, so it refuses to continue.
Remove the root from your trust stores by hand if it is still there — its name is
<AppName>-localca-<serial>, and the serial is in the output of:
Then delete rootCA.pem and provision again.
Verifying the chain from the command line¶
To see what is actually being served, independent of any browser:
openssl s_client -connect localhost:8443 -servername localhost \
-CAfile "$DATA_DIR/rootCA.pem" </dev/null 2>/dev/null | grep -E 'Verify return code|subject='
Verify return code: 0 (ok) means the leaf chains correctly to the root. If that passes but
a browser still complains, the problem is trust-store installation, not the certificate.