2026-07-28
Monitor Spring Boot SSL bundle expiry and verify rotation
Alert on the serving certificate chain, then prove a Spring Boot SSL bundle rotation reached the external TLS endpoint.
An SSL renewal is not complete when a file changes. It is complete when the bundle reports the new chain, the application diagnostic view reports the same chain, and a new TLS connection presents it to a client. The metric and diagnostic view are two in-process views derived from the same SslInfo model; a fresh external TLS handshake is the independent network observation. This procedure uses Spring Boot 4.1's SSL bundle metric to alert before that point is urgent, then reconciles those views after a rotation.
This is source-reviewed guidance. No Spring Boot application, Prometheus server, certificate renewal, or openssl command was run for this article. Treat the commands and thresholds as operator templates and replace every placeholder with values from your own deployment.
Start with the chain Spring Boot measures
Spring Boot Actuator publishes ssl.chain.expiry, a gauge in seconds until the soonest-expiring certificate in each key store or trust store chain. A negative value means that chain has already expired. The meter has bundle, certificate, chain, and source tags. certificate is the serial number, in hexadecimal, of the certificate that expires first; source is keystore or truststore. In Prometheus, Micrometer's naming convention exposes this as ssl_chain_expiry_seconds. See the Spring Boot 4.1 metrics reference and the immutable SslMeterBinder source.
Alert on the serving key store chain, not every trust anchor by default. A trust store can contain many certificates that matter for outbound validation but do not identify the certificate presented by your HTTPS listener. First inspect a real scrape and record the exact bundle name and key store chain alias. The rule below deliberately requires all three stable selectors: bundle, source, and chain.
The certificate label is not stable across a renewal. It identifies the currently soonest-expiring certificate, so do not aggregate it away when verifying a rotation. After the old sample stops being scraped, Prometheus marks the old series stale according to its staleness handling; dashboards and queries can therefore show the former and replacement series during the transition. Prometheus documents this behavior in its staleness notes. Verify the label values from your own scrape before relying on the selectors below.
Configure a reloadable serving bundle
This is a minimal PEM bundle for an embedded server. reload-on-update: true is required to opt in to automatic reload. In the current Spring Boot 4.1 documentation, Tomcat and Netty are the compatible consumers. A watcher reloads the bundle when the configured files change, then notifies a compatible consumer. Spring Boot does not obtain or renew the certificate for you. See SSL bundle reloading.
spring:
ssl:
bundle:
pem:
webserver:
reload-on-update: true
keystore:
certificate: "file:/etc/tls/live/example.com/fullchain.pem"
private-key: "file:/etc/tls/live/example.com/privkey.pem"
server:
ssl:
bundle: "webserver"Keep the private key readable only by the application identity. Do not put a private-key password, bearer token, or management credential in shell arguments, copied command history, or this configuration. If a proxy or load balancer terminates public TLS, use the bundle and metric for the application listener only when it is actually the serving endpoint. Otherwise, monitor and verify the terminating tier separately.
Prometheus needs the Prometheus registry and an intentionally exposed scrape endpoint. Spring Boot documents /actuator/prometheus as the scrape endpoint and notes that it is not exposed by default. Place it on a protected monitoring path rather than opening it to the internet. Spring Boot's Prometheus export documentation covers the endpoint; Actuator endpoint documentation explains that endpoint access and remote exposure are separate controls.
Enable the SSL info contributor explicitly. In Spring Boot 4.1 it is disabled by default, and only health is exposed over HTTP by default. The following configuration explicitly exposes info and prometheus as well as health:
management:
info:
ssl:
enabled: true
endpoints:
web:
exposure:
include: "health,info,prometheus"Use this only with protected web exposure: make the management path reachable solely through your deployment's authenticated internal network, firewall, proxy, or equivalent control before allowing /actuator/info or /actuator/prometheus. Exposure and access control are separate, so the include setting above does not authenticate callers or authorize access. Authentication and network controls are deployment-specific and are deliberately not pretended in this article; verify them before using the procedure below.
Alert before the serving chain expires
The following rule group uses 30 days for warning and 14 days for critical. The states do not overlap: a chain cannot match warning and critical at the same time. Substitute the exact serving bundle name and chain alias discovered from your scrape, then review the resulting series before enabling notifications.
groups:
- name: spring-boot-ssl-bundle-expiry
rules:
- alert: SpringBootServingCertificateExpiryWarning
expr: ssl_chain_expiry_seconds{bundle="webserver",source="keystore",chain="application"} >= 1209600 and ssl_chain_expiry_seconds{bundle="webserver",source="keystore",chain="application"} < 2592000
for: 15m
labels:
severity: warning
annotations:
summary: "Serving certificate chain expires within 30 days"
- alert: SpringBootServingCertificateExpiryCritical
expr: ssl_chain_expiry_seconds{bundle="webserver",source="keystore",chain="application"} >= 0 and ssl_chain_expiry_seconds{bundle="webserver",source="keystore",chain="application"} < 1209600
for: 15m
labels:
severity: critical
annotations:
summary: "Serving certificate chain expires within 14 days"
- alert: SpringBootServingCertificateExpired
expr: ssl_chain_expiry_seconds{bundle="webserver",source="keystore",chain="application"} < 0
for: 5m
labels:
severity: critical
annotations:
summary: "Serving certificate chain has expired"The durations are an operational policy, not a Spring Boot default. Set them from your renewal lead time, change-control window, and escalation path. The for duration delays a transition from pending to firing; it does not delay clearing. Without keep_firing_for, a false expression deactivates the alert on the next rule evaluation. See the Prometheus alerting rules documentation. A missing matching series is also a monitoring failure, but it is a separate alert with its own scrape, deployment, and label diagnosis. Do not silently broaden this rule to source="truststore" just to make it fire.
Keep the SSL inventory diagnostic-only
SslInfoContributor contributes SSL information to /actuator/info, including bundle and certificate-chain details. This makes it useful after a rotation, but it is not a public status page. Certificate inventory can reveal subjects, issuers, aliases, serials, and validity dates. Restrict it to loopback or an authenticated internal management network, and grant read access only to operators who need it. The Info endpoint reference describes /actuator/info; the immutable SslInfoContributor source shows that it adds the SSL detail.
For a local or internally authenticated diagnostic session, request the endpoint without placing credentials in the command line. This is a template, not an executed request.
curl --fail --silent http://127.0.0.1:<management-port>/actuator/infoConfirm that the returned SSL bundle and chain identify the same newly active serial as the metric's certificate label. If an intermediary terminates TLS, this confirms the application bundle only, not the certificate that an external client receives.
Verify rotation from the network edge
After the renewal mechanism updates the PEM files, wait for the configured watcher quiet period and the next Prometheus scrape. The meter binder registers an SSL bundle update handler and updates its gauge rows when the bundle changes, which is why the metric is useful after a hot rotation. That is source-reviewed behavior, not evidence that your specific server completed a reload. See the Spring Boot 4.1 SslMeterBinder implementation.
Use a fresh connection from an appropriate external network. -servername sends SNI, avoiding a false result from a shared virtual host. This template prints the leaf certificate serial and expiry from the handshake; it does not execute anything in this publication.
openssl s_client -connect <public-dns-name>:443 -servername <public-dns-name> </dev/null 2>/dev/null \
| openssl x509 -noout -serial -enddateThe meter tracks the soonest-expiring certificate in a chain, while the command above reports the served leaf certificate. If the metric's certificate label identifies an intermediate certificate, inspect the full externally served chain with -showcerts and compare that certificate's serial and validity too. Do not use an old browser session, a cached probe, or a connection that bypasses the intended TLS terminator as proof of rotation.
Use one reconciliation checklist
Run this checklist in your environment after every hot rotation:
- From the current Prometheus scrape, find
ssl_chain_expiry_secondswith the exactbundle,source="keystore", andchainselectors. Record its value and thecertificatelabel. Confirm that the value is positive and reflects the expected renewal window. - From restricted
/actuator/info, confirm the same bundle and chain and record the certificate serial that expires first. This is a second in-process view derived from the sameSslInfomodel as the metric, so use it to reconcile application state rather than as an independent observation. - Make a fresh SNI-aware
openssl s_clienthandshake against the real serving address. Record the leaf serial and, if needed, the serial of the earliest-expiring certificate in the presented chain. This independent network observation must agree with the in-process state for the certificate that the external endpoint serves. - Confirm that the former certificate series has stopped receiving samples and that the appropriate expiry alert deactivates on the next rule evaluation after its expression becomes false, unless your rule sets
keep_firing_for. Do not mistake Prometheus staleness transition behavior for a second active certificate.
Rotation is complete when the two in-process views reconcile and the independent external handshake confirms the expected served certificate, with the expiry alerts no longer firing. If they do not agree, stop the rollout conclusion and locate the boundary that differs: file update, Spring Boot bundle reload, embedded server, proxy or load balancer, DNS target, or the probe path.