Introduction

Spring Boot Actuator exposes operational endpoints (/actuator/health, /actuator/env, /actuator/beans, /actuator/metrics) that reveal sensitive application internals. By default in Spring Boot 3, only health and info are exposed over HTTP, but misconfiguration can expose env (showing environment variables and passwords), beans (showing application architecture), or threaddump to unauthenticated users. The most common production incidents involve accidentally exposed actuator endpoints that leak credentials, database connection strings, or internal configuration to the public internet.

Symptoms

Sensitive data exposed:

bash
curl http://localhost:8080/actuator/env
# Shows all environment variables including passwords and API keys

Or endpoints not accessible:

json
{
    "timestamp": "2026-04-09T10:00:00.000+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/actuator/health"
}

Common Causes

  • expose=* exposes all endpoints: Including sensitive ones like env and beans
  • No authentication on actuator port: Actuator accessible without login
  • Actuator on same port as application: Public users can reach actuator endpoints
  • Custom management port not configured: No separation between app and management
  • Health endpoint shows full details: Database version, disk space exposed
  • Actuator CORS allows all origins: Cross-origin requests to actuator endpoints

Step-by-Step Fix

Step 1: Configure safe endpoint exposure

```yaml # application.yml management: endpoints: web: exposure: include: health,info,metrics,prometheus # Only safe endpoints exclude: env,beans,threaddump,httptrace # Explicitly exclude sensitive

endpoint: health: show-details: when-authorized # Only show details to authenticated users show-components: always env: enabled: false # Disable entirely if not needed ```

Step 2: Secure actuator endpoints with Spring Security

```java @Configuration public class ActuatorSecurity extends WebSecurityConfigurerAdapter {

@Override protected void configure(HttpSecurity http) throws Exception { http.requestMatcher(EndpointRequest.toAnyEndpoint()) .authorizeRequests() .requestMatchers(EndpointRequest.to("health", "info")) .permitAll() // Health checks accessible to load balancer .anyRequest() .hasRole("ACTUATOR_ADMIN") // Other endpoints require admin role .and() .httpBasic(); } } ```

Step 3: Use separate management port

```yaml # application.yml management: server: port: 8081 # Separate port for actuator endpoints address: 127.0.0.1 # Bind to localhost only

endpoints: web: exposure: include: "*" # Safe because port is not publicly accessible ```

```nginx # nginx config - do NOT proxy /actuator to public server { listen 80; server_name app.example.com;

location / { proxy_pass http://localhost:8080; }

# Actuator on port 8081 is NOT proxied -- internal only } ```

Prevention

  • Never use management.endpoints.web.exposure.include=* in production
  • Use a separate management port bound to localhost only
  • Restrict health endpoint details to authenticated users with show-details: when-authorized
  • Add Spring Security rules specifically for actuator endpoints
  • Disable endpoints you do not need with management.endpoint.<name>.enabled: false
  • Configure CORS to block cross-origin requests to actuator endpoints
  • Add security scanning to CI that checks actuator endpoint exposure