Skip to main content

Actuator Security

Secure your client application's actuator endpoints and configure Spring Boot Admin Server to access them.

Overview

When client applications expose actuator endpoints, they should be secured. The Admin Server needs credentials to access these secured endpoints.


Quick Start

1. Client: Secure Actuator Endpoints

Add Spring Security to your client application:

Maven:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Gradle:

implementation 'org.springframework.boot:spring-boot-starter-security'

application.yml:

spring:
security:
user:
name: actuator
password: ${ACTUATOR_PASSWORD}

management:
endpoints:
web:
exposure:
include: "*"

2. Client: Share Credentials with Admin Server

Pass credentials via metadata:

spring:
boot:
admin:
client:
url: http://admin-server:8080
instance:
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

3. Server: Enable Instance Authentication

application.yml:

spring:
boot:
admin:
instance-auth:
enabled: true

The Admin Server will automatically use credentials from instance metadata to access actuator endpoints.


Client Configuration

Basic Actuator Security

Simplest approach using default Spring Security user:

spring:
application:
name: my-service

security:
user:
name: actuator
password: ${ACTUATOR_PASSWORD}

boot:
admin:
client:
url: http://admin-server:8080
instance:
metadata:
user.name: ${spring.security.user.name}
user.password: ${spring.security.user.password}

management:
endpoints:
web:
exposure:
include: health,info,metrics,env,loggers

Custom Security Configuration

For more control, create a custom SecurityFilterChain:

package com.example.myservice;

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ActuatorSecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// Permit health endpoint for load balancers
.requestMatchers(EndpointRequest.to("health")).permitAll()
// Secure all other actuator endpoints
.requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated()
// Allow application endpoints
.anyRequest().permitAll()
)
// Use HTTP Basic for actuator
.httpBasic(Customizer.withDefaults())
// Disable CSRF for stateless API
.csrf(csrf -> csrf.disable());

return http.build();
}

@Bean
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder encoder) {
UserDetails actuator = User.builder()
.username("actuator")
.password(encoder.encode(System.getenv("ACTUATOR_PASSWORD")))
.roles("ACTUATOR")
.build();

return new InMemoryUserDetailsManager(actuator);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

Different Actuator and Application Security

Separate security for actuator and application:

@Configuration
@Order(1) // Higher precedence
public class ActuatorSecurityConfig {

@Bean
@Order(1)
public SecurityFilterChain actuatorFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to("health")).permitAll()
.anyRequest().hasRole("ACTUATOR")
)
.httpBasic(Customizer.withDefaults())
.csrf(csrf -> csrf.disable());

return http.build();
}

@Bean
public InMemoryUserDetailsManager actuatorUserDetailsService(PasswordEncoder encoder) {
UserDetails actuator = User.builder()
.username("actuator")
.password(encoder.encode(System.getenv("ACTUATOR_PASSWORD")))
.roles("ACTUATOR")
.build();

return new InMemoryUserDetailsManager(actuator);
}
}

@Configuration
@Order(2) // Lower precedence
public class ApplicationSecurityConfig {

@Bean
@Order(2)
public SecurityFilterChain applicationFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(Customizer.withDefaults());

return http.build();
}
}

Actuator on Separate Port

Run actuator on a different port for isolation:

management:
server:
port: 8081 # Separate management port
endpoints:
web:
exposure:
include: "*"

spring:
security:
user:
name: actuator
password: ${ACTUATOR_PASSWORD}

boot:
admin:
client:
instance:
# Admin Server will auto-detect management port
# Or specify explicitly:
management-base-url: http://localhost:8081
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

Security configuration:

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// No actuator endpoints on main port
.anyRequest().permitAll()
)
.csrf(csrf -> csrf.disable());

return http.build();
}
}

On port 8081, actuator endpoints are secured with Spring Security's default security.


Server Configuration

Enable Instance Authentication

spring:
boot:
admin:
instance-auth:
enabled: true

Admin Server will:

  1. Check instance metadata for user.name and user.password
  2. Use these credentials to access actuator endpoints via HTTP Basic

Default Credentials

Set default credentials for all instances:

