skip to content

How do you enable hot-reloadable TLS certificates on the embedded server without restarting the app?

level: seniorimportance: should knowfreq 20%

answer

  1. reload-on-update: true on the bundle
  2. bundle-only feature; legacy key-store can't reload
  3. FileWatcher -> rebuild SslBundle -> update handler -> new SSLContext
  4. K8s atomic symlink swap = supported path
  5. old connections drain, new handshakes get new cert

basics

~10 s

Use an SSL bundle and set reload-on-update: true on it (e.g. spring.ssl.bundle.pem.web.reload-on-update). Spring Boot watches the cert/keystore files and rebuilds the embedded connector's SSLContext in place when they change — no restart.

solid answer

~50 s

Certificate rotation without downtime requires SSL bundles; the legacy server.ssl.key-store path can't do it. On the bundle you set reload-on-update: true and Spring Boot's file watcher monitors the referenced keystore/truststore or PEM files. When cert-manager, a mounted secret, or an ops job rewrites those files, the watcher fires, the SslBundle is rebuilt, and registered update handlers push a fresh SSLContext into the embedded connector (Tomcat/Jetty/Undertow/Netty) so new handshakes use the new cert while existing connections drain. You reference the bundle with server.ssl.bundle=<name>. Gotchas: the file path must be stable — Kubernetes atomic symlink swaps of a mounted secret are the supported case, but some editors replace inodes in ways watchers miss; and only bundle-based connectors reload, so any client you built from the bundle must also be re-derived or use the same live SslBundle to pick up the change.

code

yaml · 14 lines
yaml
spring:
  ssl:
    bundle:
      pem:
        web:
          reload-on-update: true          # enable hot reload
          keystore:
            certificate: "file:/etc/tls/tls.crt"
            private-key: "file:/etc/tls/tls.key"
  # optional debounce for rapid/partial writes:
  # ssl.bundle.watch.file.quiet-period: 10s
server:
  ssl:
    bundle: web

go deeper

for a junior

Know hot reload requires a bundle with reload-on-update: true.

for a middle

Explain that only bundle-based connectors reload and new handshakes get the new cert.

for a senior

Describe the FileWatcher -> SslBundle rebuild -> update-handler mechanism and the K8s atomic-swap path.

for a principal

Design cert-rotation automation (cert-manager/Vault), account for inode/quiet-period edge cases and outbound-client propagation across the fleet.

## Why restart-free rotation matters TLS certificates expire and must be rotated (Let's Encrypt every ~90 days; internal CAs more often). Restarting every replica to pick up a new cert causes churn and, at scale, downtime windows. **Hot reload** swaps the certificate on a running embedded server. ## The mechanism Hot reload is a **SSL-bundle-only** feature (Spring Boot 3.2+ for the reload wiring on embedded servers). Steps: 1. Define the connector's material as a bundle: `spring.ssl.bundle.pem.<name>` or `spring.ssl.bundle.jks.<name>`. 2. Set **`reload-on-update: true`** on that bundle. 3. Reference it: `server.ssl.bundle=<name>`. Under the hood Spring Boot registers a **file watcher** (a `FileWatcher` polling the referenced files' last-modified/inode) against the bundle's keystore/truststore/PEM paths. On a detected change it re-reads the material, builds a new `SslBundle`, and invokes the **bundle update handlers** registered via `SslBundles.addBundleUpdateHandler(name, handler)` / `SslBundleRegistry`. The embedded `WebServerFactory` integration registers such a handler so it rebuilds the connector's `SSLContext`/`SSLHostConfig` **in place**. Existing keep-alive connections continue on the old context and drain; **new handshakes use the new certificate**. ## Config example ```yaml spring: ssl: bundle: pem: web: reload-on-update: true keystore: certificate: "file:/etc/tls/tls.crt" private-key: "file:/etc/tls/tls.key" server: ssl: bundle: web ``` (There is also a global watch tuning under `spring.ssl.bundle.watch.file.quiet-period` in recent versions to debounce rapid writes.) ## The Kubernetes happy path cert-manager writes the renewed cert into a Secret; the kubelet updates the mounted files via an **atomic symlink swap** of the `..data` directory. The watcher sees the change and reloads — no pod restart. This is the canonical use case bundles were designed for. ## Gotchas - **Legacy path can't reload.** If you used `server.ssl.key-store` instead of a bundle, `reload-on-update` doesn't exist — you must migrate to a bundle. - **Inode/atomic-write semantics.** Watchers key off file changes; tools that replace the file via a new inode (some editors, `cp` then `mv`) or non-atomic partial writes can be missed or read mid-write. Kubernetes' atomic swap is the tested pattern. - **Outbound clients don't auto-follow unless wired.** A `RestClient`/`WebClient` you built once from the bundle's `SSLContext` keeps the old context; use the live `SslBundle`/register your own update handler so those clients also refresh. - **Quiet period / debounce.** Very rapid successive writes may need the watch quiet-period so you don't reload on a half-written file. - **Not the same as graceful shutdown.** Reload swaps certs; it does not restart the context or re-run bean initialization. ## When to use Use it for any long-lived service behind automated cert issuance (cert-manager, Vault, ACME) where restart-to-rotate is undesirable — internal mTLS meshes, public HTTPS endpoints on Kubernetes. For static, manually rotated certs on a box you restart anyway, it's optional.

  • You configured reload-on-update but the app never picks up the rotated cert. What would you check?
    Confirm the connector uses server.ssl.bundle (not legacy server.ssl.key-store, which can't reload); verify the file path is what actually changes (Kubernetes atomic symlink swap vs a new inode the watcher misses); check the private key was updated alongside the cert; and ensure the write was atomic, not a partial write during the quiet period.
  • Does hot reload drop existing TLS connections?
    No. The SSLContext is swapped for new handshakes; established keep-alive connections continue on the old context and drain naturally. It's a rebuild-in-place, not a connector restart.

saying these in an interview costs you the question

  • Claiming server.ssl.key-store supports reload-on-update.
  • Thinking reload restarts the app context or drops live connections.
  • Assuming any file-change method works — non-atomic/new-inode writes can be missed.
  • Forgetting that clients built from the bundle need re-wiring to follow the reload.

context