Skip to content

Serve your tool over HTTPS locally

Recipes beyond the tutorial happy path. Each is independent; take the one you need.

Serve on a LAN address so a phone can reach it

Add the bind host or IP to the certificate's subject alternative names. EnsureServed re-mints automatically when the host list changes, so adding one is enough:

pair, err := localca.EnsureServed(ctx, cfg,
    []string{"localhost", "127.0.0.1", "::1", "192.168.1.20"})

Anything in the list that parses as an IP address becomes an IP SAN; everything else becomes a DNS name. Nothing validates that a DNS name is plausible or resolvable.

Ask for every name you need in one call. localca caches a single leaf per data directory, so two calls with different host lists overwrite each other rather than accumulating — see only one leaf is cached.

The root only has to be installed once, on the serving machine. Other devices trust the certificate only if you import the root into their trust store — copy DataDir/rootCA.pem across. Copy the certificate, never rootCA-key.pem.

Install into one trust store rather than both

EnsureServed targets every supported store. To choose, drive the Authority directly:

a, err := localca.New(cfg)
if err != nil {
    return err
}

_, err = a.Install(ctx, localca.StoreNSS)    // Firefox and Chromium-on-Linux; no password prompt
_, err = a.Install(ctx, localca.StoreSystem) // system-wide; prompts for elevation
  • StoreSystem — the OS store, read by Chrome, Safari, Edge and Go's HTTP client. Needs administrator privileges.
  • StoreNSS — the NSS databases used by Firefox, and by Chromium on Linux. User-level, no password, but needs certutil.

Which mechanism each uses per platform is in platform support.

There is no matching scope on removal: Uninstall always targets both stores, and therefore always asks for elevation. To remove an NSS-only install without a password prompt, use certutil -D against the profile directly.

Get Firefox to trust the certificate

Firefox reads NSS, not the OS trust store, and the NSS install needs certutil:

sudo apt-get install -y libnss3-tools   # Debian, Ubuntu
sudo dnf install -y nss-tools           # Fedora, RHEL
brew install nss                        # macOS

Then install again — Install is idempotent, so re-running it costs nothing:

_, err := a.Install(ctx, localca.StoreNSS)

Restart Firefox afterwards; it caches trust decisions for the life of the process.

Without certutil, localca skips NSS and carries on rather than failing. Chrome, Safari, Edge and Go still trust the certificate; Firefox alone does not. On a box where you cannot install packages, unpack the binary without root.

Find out where the root is currently trusted

state, err := a.Installed(ctx)
if err != nil {
    return err // "no root CA provisioned" means nothing has been minted yet
}

fmt.Println(state.System, state.NSS)

System is read from DataDir/trust-install.json, which records localca's own installs — the OS store has no portable "is this trusted?" query. NSS is queried live with certutil, and is true only when the root is present in every NSS profile found.

Installed never mints a root; it errors if there isn't one.

Get a certificate without touching the trust store

Authority.Leaf mints an in-memory certificate and does nothing else — no install, no files, no cache:

cert, err := a.Leaf(ctx, "localhost", "127.0.0.1") // *crypto/tls.Certificate

Every call mints a fresh certificate and key. It errors if no root has been provisioned yet, so it is not a substitute for EnsureServed on first run.

Change how long certificates last

cfg := localca.Config{
    DataDir: dir,
    AppName: "myapp",
    RootTTL: 5 * 365 * 24 * time.Hour, // default is ten years
    LeafTTL: 30 * 24 * time.Hour,      // default is 90 days
}

RootTTL applies only when a root is minted; changing it later does not extend an existing one. Keep LeafTTL above seven days, or the renewal window defeats the cache and every call re-mints. Both are covered in the configuration reference.

A leaf is never issued past its root's expiry. Once the root has less than one leaf lifetime left, every minted leaf is clamped to the root's NotAfter and localca logs a WARN telling you to re-install. You will only see it if you attach a logger:

a, err := localca.New(cfg, localca.WithLogger(slog.Default()))

Remove everything

_ = a.Uninstall(ctx)                  // out of the trust stores, key kept
_ = a.Uninstall(ctx, localca.Purge()) // ...and delete the stored root cert + key

Purge() does not delete the cached leaf. Delete it too, or the next EnsureServed here serves a certificate signed by the root you just removed:

rm -f "$DATA_DIR"/leaf.pem "$DATA_DIR"/leaf-key.pem

Run Uninstall before deleting the data directory. Without the stored root certificate, localca has no way to identify what to remove, and the entry stays in your system trust store until you delete it by hand.

Provision in CI or a container

Trust installation needs either root or an interactive terminal, and CI usually has neither. Options:

  • Run the container as root. The elevation pre-flight passes on euid == 0 and nothing prompts.
  • Install into NSS only, which needs no privileges at all — enough if the tests drive a browser rather than the OS store.
  • Skip trust entirely and have the test client trust rootCA.pem explicitly, which needs no install:

    pool := x509.NewCertPool()
    pem, _ := os.ReadFile(filepath.Join(dataDir, "rootCA.pem"))
    pool.AppendCertsFromPEM(pem)
    client := &http.Client{Transport: &http.Transport{
        TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12},
    }}
    

Handle ErrElevationUnavailable so a CI run fails with a clear message rather than an unexplained error:

if _, err := a.Install(ctx); errors.Is(err, localca.ErrElevationUnavailable) {
    // no privileges available here — fall back or skip
}

Keep a test off the host machine entirely

Injecting an in-memory filesystem is not enough on its own: the default trust-store backend shells out to real commands regardless of what WithFS is set to. Replace both:

a, err := localca.New(cfg,
    localca.WithFS(afero.NewMemMapFs()),   // root and leaf in memory
    localca.WithTrustStore(fakeTrustStore{}), // nothing touches the real store
    localca.WithClock(func() time.Time { return fixedTime }),
)

A TrustStore whose State reports System: true makes EnsureServed skip the install path completely.