Safeguard
Developer Tools•

How do you reload a TLS certificate in a Go server without restarting?

Use the GetCertificate callback in Go's tls.Config to load a renewed certificate on the next handshake, with no restart. Steps, caveats and a test.

Safeguard Security Team
4 min read

Set the GetCertificate callback on tls.Config and leave the Certificates list empty. Go then calls your function during handshakes, so your function can return a freshly loaded certificate after renewal. Cache it, reload when the files change, and guard it with a lock. No restart or dropped connections are needed.

Why a static config goes stale

The common pattern is to load a certificate pair once at startup, pass it in the Certificates field, and call ListenAndServeTLS. That certificate lives in memory until the process exits. When a certificate manager renews the files on disk, the server keeps presenting the old one. The outage shows up on the expiry date, as covered in the expiry outage checklist.

What GetCertificate does

The crypto/tls documentation describes GetCertificate as a function that returns a Certificate based on the given ClientHelloInfo. Two details matter:

  • It is only called if the client supplies SNI information, or if the Certificates field is empty.
  • If it is nil or returns nil, Go falls back to NameToCertificate and then to the best element of Certificates.

So to guarantee your callback runs for every handshake, leave Certificates empty. A client that sends no SNI will otherwise skip the callback and get the static entry.

The documentation also says that once a Certificate is returned it should not be modified. Replace the pointer rather than editing the struct.

A safe reload pattern

type reloader struct {
    mu   sync.RWMutex
    cert *tls.Certificate
    crt, key string
}

func (r *reloader) load() error {
    c, err := tls.LoadX509KeyPair(r.crt, r.key)
    if err != nil { return err }
    r.mu.Lock(); r.cert = &c; r.mu.Unlock()
    return nil
}

func (r *reloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    r.mu.RLock(); defer r.mu.RUnlock()
    return r.cert, nil
}

Call load once at startup and fail fast if it errors. Then trigger load again from a file watcher, a timer, or a signal handler.

Choosing the trigger

TriggerStrengthWeakness
Timer every few minutesSimple, no extra dependencyDelay after renewal
File watcherFastSymlink swaps in Kubernetes volumes need care
Signal such as SIGHUPExplicit, works with deploy hooksNeeds a hook to send it

In Kubernetes, a mounted Secret updates through a symlink swap, and only when you do not use subPath. See why subPath mounts miss renewals.

Keep the old certificate on failure

If a reload fails because a file is half written or the pair does not match, keep serving the previous certificate and log the error. LoadX509KeyPair returns an error when the pair is inconsistent, so you can detect it before swapping.

How to test it

  1. Start the server with a short-lived test certificate.
  2. Connect with a TLS client and note the serial number.
  3. Replace the files with a new pair and trigger the reload.
  4. Connect again without restarting the server. The serial should change.
  5. Break the files on purpose and confirm the server keeps the old certificate and logs the failure.

Client side

The same idea applies to mTLS clients: GetClientCertificate is called when a server requests a certificate from the client, and when set, the Certificates field is ignored. Use it to present rotated client certificates. See mTLS decisions.

Where Safeguard fits

Safeguard does not manage certificates. It provides SBOM and dependency scanning, reachability analysis and policy gates for Go modules and the rest of your build. Try the free scan at gold.safeguard.sh/scan.

Quick check

Do I need to restart when the certificate changes?

Not if you use GetCertificate and reload the pair yourself. New handshakes then use the new certificate.

Why leave Certificates empty?

The callback is only called when the client sends SNI or when Certificates is empty, so an empty list makes it run every time.

Do existing connections switch certificates?

No. A certificate is presented during the handshake, so open connections keep their session. Only new handshakes see the change.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.