Spring Boot Actuator: What to Expose, What to Hide, and What to Check Before Adding Endpoints
There's a configuration that shows up in almost every Spring Boot tutorial, and people copy it without really reading it:
management:
endpoints:
web:
exposure:
include: "*"
One asterisk. Every endpoint. All at once. And the tutorial moves straight to the next step without pausing for even a second to think about what that actually means.
My take is simple: Actuator isn't bad. The mistake is enabling it without a clear policy for what to expose, what to restrict, and for whom. That gap between operational tool and unnecessary public surface area isn't something Spring Boot closes on its own — you close it with explicit judgment.
I've been in tech for over thirty years, and one of the patterns I keep seeing isn't the dramatic bug that takes down production: it's the small decision made without a policy, accumulating quietly until someone finds it from the outside.
Spring Boot Actuator Endpoints Security: What the Docs Say — and What They Don't
The official Spring Boot Actuator documentation is clear about something most tutorials skip: by default, only health and info are exposed over HTTP. The rest exist but aren't automatically accessible.
What the documentation spells out precisely:
- The distinction between enabling an endpoint (it exists in the context) and exposing it (it's reachable via HTTP or JMX).
- That
shutdownis enabled but not exposed by default — precisely because its impact is irreversible. - That
envandconfigpropscan leak sensitive values, and that Spring Boot offers configurable sanitization for those cases. - That securing endpoints is the developer's responsibility: Spring Boot doesn't add automatic authentication over Actuator.
What the documentation doesn't resolve for you:
- Which endpoints actually make sense for your specific operation.
- Whether the management port is separated from the app port or shared.
- Whether there's a proxy, API gateway, or load balancer in front that filters — or doesn't filter — those routes.
- Whether the team operating the system has differentiated access from public traffic.
That gap between "what Spring Boot configures by default" and "what you need in production" is exactly where implicit decisions pile up.
The Most Common Mistake: Enable Everything and Secure It Later (or Never)
The recipe that circulates across tutorials, Stack Overflow, and example repos usually looks like this:
# application.yml — common pattern worth reviewing before copying
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
The hidden cost of this pattern isn't that it's "dangerous" in the abstract. The concrete cost is:
-
/actuator/envexposes environment variables — including keys, database URLs, and tokens if they aren't sanitized. -
/actuator/heapdumpcan return a full JVM heap dump, which in many systems contains live in-memory data. -
/actuator/shutdownlets you kill the application via HTTP POST if it's enabled and exposed. -
/actuator/loggerslets you change log levels at runtime, which can generate operational noise or expose diagnostic information.
None of those endpoints are inherently bad. heapdump is invaluable for diagnosing a memory leak. loggers is exactly the tool you want when you need DEBUG output without a restart. The problem is exposing them without deciding who can reach them and from where.
The analogy I keep coming back to: it's similar to what happens with Docker healthchecks. A badly designed healthcheck doesn't fail because Docker is broken — it fails because nobody defined what "healthy" actually means for that container. Actuator is the same story: the tool is solid, but the exposure criteria has to be explicit.
Decision Matrix: What to Expose, What to Restrict, What to Disable Entirely
This matrix is a pragmatic guide, not an absolute rule. Each row represents an analysis worth doing before including that endpoint.
| Endpoint | What it exposes | Recommended approach |
|---|---|---|
health |
App state and dependency health | Expose. Use show-details: when-authorized in production |
info |
Build metadata, version, git commit | Expose if you have useful data. Nothing sensitive by default |
metrics |
JVM, HTTP, and custom metrics | Expose only to internal monitoring systems (Prometheus, etc.) |
loggers |
Current logging levels | Restrict to operators. Useful, but with controlled access |
env |
Environment variables | Restrict. Review sanitization before enabling |
configprops |
Configuration properties | Restrict. Can surface sensitive values |
heapdump |
JVM heap dump | Controlled environments only. Not on public production |
threaddump |
Active thread state | Useful for diagnostics. Restrict to operators |
shutdown |
Shuts down the application | Don't enable unless there's a very clear operational reason |
mappings |
All REST endpoints in the app | Surface area information. Restrict |
beans |
All Spring context beans | Development only. Not in production |
The configuration I'd use as a starting point in a production environment:
# application.yml — pragmatic starting point for production
management:
server:
port: 8081 # separate port, not publicly exposed
endpoints:
web:
exposure:
include: health, info, metrics, loggers
endpoint:
health:
show-details: when-authorized
env:
enabled: false # disable if no sanitization policy is defined
Separating the management port (management.server.port) from the application port is a decision that dramatically simplifies your policy: internal traffic hits 8081, public traffic has no access to that port at all. It turns security into a structural property instead of something that depends entirely on getting every auth rule right in code.
What You Can't Conclude Without Your Own Experiments, Logs, or Data
There's an honest limit worth naming here. This guide is based on the official documentation and careful analysis. What you can't conclude from this alone:
- That your current configuration is secure: that requires checking what's in front of your app (proxy, gateway, firewall) and how it's configured.
- That separating the management port is sufficient: if port 8081 is open in your security group or network firewall, the separation means nothing.
- That
envsanitization covers all your sensitive keys: Spring Boot sanitizes by known patterns (password,secret,key, etc.), but if you're using unconventional variable names, verify it explicitly. - That adding Spring Security over Actuator is the only solution: you can also handle access at the network level, proxy level, or ingress level. The right layer depends on your architecture.
If you want to inspect what traffic is actually reaching your management endpoints in a local environment, a tool like Sniffnet can help you visualize connections without diving straight into tcpdump.
FAQ: Spring Boot Actuator Endpoints Security
Which Actuator endpoints are enabled by default?
According to the official documentation, only health and info are exposed over HTTP by default. The rest are enabled in the context but not reachable via HTTP. The exception is shutdown, which isn't enabled by default precisely because of its impact.
What's the difference between enabling and exposing an endpoint?
Enabling means the endpoint exists in the Spring context and can be invoked internally. Exposing means it's reachable from outside (HTTP or JMX). You can have an endpoint enabled but not exposed: it exists, but nothing can call it from the outside. The management.endpoints.web.exposure.include property controls HTTP exposure.
Is Spring Security enough to protect Actuator?
Spring Security is a valid layer, but it's not the only one — and it's not necessarily your first line of defense. Separating the management port and restricting access to it at the network or proxy level is a more robust policy because it doesn't depend on every security rule in your code being correct. Ideally, both layers coexist.
Does /actuator/health expose sensitive information?
With the default configuration, health returns only the status (UP or DOWN). Dependency details (database, disk, etc.) only appear if you configure show-details: always. The more sensible middle ground for production is show-details: when-authorized: your monitoring system sees the detail, anonymous traffic doesn't.
What happens if I use include: "*" in production?
Every enabled endpoint gets exposed — including env, configprops, heapdump, and potentially shutdown if you've enabled it. It's not an invalid configuration in every context, but it requires that access to the management port be completely restricted to internal systems. If that port is publicly reachable, anyone can query your environment variables or request a heap dump.
How do I validate which endpoints are actually exposed in my app?
The /actuator endpoint returns a JSON with active endpoints and their URLs. That's your first stop. You can also check Spring Boot's startup logs: it logs registered endpoints at the right log level. And if you have Prometheus or any scraping system configured, verify it's only hitting the management port — not the public one.
My Take: Actuator Is an Operational Tool, Not a Convenience Feature
When I first encountered the Docker healthcheck story I mentioned in that earlier post, what stuck with me was the same lesson that applies here: the value of a tool is in describing an explicit contract, not in turning it on and seeing what happens.
Actuator is the same. It's not a feature you "add" to the app. It's an observability policy that defines what operational information gets exposed, to whom, and from where. That policy can be as simple as two lines in your YAML or as elaborate as differentiated roles with Spring Security — but it has to exist consciously.
The uncomfortable part is that the tutorial everyone copies shows include: "*" as if it were the natural next step. And technically it works. But "it works" and "it's a decision made with judgment" are different things, and that difference matters when the system grows.
The practical move for right now: open the application.yml of whatever project you're working on, find the management.endpoints section, and ask yourself whether what's there is an explicit policy or the result of copying a tutorial without questioning it. If it's the second thing, spend fifteen minutes on it before the system goes to a shared environment.
Not because Actuator is dangerous. But because implicit decisions are the ones that accumulate.
Sources:
This article was originally published on juanchi.dev
Top comments (0)