Spring Boot Actuator Unauthorized Access: Production Endpoint Hardening
Unprotected Spring Boot Actuator endpoints leak memory, config, and secrets, and can invoke shutdown. This guide covers three production hardening methods: Maven dependency control, disabling sensitive endpoints, and Spring Security auth, with full code and verification steps.
Scope
Unauthenticated Spring Boot Actuator is a high-frequency production risk. Developers enable actuator and skip auth. Attackers hit /actuator/env for database passwords, /actuator/heapdump for heap dumps and extracted keys, or /actuator/shutdown to kill the app. Applies to: Spring Boot 2.x/3.x pre-production security review, emergency containment of exposed actuator, and teams that must meet MLPS controls for sensitive-data leakage.
Prerequisites
- Spring Boot 2.x or 3.x project with spring-boot-starter-actuator
- Maven or Gradle build
- Source-change rights and ability to rebuild/redeploy
- For the Spring Security path, spring-boot-starter-security already on the classpath
Mechanism
Actuator is Spring Boot's production monitoring component. Default exposure is health, but developers often set management.endpoints.web.exposure.include=* and open everything. Sensitive endpoints:
- env / configprops: leak datasource passwords, keys, and other config
- heapdump: JVM heap snapshot; credentials extractable offline (highest risk)
- shutdown: kills the app — DoS
- jolokia / loggers: JMX operations or log-level changes that aid follow-on attacks
Defense: minimum exposure + mandatory auth. Production exposes only health/info. Everything else off. Sensitive endpoints that must stay on are gated by Spring Security (IP allowlist or account auth).
Procedure
Step 1: Trim exposed endpoints (first)
In application.yml, expose only health checks. Disable heapdump/shutdown/env and other sensitive endpoints (key config):
`# application.yml
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: never # do not expose database or other details
shutdown:
enabled: false # explicitly disable shutdown`
Change the default path to cut scanner hits:
`management:
endpoints:
web:
base-path: /internal-monitor # default is /actuator; change to a random path`
Step 2: Enforce auth with Spring Security
If some sensitive endpoints must remain, intercept with Security. Add to pom.xml:
`<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>`
Config class (Spring Boot 3.x):
`@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/internal-monitor/**")
.authorizeHttpRequests(auth -> auth.anyRequest().hasRole("MONITOR"))
.httpBasic(Customizer.withDefaults());
return http.build();
}
}`
Define the monitor account in application.yml (inject via env var in production; do not hardcode):
`spring:
security:
user:
name: monitor
password: ${MONITOR_PASSWORD} # inject a strong password via environment variable
roles: MONITOR`
Step 3: Port isolation (advanced)
Bind actuator to an internal management port, physically separate from the app port:
`management:
server:
port: 9081 # dedicated port, not exposed externally
address: 127.0.0.1 # localhost only; pair with Nginx internal reverse proxy`
Step 4: Live self-check commands
`# check whether sensitive endpoints are anonymously accessible (should return 401 or 404)
curl -i http://your-app:8080/actuator/env | head -5
curl -i http://your-app:8080/actuator/heapdump -o /dev/null -w "%{http_code}\\n"
# if already hardened, should return 401 (auth required) or 404 (not exposed) respectively`
Verification
`# 1) rebuild the package and start
mvn clean package -DskipTests && java -jar target/app.jar
# 2) unauthenticated access to sensitive endpoints -> 401
curl -i http://localhost:8080/actuator/env | grep -E "HTTP/|401"
# 3) access with the monitor account -> 200 and returns health data only
curl -u monitor:password http://localhost:8080/actuator/health
# 4) confirm the heapdump endpoint no longer exists -> 404
curl -i http://localhost:8080/actuator/heapdump | head -3
# 5) if a dedicated port is configured, /actuator on business port 8080 should return 404 directly`
FAQ
FAQ 1: include: health,info is set but /actuator/env is still reachable?
Check overlapping config files (application-dev.yml overriding include) or leftover management.endpoints.web.exposure.include=*. Unify with spring.config.import and confirm the effective include value in startup logs.
FAQ 2: After Spring Security, business APIs also require login?
securityMatcher did not take effect. Security intercepts all requests by default. Constrain with securityMatcher(/internal-monitor/**) on Spring Boot 3.x. Spring Boot 2.x uses antMatchers. The APIs are not interchangeable.
FAQ 3: health must stay for cloud probe/liveness?
Keep health exposed. Set show-details: never (Spring Boot 2.x never; 3.x never/always) so the probe does not leak database state or version. Restrict probe sources with an IP allowlist.
Summary
Unauthenticated Actuator defense is minimum exposure + mandatory auth + port isolation. Production exposes only health/info; sensitive endpoints off. Endpoints that must remain get Spring Security account/IP dual restriction. Advanced: isolate actuator on an internal port. Put this on the go-live security checklist. After deploy, curl every sensitive endpoint immediately.