Rotate certificates without restarting.

keyload is a Go library that reloads TLS certificates, keys and CA bundles while your servers and clients keep running. When cert-manager renews a certificate, the next handshake uses it. When a bad file shows up, the last good one keeps serving.

Get started Try the simulator
go get github.com/rossedman/keyload
πŸ”

Both directions

Reloads the certificate you present and the CAs you trust, on servers and clients. That's the part crypto/tls has no hook for.

πŸ›‘οΈ

Never applies bad files

Expired certificates, mismatched keys and half-written files are rejected. The last good certificate keeps serving and you get an event and a metric.

☸️

Kubernetes-ready

Watches Secret volumes correctly, including the ..data symlink swap. CA rotations get an overlap window so peers keep working.

πŸ“¦

Standard library only

crypto/tls still does the verification. No dependencies; gRPC support is a separate module.

Quick start

Point keyload at your files and hand its configs to your server and client. That's the whole setup.

1 Β· Load the files
r, err := keyload.New(keyload.Files{
    Cert:      "/tls/tls.crt",
    Key:       "/tls/tls.key",
    Roots:     "/tls/ca.crt", // to verify servers
    ClientCAs: "/tls/ca.crt", // to verify clients
})
if err != nil {
    log.Fatal(err) // the first load must be valid
}
defer r.Close()
2 Β· Use it
// Server: certificate and client CAs follow reloads.
srv := &http.Server{Addr: ":8443", TLSConfig: r.ServerConfig(&tls.Config{
    ClientAuth: tls.RequireAndVerifyClientCert,
    NextProtos: []string{"h2", "http/1.1"},
})}
go srv.ListenAndServeTLS("", "") // cert comes from TLSConfig

// Client: each connection uses the current files.
tr := &http.Transport{ForceAttemptHTTP2: true}
if err := r.ConfigureTransport(tr, nil); err != nil {
    log.Fatal(err)
}

No certificates to try it with? go run github.com/rossedman/keyload/examples/gencerts@latest -dir ./certs writes a throwaway CA and a certificate for localhost; add -rotate to issue a new one. The getting started guide walks through running, rotating, breaking and testing.

Mount a Kubernetes Secret as a whole directory. A subPath mount never sees updates, from keyload or anything else.

How a reload works

Everything lives in one immutable snapshot. Handshakes read it without locks; a reload builds a new one off to the side and swaps it in only if it passes every check.

  1. Something changes

    A file in a watched directory, SIGHUP with OnSignal, a timer with PollEvery, or a call to r.Reload(ctx).

  2. Read every file

    If a read fails or the files look half-written, wait briefly and retry, up to 3 times.

  3. Validate

    Every check in the table must pass. Unchanged files are a no-op.

  4. Swap

    The new snapshot goes live atomically with the next generation number. On failure, the old one stays and you get an event.

CheckError
Key matches the certificateErrKeyMismatch
Chain inside its validity windowErrExpired
ErrNotYetValid
At least MinRemaining(d) leftErrExpiresTooSoon
Chains to the new roots (RequireChain())ErrChainVerification
CA bundles aren't emptyErrEmptyBundle
No truncated PEMErrTruncatedPEM

Use it with

Works with any crypto/tls server. Each handshake gets a config built from the current snapshot, cached per reload, so it adds about 2.5 ns and no allocations. Session resumption keeps working across reloads. For HTTP/2, set NextProtos here.

srv := &http.Server{TLSConfig: r.ServerConfig(&tls.Config{
    ClientAuth: tls.RequireAndVerifyClientCert,
    NextProtos: []string{"h2", "http/1.1"},
})}
srv.ListenAndServeTLS("", "")

Simulator

Push files at a reloader and see what it serves. This runs keyload's reload rules in your browser: the same checks, retries, keep-last-good and CA overlap.

What lands on disk

On disk

In service

1
generation

Can these peers connect?

OnReload events

    /metrics

    
          

    Know before it breaks

    Every reload that changes something or fails produces an event naming the file, the reason, and the certificates that changed (never key material). MetricsHandler serves Prometheus metrics with no client library.

    MetricMeaning
    keyload_generationCurrent snapshot generation
    keyload_reload_total{result}success, invalid, read_error
    keyload_cert_not_after_seconds{role}Expiry of your certificate and the earliest CA in each bundle
    keyload_last_success_timestamp_secondsCatches a watcher that silently stopped
    # Prometheus alerting rules
    - alert: CertificateExpiresSoon
      expr: keyload_cert_not_after_seconds - time() < 7 * 86400
    - alert: CertificateReloadFailing
      expr: increase(keyload_reload_total{result!="success"}[15m]) > 0

    Examples

    Small runnable programs. The commands to try each one are at the top of its file, and gencerts makes throwaway certificates to try them with.

    Good to know

    The details that usually bite.

    Existing connections

    Keep the certificates they started with. Only new handshakes use reloaded files, so long-lived connections rotate when they reconnect.

    Rotating a CA

    Add the new CA, move certificates over, then remove the old one. With OverlapWindow, peers that haven't rotated yet keep working until the window ends.

    Network filesystems

    inotify misses changes on NFS and FUSE, so keyload checks every 30 seconds there, and on macOS and Windows. Add OnSignal or PollEvery if you need faster.

    Other sources

    Anything that can return PEM works: implement Source, or wrap a function with SourceFunc, for Vault or a secrets API.