Web and JMX exposure are configured separately. Discuss the design rationale and how you'd harden exposure for a production service.
answer
- web vs JMX = separate include/exclude
- different transports, different threat models
- allow-list, not `*`, in prod
- separate port + loopback bind
- exposure ≠ security boundary
basics
~20 sWeb and JMX each have their own include/exclude sets so you can surface an endpoint on one transport but not the other. In prod, keep web to a minimal allow-list (or a separate internal port), avoid *, and layer Spring Security on top.
solid answer
~40 sActuator treats each transport independently: `management.endpoints.web.exposure.*` and `management.endpoints.jmx.exposure.*`. That separation exists because the transports have different reach and threat models — JMX is typically local/agent-managed, HTTP is potentially internet-facing. The health-only web default reflects a secure-by-default stance: sensitive endpoints (env, configprops, heapdump, threaddump, beans, loggers) shouldn't be trivially reachable. Production hardening layers: (1) prefer an explicit allow-list over `include=*`, or `include=*` with an `exclude` denylist of sensitive IDs; (2) move Actuator to a separate `management.server.port` bound to an internal address so scrapers/probes reach it but the public LB doesn't; (3) put a dedicated `SecurityFilterChain` with `EndpointRequest.toAnyEndpoint()` requiring auth; (4) keep JMX off (`spring.jmx.enabled=false`, the modern default) unless a management agent needs it. Exposure is one layer, not the security boundary — combine it with network and authz controls.
code
java · 21 lines@Configuration
class ActuatorSecurityConfig {
// Dedicated chain for Actuator endpoints (on the management port).
@Bean
SecurityFilterChain actuatorChain(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
// health/info open for probes; everything else authenticated
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.anyRequest().hasRole("ACTUATOR"))
.httpBasic(withDefaults());
return http.build();
}
}
// application.properties
// management.server.port=8081
// management.server.address=127.0.0.1
// management.endpoints.web.exposure.include=health,info,metrics,prometheus
// management.endpoint.health.show-details=when-authorizedgo deeper
Aware web and JMX are configured with different properties.
Can build a minimal web allow-list and knows sensitive endpoints exist.
Combines allow-list + separate port + Spring Security and gates health details.
Articulates defense-in-depth layering, transport threat models, and codifies exposure/security/network standards across the fleet.
## Why two independent exposure sets Actuator surfaces endpoints over two transports, each with its own **threat model and audience**: - **Web/HTTP** — potentially reachable by anything that can route to the port; the most dangerous surface. Default: only `health` exposed. - **JMX** — traditionally consumed by local JMX agents/consoles or JMX-over-RMI; different access path. Modern Spring Boot disables JMX entirely by default (`spring.jmx.enabled=false`). Giving each transport its own `include`/`exclude` pair lets you, say, expose `threaddump` to an internal JMX agent while keeping it off HTTP — or the reverse. Coupling them would force an all-or-nothing decision that doesn't match real ops needs. ## The secure-by-default rationale The **health-only web default** is a deliberate least-exposure posture. Endpoints like `env`, `configprops`, `beans`, `mappings`, `loggers`, `heapdump`, `threaddump` reveal configuration, secrets-in-env, internal structure, or allow live mutation. Shipping them open would be a data-leak/DoS footgun, so Spring Boot ships them enabled-but-unexposed. ## Production hardening layers 1. **Minimal allow-list** — expose only what you operate on: `management.endpoints.web.exposure.include=health,info,prometheus`. Avoid `*` in prod; if you use it, pair with an `exclude` denylist of sensitive IDs (`env,configprops,heapdump,threaddump,beans`). 2. **Separate management port + internal bind** — `management.server.port=8081`, `management.server.address=127.0.0.1` (or a private interface). The public LB fronts `server.port` only; Actuator is reachable by the sidecar/scraper, not the internet. 3. **Authentication/authorization** — a dedicated `SecurityFilterChain` using `EndpointRequest.toAnyEndpoint()` to require auth for actuator routes, optionally permitting `health`/`info` for probes. Exposure is NOT an authz mechanism; Spring Security is. 4. **Health detail control** — `management.endpoint.health.show-details=when-authorized` so anonymous probes get UP/DOWN but not component internals. 5. **Keep JMX off** unless a management agent needs it; if on, mirror the same restraint on `jmx.exposure.include`. 6. **Sanitization** — env/configprops values are sanitized by default; verify custom sanitizers for extra secret keys. ## Common architectural mistakes - Treating `exposure.include=*` as safe because 'it's behind the LB' — one misrouted ingress leaks everything. - Relying on exposure alone with no Spring Security — anyone who reaches the port reads it. - Exposing `heapdump`/`threaddump` publicly (info leak + DoS). - Forgetting that a separate management port needs its own security filter chain. ## Where exposure sits in the layers Exposure is the **innermost** filter (what Spring even serves). Around it you stack authz (Spring Security), then network (port/address/firewall), then platform (ingress rules). Defense in depth — never a single knob.
- Is exposure configuration a security control?No — it only decides what Spring serves. Anyone who can reach the port sees exposed endpoints. Real access control needs Spring Security (EndpointRequest matchers), network isolation, and health detail gating.
- You must scrape Prometheus metrics but not expose them publicly. How?Expose `prometheus` in the include set, run Actuator on a separate `management.server.port` bound to an internal address, and let the in-cluster scraper reach that port while the public LB fronts only the app port.
- Why keep health probes unauthenticated but everything else locked?Liveness/readiness probes and LBs need cheap unauthenticated UP/DOWN; use `EndpointRequest.to("health","info").permitAll()` plus `health.show-details=when-authorized` so anonymous callers don't see component internals.
saying these in an interview costs you the question
- Treating exposure.include as an access-control/security mechanism
- Using `include=*` in production 'because it's behind the LB'
- Exposing heapdump/threaddump/env publicly
- Assuming configuring web exposure also secures JMX