spring:
boot:
admin:
instance-auth:
enabled: true
default-user-name: actuator
default-password: ${DEFAULT_ACTUATOR_PASSWORD}

Instances can override via metadata.

Per-Service Credentials

Configure different credentials for each service:

spring:
boot:
admin:
instance-auth:
enabled: true
service-map:
# Service name from spring.application.name
my-service:
user-name: my-service-actuator
user-password: ${MY_SERVICE_PASSWORD}
another-service:
user-name: another-actuator
user-password: ${ANOTHER_SERVICE_PASSWORD}

# Fallback for services not in service-map
default-user-name: default-actuator
default-password: ${DEFAULT_PASSWORD}

Client (my-service):

spring:
application:
name: my-service

security:
user:
name: my-service-actuator
password: ${MY_SERVICE_PASSWORD}

Credential Strategies

Client passes credentials in metadata:

spring:
boot:
admin:
client:
instance:
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

Server uses metadata automatically:

spring:
boot:
admin:
instance-auth:
enabled: true

Pros:

  • Client controls its own credentials
  • Each instance can have unique credentials
  • Server automatically picks up credentials

Cons:

  • Credentials visible in instance metadata (sanitized by default)
  • Requires client configuration

Strategy 2: Server-Side Configuration

Server has all credentials:

spring:
boot:
admin:
instance-auth:
enabled: true
service-map:
service-a:
user-name: service-a-user
user-password: ${SERVICE_A_PASSWORD}

Client just secures actuator:

spring:
security:
user:
name: service-a-user
password: ${SERVICE_A_PASSWORD}

Pros:

  • Centralized credential management
  • Client configuration simpler

Cons:

  • Server must know all client credentials
  • Harder to scale with many services

Strategy 3: Default Credentials

All clients use same credentials:

Server:

spring:
boot:
admin:
instance-auth:
enabled: true
default-user-name: actuator
default-password: ${ACTUATOR_PASSWORD}

All Clients:

spring:
security:
user:
name: actuator
password: ${ACTUATOR_PASSWORD}

Pros:

  • Simplest to configure
  • Uniform across all services

Cons:

  • Single credentials compromise affects all services
  • Less secure

Limiting Exposed Endpoints

Only expose necessary endpoints:

management:
endpoints:
web:
exposure:
include: health,info,metrics,env,loggers

Or exclude specific endpoints:

management:
endpoints:
web:
exposure:
include: "*"
exclude: heapdump,threaddump

Health endpoint details:

management:
endpoint:
health:
show-details: when-authorized
roles: ACTUATOR

Metadata Sanitization

By default, credentials in metadata are sanitized:

spring:
boot:
admin:
metadata-keys-to-sanitize:
- ".*password$"
- ".*secret$"
- ".*key$"
- ".*token$"
- ".*credentials.*"

Metadata user.password will appear as ****** in responses, but the server still uses it internally.


Service Discovery

When using service discovery (Eureka, Consul, etc.), credentials can be set via metadata:

Eureka:

eureka:
instance:
metadata-map:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

Consul:

spring:
cloud:
consul:
discovery:
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

Kubernetes ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
name: my-service-config
data:
application.yml: |
spring:
boot:
admin:
client:
instance:
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

TLS/SSL for Actuator

Use HTTPS for actuator endpoints:

management:
server:
port: 8443
ssl:
enabled: true
key-store: classpath:actuator-keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12

spring:
boot:
admin:
client:
instance:
management-base-url: https://localhost:8443
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

Generate keystore:

keytool -genkeypair -alias actuator \
-keyalg RSA -keysize 2048 \
-storetype PKCS12 \
-keystore actuator-keystore.p12 \
-validity 3650 \
-storepass changeit

Examples

Example 1: Development (No Security)

Client:

management:
endpoints:
web:
exposure:
include: "*"

spring:
boot:
admin:
client:
url: http://localhost:8080

No Spring Security dependency, all endpoints open.

Example 2: Full Security Setup

Client:

spring:
application:
name: payment-service

security:
user:
name: ${ACTUATOR_USER}
password: ${ACTUATOR_PASSWORD}

boot:
admin:
client:
url: https://admin.company.com
username: ${ADMIN_CLIENT_USER}
password: ${ADMIN_CLIENT_PASSWORD}
instance:
service-base-url: https://payment.company.com
management-base-url: https://payment.company.com:8443
metadata:
user.name: ${ACTUATOR_USER}
user.password: ${ACTUATOR_PASSWORD}
tags:
environment: production

management:
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}

endpoints:
web:
exposure:
include: health,info,metrics,env,loggers

endpoint:
health:
show-details: when-authorized

Server:

spring:
boot:
admin:
instance-auth:
enabled: true
# Uses credentials from instance metadata

Example 3: Kubernetes with Secrets

Secret:

apiVersion: v1
kind: Secret
metadata:
name: actuator-credentials
type: Opaque
stringData:
username: actuator
password: secure-password-123

Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
template:
spec:
containers:
- name: my-service
image: my-service:latest
env:
- name: ACTUATOR_USER
valueFrom:
secretKeyRef:
name: actuator-credentials
key: username
- name: ACTUATOR_PASSWORD
valueFrom:
secretKeyRef:
name: actuator-credentials
key: password
ports:
- containerPort: 8080
- containerPort: 8081 # Actuator

application.yml (in ConfigMap):

spring:
security:
user:
name: ${ACTUATOR_USER}
password: ${ACTUATOR_PASSWORD}

boot:
admin:
client:
instance:
metadata:
user.name: ${ACTUATOR_USER}
user.password: ${ACTUATOR_PASSWORD}

management:
server:
port: 8081

Example 4: Multiple Environments

application.yml (common):

spring:
boot:
admin:
client:
instance:
metadata:
user.name: ${ACTUATOR_USER:actuator}
user.password: ${ACTUATOR_PASSWORD}

management:
endpoints:
web:
exposure:
include: "*"

application-dev.yml:

spring:
security:
user:
name: actuator
password: dev-password

boot:
admin:
client:
url: http://localhost:8080

application-prod.yml:

spring:
security:
user:
name: ${ACTUATOR_USER}
password: ${ACTUATOR_PASSWORD}

boot:
admin:
client:
url: https://admin.company.com
username: ${ADMIN_CLIENT_USER}
password: ${ADMIN_CLIENT_PASSWORD}

management:
endpoints:
web:
exposure:
include: health,info,metrics,env,loggers
endpoint:
health:
show-details: when-authorized

Troubleshooting

Issue: 401 Unauthorized on actuator endpoints

Cause: Admin Server doesn't have valid credentials.

Check:

  1. Instance metadata contains credentials:

    curl http://admin-server:8080/instances/{id} | jq '.metadata'
  2. Credentials match actuator configuration:

    curl -u actuator:password http://client:8080/actuator/health

Solution: Add credentials to metadata:

spring:
boot:
admin:
client:
instance:
metadata:
user.name: actuator
user.password: ${ACTUATOR_PASSWORD}

Issue: Admin Server shows "Unavailable"

Cause: Cannot access health endpoint.

Check:

curl -u actuator:password http://client:8080/actuator/health

Solution: Verify:

  1. Health endpoint is exposed
  2. Credentials are correct
  3. Health URL is accessible from Admin Server

Issue: Some endpoints work, others return 401

Cause: Different security rules for different endpoints.

Solution: Ensure all actuator endpoints have same security:

http.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.toAnyEndpoint()).authenticated()
)

Issue: Metadata shows credentials in plain text

Expected: Credentials should be sanitized as ******.

Check sanitization patterns:

spring:
boot:
admin:
metadata-keys-to-sanitize:
- ".*password$"

This is working correctly if API responses show ****** for user.password, even though the server uses the real value internally.


Best Practices

  1. Use Strong Passwords: Generate secure random passwords
  2. Environment Variables: Never hardcode credentials
  3. Limit Exposure: Only expose necessary actuator endpoints
  4. Use HTTPS: Encrypt actuator traffic with TLS
  5. Separate Port: Consider separate management port for isolation
  6. Role-Based Access: Use roles for fine-grained control
  7. Monitor Access: Log actuator access attempts
  8. Rotate Credentials: Regularly update actuator passwords

See Also