Junior 11 min · May 23, 2026

Spring Cloud Config Server: Centralized Configuration for Microservices

Master Spring Cloud Config Server with Git backend, @RefreshScope, Spring Cloud Bus, {cipher} encryption, and Vault integration for production microservices.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • @EnableConfigServer turns a Spring Boot app into a config server backed by Git, filesystem, or Vault
  • Clients pull config at startup via spring.config.import=configserver: and override with profile-specific files like app-dev.yml
  • @RefreshScope beans reload at runtime when /actuator/refresh is POST-ed without restarting the JVM
  • Spring Cloud Bus broadcasts refresh events across all instances using RabbitMQ or Kafka, eliminating per-pod curl calls
  • Sensitive values are stored as {cipher}... in Git and decrypted by the server using symmetric or RSA keys
✦ Definition~90s read
What is Spring Cloud Config Server?

Spring Cloud Config Server is a Spring Boot application annotated with @EnableConfigServer that exposes an HTTP API for serving externalized configuration to other microservices. It sits between your configuration source (typically a Git repository) and your application instances, acting as a configuration proxy that understands Spring's Environment abstraction.

Think of Spring Cloud Config Server as a central settings book kept in a locked filing cabinet (Git).

Clients resolve their configuration via a URL pattern like /application/profile/label where application matches spring.application.name, profile matches the active Spring profiles, and label maps to a Git branch or tag.

The server supports multiple backends through the EnvironmentRepository abstraction: Git (default, recommended for production), native filesystem (useful for testing), HashiCorp Vault, AWS Parameter Store, Azure Key Vault, and JDBC. Multiple backends can be composed using composite repositories, allowing you to serve non-secret config from Git and secrets from Vault simultaneously.

On the client side, the Spring Cloud Config Client starter auto-configures a PropertySourceLocator that fetches remote properties before the application context refreshes, ensuring all beans see the correct values at startup.

The environment served by Config Server is hierarchical and follows Spring's standard property source precedence rules. An app-dev.yml in the Git repo overrides app.yml, which overrides application.yml (the global defaults). This allows teams to define sensible defaults in a shared application.yml and override only what differs per service or environment.

Combined with Git branching strategies (e.g., a config/production branch for prod), you get environment promotion workflows that mirror your code promotion pipeline.

Plain-English First

Think of Spring Cloud Config Server as a central settings book kept in a locked filing cabinet (Git). Every microservice is like a new employee who looks up their settings from that book on their first day. If the manager updates the book (pushes a commit), Spring Cloud Bus is the office PA system that tells every employee simultaneously — no one has to restart or re-read the whole book, just the pages that changed.

You are debugging a production outage at 2 AM. The database connection pool is exhausted. The fix is a one-line property change, but your microservices architecture has 40 pods across 12 services. Rolling restart will take 20 minutes and risk dropping in-flight transactions. If only you could push a config change and have it applied everywhere in seconds — without a restart. That is exactly the problem Spring Cloud Config Server was built to solve.

In a monolith, externalized configuration is easy: one application.properties file, one JVM, one restart. But microservices shatter that simplicity. You now have dozens of services, each with its own config files, often duplicating the same database URLs, API keys, and feature flags. Drift is inevitable. A developer changes a property in one service and forgets the five others that share it. By next quarter, you have a configuration archaeology problem.

Spring Cloud Config Server centralizes all configuration in a single Git repository (or Vault, or a filesystem). Every microservice becomes a config client that fetches its properties at startup. The server resolves configurations by application name, Spring profile, and label (Git branch or tag), giving you per-environment, per-service configuration with a complete audit trail courtesy of Git history.

Beyond centralization, the real power is dynamic refresh. @RefreshScope marks beans so their dependencies can be re-injected without restarting the JVM. POST /actuator/refresh on a single pod refreshes it. Spring Cloud Bus — a thin event bus on top of RabbitMQ or Kafka — broadcasts that refresh event to every pod at once, making fleet-wide config changes a sub-second operation.

Encryption is the other production concern that Config Server solves elegantly. Plaintext credentials in Git repositories are an audit failure waiting to happen. Config Server's {cipher} prefix lets you store AES-encrypted or RSA-encrypted values in Git and have the server decrypt them transparently before serving clients. Alternatively, Vault backend gives you full secrets management with dynamic credentials, lease rotation, and fine-grained access policies.

This guide covers every layer of Spring Cloud Config Server production deployment: Git backend configuration, client bootstrap, profile-specific overrides, @RefreshScope internals, Spring Cloud Bus setup, encryption key management, Vault backend, and the failure modes that trip up senior engineers in production.

Setting Up the Config Server with Git Backend

A Config Server is a plain Spring Boot application with one annotation and a dependency. Add spring-cloud-config-server to your pom.xml, annotate the main class with @EnableConfigServer, and configure the Git backend. The minimal setup is deceptively simple, but production hardening requires attention to clone-on-start, timeout, basedir, and retry configuration.

The Git backend clones the remote repository to a local basedir on the server. By default, this clone happens lazily on the first request, meaning the first client to start after a server deployment bears the clone latency and any Git authentication failures surface only then. Set spring.cloud.config.server.git.clone-on-start=true to fail fast at startup.

Authentication with private repositories is a common stumbling block. HTTPS authentication uses spring.cloud.config.server.git.username and spring.cloud.config.server.git.password (or a token). SSH authentication requires either mounting a private key file and configuring spring.cloud.config.server.git.private-key, or using the ignoreLocalSshSettings approach with an inline key. Always add the Git host to known_hosts or set strict-host-key-checking=false in non-critical environments.

Search paths let you organize configs in subdirectories. spring.cloud.config.server.git.search-paths={application} allows each service to have its own directory, keeping the repository organized as your service count grows. The {application}, {profile}, and {label} placeholders are available in both search-paths and uri configurations, enabling per-service repository strategies if you need stricter isolation.

For high availability, run multiple Config Server instances behind a load balancer. Since the Git backend uses a local clone, configure a shared volume or — better — accept that each instance will maintain its own clone. Clients should configure spring.cloud.config.fail-fast=true with retry: initialInterval, maxAttempts, and multiplier to handle transient Config Server unavailability without crashing at startup. With multiple config server instances registered in Eureka, clients can use service discovery to locate the config server, removing the need for a hardcoded URL.

The server exposes configuration at /{application}/{profiles}/{label}. The label defaults to main (or master) but can be overridden per-client with spring.cloud.config.label=release/1.2, enabling blue-green config deployments where new config is tested on the release branch before being merged to main.

Git Clone Latency in Production
Without clone-on-start=true, Config Server clones the repository on the first request. If the Git host has latency, your first deploying client will time out waiting for config. Always set clone-on-start=true and configure a reasonable timeout.
Production Insight
Configure spring.cloud.config.server.git.refresh-rate=30 to control how often the server polls Git for changes. Without this, the server uses a stale local clone indefinitely. A 30-second refresh rate is a good balance between latency and Git API rate limits for most teams.
Key Takeaway
@EnableConfigServer + Git URI is all you need to start, but clone-on-start, retry configuration, and search-paths are what separate a toy config server from a production-grade one.

@RefreshScope and Runtime Configuration Updates

@RefreshScope is one of the most powerful — and most misunderstood — features of Spring Cloud Config. It creates a special Spring scope where beans are re-initialized when a refresh event is triggered. Under the hood, Spring wraps @RefreshScope beans in a CGLIB proxy. The proxy stores the bean instance in a RefreshScope cache. When POST /actuator/refresh is called, Spring Cloud Context clears this cache and re-fetches all @Value and @ConfigurationProperties from the updated Environment. The next method call on the proxy triggers re-initialization of the actual bean instance.

The critical subtlety is that the proxy only works when the bean is accessed through Spring's proxy mechanism — i.e., when another bean calls a method on the injected reference. If a singleton bean stores a hard reference to the concrete unwrapped instance (which can happen with constructor injection in certain patterns), the proxy chain is broken and refresh has no effect on that singleton's view.

For @ConfigurationProperties beans, the behavior is slightly different. If the @ConfigurationProperties class is not @RefreshScope, it will not be re-bound on refresh. However, if it is @RefreshScope, Spring will re-bind the entire properties object. This works well for configuration classes used via property accessors (getters) rather than stored field values.

The /actuator/refresh endpoint returns a JSON array of changed keys — use this in your CI/CD pipeline to log which configuration changed and verify the intended keys appear. If the array is empty after a legitimate config push, the Config Server may still be serving a cached Git clone — check spring.cloud.config.server.git.refresh-rate or manually hit /actuator/refresh on the Config Server itself first.

For Spring WebFlux (reactive) applications, @RefreshScope works the same way but be careful with reactive pipelines that capture configuration at assembly time (e.g., a Flux built from a configurable duration). These do not automatically see refreshed values because the pipeline is assembled once. Move configuration reads inside the reactive operators (e.g., Mono.fromSupplier(() -> configBean.getTimeout())) so each subscription picks up current values.

@RefreshScope Cannot Refresh Singletons That Bypass the Proxy
If a singleton bean injects a @RefreshScope bean and stores it as a field, the singleton will always see the original instance even after refresh. Use ObjectProvider<T> or annotate the singleton itself with @RefreshScope.
Production Insight
After calling /actuator/refresh, check the response body for the list of changed keys. If expected keys are missing, the Config Server's Git clone may be stale — issue a refresh on the Config Server itself or reduce spring.cloud.config.server.git.refresh-rate.
Key Takeaway
@RefreshScope works through CGLIB proxies — beans must always be accessed via the Spring proxy, never through stored concrete references, for dynamic refresh to work correctly.

Spring Cloud Bus: Fleet-Wide Configuration Broadcast

Calling POST /actuator/refresh on every pod manually is impractical at scale. Spring Cloud Bus solves this by routing refresh events through a shared message broker — RabbitMQ or Kafka — so a single trigger refreshes every connected instance simultaneously. The bus integration adds two endpoints to the Config Server: /actuator/busrefresh (refresh all instances) and /actuator/busrefresh/{destination} (refresh a specific service:instance).

The canonical production flow is: developer pushes config to Git → Git webhook calls POST /actuator/busrefresh on the Config Server → Config Server publishes a RefreshRemoteApplicationEvent to the bus topic → all microservices subscribed to the topic receive the event → each instance calls its local /actuator/refresh logic → @RefreshScope beans reload their @Value fields.

Setting up Spring Cloud Bus requires adding spring-cloud-starter-bus-amqp (RabbitMQ) or spring-cloud-starter-bus-kafka to every service that needs to participate, including the Config Server itself. All services must connect to the same broker. The default exchange/topic is named springCloudBus for AMQP and springCloudBus for Kafka.

For Kafka-backed Bus, configure the default topic: spring.cloud.bus.destination=springCloudBus. Each application instance creates a consumer group based on its spring.application.name and instance index, ensuring each pod receives its own copy of the event (broadcast semantics, not work-queue semantics). Verify this with kafka-consumer-groups.sh — you should see one consumer group per service name, not one shared group.

Webhook security is critical. Your Git provider (GitHub, GitLab, Bitbucket) will send a POST request to your Config Server's public endpoint. Protect this endpoint with a shared secret header or IP allowlist. Spring Security can restrict /actuator/busrefresh to requests bearing a specific token. Alternatively, put the Config Server behind an API gateway and configure the webhook target to the gateway with authentication.

Bus Requires Identical Broker Config on All Services
If even one service has a wrong RabbitMQ host or wrong virtual-host, it will silently fail to subscribe. Always verify bindings with rabbitmqctl list_bindings | grep springCloudBus after deploying a new service.
Production Insight
Use /actuator/busrefresh/{destination} with destination=payment-service:** to refresh only one service during a canary deployment. This avoids triggering restarts across the entire fleet when you are validating a config change for a single service.
Key Takeaway
Spring Cloud Bus turns a per-pod manual curl into a single Git webhook. Every participating service must have the bus starter on its classpath and connect to the same broker — missing either silently excludes that service from fleet refreshes.

Encrypting Secrets with {cipher} and Vault Backend

Storing plaintext credentials in a Git repository is a security anti-pattern that violates SOC 2, PCI DSS, and most corporate security policies. Spring Cloud Config Server provides two solutions: built-in {cipher} encryption using AES or RSA, and full HashiCorp Vault backend integration.

For {cipher} encryption, the Config Server exposes /encrypt and /decrypt endpoints. You POST a plaintext value to /encrypt and receive an encrypted hex string. Store this in your Git repo as {cipher}EncryptedHexString. When serving this property to a client, the server automatically decrypts it before including it in the response. Clients never see or handle encrypted values — they receive plaintext, so no client-side changes are needed.

Symmetric encryption uses a single key configured via encrypt.key (or the ENCRYPT_KEY environment variable). RSA encryption uses a keystore configured with encrypt.keyStore.location, .alias, .password, and .secret. RSA is preferred in regulated environments because it allows you to rotate the private key without re-encrypting all values, and you can distribute the public key to teams who need to encrypt new secrets without giving them decryption access.

Vault backend is the enterprise-grade alternative. Configure spring.cloud.config.server.vault.* to point Config Server at your Vault cluster. Services can authenticate with their Vault tokens, AppRole credentials, or Kubernetes service account JWTs. Vault provides dynamic secrets (short-lived database credentials generated on demand), automatic lease renewal, and detailed audit logs of every secret access — capabilities that static {cipher} encryption cannot match.

For composite backends, combine Git (non-secret config) with Vault (secrets) using the composite repository configuration. The Config Server merges property sources from both backends before serving clients, so services see a unified environment without knowing that some properties came from Vault.

Key management is the operational burden with {cipher} encryption. When you rotate the encryption key, all existing {cipher} values must be re-encrypted. Establish a runbook: decrypt all values with the old key, re-encrypt with the new key, commit, verify, then update the server's key configuration. Vault solves this with lease rotation, making it the preferred choice for mature production deployments.

Never Commit Unencrypted Secrets to Git
Git history is permanent. Even if you delete a plaintext secret and commit the deletion, it remains in git log. If credentials are ever committed in plaintext, rotate them immediately — do not rely on squashing or rewriting history as the sole remediation.
Production Insight
Use Vault's AppRole authentication for Config Server in Kubernetes instead of a static Vault token. AppRole roleId/secretId can be delivered via Kubernetes secrets and rotated without modifying the Config Server deployment.
Key Takeaway
{cipher} encryption gets you out of plaintext-in-Git quickly; Vault backend gives you dynamic secrets, audit logs, and lease rotation for mature production environments where compliance and rotation automation matter.

Profile-Specific Configuration and Hierarchical Overrides

Spring Cloud Config Server resolves configurations in a layered hierarchy that mirrors Spring's standard property source precedence. Understanding this hierarchy prevents the common confusion of why a value is not what you expect.

The resolution order (highest to lowest precedence) for a request to /payment-service/prod is: payment-service-prod.yml > payment-service.yml > application-prod.yml > application.yml. The server merges these files and returns a unified property source. This lets you define global defaults in application.yml (shared across all services), service-wide defaults in payment-service.yml, environment overrides in application-prod.yml, and service-plus-environment specific values in payment-service-prod.yml.

A common pattern is to keep shared infrastructure configuration (like logging levels, actuator settings, tracing configuration) in application.yml and let each service own only what differs. This dramatically reduces duplication. When you want to change the global log level from INFO to DEBUG for a production incident, one commit to application.yml + one busrefresh propagates to the entire fleet.

Profile activation on the client side follows normal Spring rules: spring.profiles.active=prod in application.yml or the SPRING_PROFILES_ACTIVE environment variable. In Kubernetes, set this as an environment variable in the Deployment spec so the same Docker image behaves differently per environment without rebuilding.

Label-based configuration (mapping to Git branches or tags) enables config promotion workflows. Maintain a config/dev branch for development, config/staging for staging, and config/prod for production. Clients in each environment set spring.cloud.config.label=config/prod accordingly. Promoting config to production becomes a Git merge with a pull request review — exactly the same workflow used for code changes, complete with diff visibility and approval gates.

Pattern-based repository selection allows routing different services to different Git repositories: spring.cloud.config.server.git.repos.payment.uri and spring.cloud.config.server.git.repos.payment.pattern=payment- routes all services matching payment- to a dedicated repository. This is useful for compliance isolation — keeping PCI-scoped services' configuration in a repository with stricter access controls.

Profile Ordering Matters in Client Config
When spring.profiles.active=prod,cloud the server resolves /payment-service/prod,cloud and merges configs for both profiles. Properties in payment-service-cloud.yml may override payment-service-prod.yml depending on the merge order. Always test multi-profile configs explicitly.
Production Insight
Use config/prod as a Git branch for production config rather than using the main branch. This allows a PR-review-gated promotion workflow: dev pushes to config/staging, staging verifies, then a PR merges to config/prod. Production config changes become auditable, reviewable events.
Key Takeaway
The four-level hierarchy (application.yml → application-{profile}.yml → {app}.yml → {app}-{profile}.yml) lets you minimize duplication by defining defaults globally and overriding only what differs per service or environment.

High Availability and Failure Modes

Config Server is a critical dependency for all microservices that read remote configuration at startup. A Config Server outage during a rolling deployment means new pods fail to start, potentially degrading service capacity. Designing Config Server for high availability is not optional in production.

The simplest HA strategy is running multiple Config Server instances behind a load balancer. Since Config Server is stateless (it reads from Git and serves HTTP), horizontal scaling is straightforward. Register Config Server instances in Eureka and configure clients with spring.cloud.config.discovery.enabled=true so they locate the server through service discovery. This eliminates the hardcoded config server URL and allows zero-downtime Config Server rolling updates.

Git connectivity is the primary failure mode. If the Git host is unreachable, the Config Server cannot fetch updated config but can still serve from its local clone (the basedir cache). This behavior is configurable — by default, the server falls back to the local cache if Git is unreachable. This means a transient Git outage does not prevent clients from starting, but they will receive the last-known-good config rather than the latest. For disaster recovery, size the basedir persistent volume to hold the full repository.

Client-side resilience requires spring.cloud.config.fail-fast=true with retry configuration. Without fail-fast, a client will start with no remote config and use only local application.yml defaults — potentially in a broken state with missing required properties. With fail-fast=true, the client retries the config server connection with exponential backoff and fails the startup if it cannot connect after the configured attempts. This is the safer behavior in production.

For truly critical services, consider caching the last-received config locally in a ConfigMap or file so the service can start even if Config Server is completely unavailable. Spring Boot's file-based property source or environment variables can serve as a last-resort fallback. Some teams implement a sidecar that periodically writes config snapshots to a local file, which the main container reads as a fallback source.

Health monitoring should include a custom HealthIndicator that periodically verifies the Config Server can reach Git. Expose this via /actuator/health and alert on failure. Combine this with synthetic monitoring that POSTs to /actuator/refresh every 5 minutes to detect silent Git connectivity failures before they affect deployments.

fail-fast=false Can Hide Config Problems Until Runtime
Without fail-fast=true, a service that cannot reach Config Server starts silently with local defaults. It may appear healthy in health checks but behave incorrectly because required remote properties are absent. Always use fail-fast=true in production.
Production Insight
Run at least 2 Config Server instances in production. Schedule their rolling updates during low-traffic windows and configure your load balancer health checks against /actuator/health so traffic drains from an instance before it is stopped.
Key Takeaway
Config Server HA requires multiple instances behind a load balancer, client-side fail-fast with retry, and periodic health monitoring of the Git connectivity — the local clone cache buys time during Git outages but is not a substitute for a healthy upstream.
● Production incidentPOST-MORTEMseverity: high

Silent Stale Config: Why @RefreshScope Did Not Refresh

Symptom
After merging a Git commit that changed feature.payments.newFlow=true, operations called POST /actuator/refresh on all 24 payment-service pods. The endpoint returned 200 with the changed key listed. Yet the old code path kept executing. Logs showed the flag was still false inside the PaymentProcessor bean.
Assumption
The team assumed @RefreshScope on PaymentProcessor was sufficient. They verified the refresh endpoint responded correctly and assumed the bean re-read its @Value.
Root cause
PaymentProcessor was @RefreshScope but it was injected into PaymentController, which was a singleton. When the context refreshed, Spring replaced the PaymentProcessor proxy — but the singleton PaymentController held a direct field reference to the old concrete instance that had been unwrapped from the CGLIB proxy during construction. The proxy shell was refreshed; the stale object reference inside the singleton was not.
Fix
Injected PaymentProcessor as a provider: @Autowired private ObjectProvider<PaymentProcessor> processorProvider; and called processorProvider.getObject() at invocation time. Alternatively, the team could annotate PaymentController itself with @RefreshScope, which forces the entire call chain to refresh. They chose the ObjectProvider approach to avoid CGLIB proxying the controller.
Key lesson
  • @RefreshScope only works when the refreshed bean is accessed through its Spring proxy at runtime — never through a stored concrete reference inside a singleton.
  • Always audit the call chain: if a @RefreshScope bean is injected into a singleton, the refresh will silently do nothing for that singleton's view of the bean.
Production debug guideSymptom → root cause → fix6 entries
Symptom · 01
Client starts with default values instead of Config Server properties
Fix
Verify spring.config.import=configserver:http://config-server:8888 is present in application.yml (Spring Boot 3.x no longer uses bootstrap.yml by default). Check that spring.application.name is set correctly — Config Server uses this to resolve /applicationName/profile/label. Confirm the config server is reachable from the client pod by curling http://config-server:8888/applicationName/default directly and inspecting the JSON response.
Symptom · 02
POST /actuator/refresh returns 200 but beans still see old values
Fix
Check that the bean consuming the property is annotated @RefreshScope and that it is always accessed through its Spring proxy (never stored as a raw reference in a singleton field). Use GET /actuator/env to confirm the Environment itself has been updated — if the Environment shows the new value but the bean does not, the issue is proxy bypass. Enable TRACE logging for org.springframework.cloud.context to see which beans are being refreshed.
Symptom · 03
Spring Cloud Bus refresh event published but only some pods refresh
Fix
Verify all pods are connected to the same RabbitMQ vhost or Kafka topic (springCloudBus). Check spring.rabbitmq.* or spring.kafka.bootstrap-servers are consistently configured. Inspect the bus exchange/topic for message delivery with rabbitmqctl list_bindings or kafka-consumer-groups.sh --describe. Ensure spring-cloud-starter-bus-amqp or spring-cloud-starter-bus-kafka is on all client classpaths, not just the config server.
Symptom · 04
Config Server fails to start with 'Could not resolve placeholder' for Git URI
Fix
The Git backend credentials (spring.cloud.config.server.git.username/password or deploy key) may be invalid or the URI format is wrong. Test with git clone <uri> locally using the same credentials. For SSH URIs, ensure the known_hosts entry for the Git host is present in the server's ~/.ssh/known_hosts or configure spring.cloud.config.server.git.strict-host-key-checking=false (not recommended for production). Check spring.cloud.config.server.git.clone-on-start=true so failures surface at startup rather than first request.
Symptom · 05
{cipher} values appear decrypted as the literal cipher text in the client
Fix
The encryption key is likely not configured on the Config Server (encrypt.key or the keystore properties). Verify POST http://config-server:8888/encrypt with a test value returns a hex string. Check the server has spring-security-rsa on the classpath for RSA encryption. Confirm ENCRYPT_KEY environment variable is set in the Config Server deployment. If using Spring Cloud Bus with encryption, ensure the key is available on the Config Server, not on clients — clients receive already-decrypted values.
Symptom · 06
Config Server returns 404 for a valid application/profile combination
Fix
The file naming convention must match exactly: {applicationName}.yml or {applicationName}-{profile}.yml in the Git repository root (or the configured search-paths). Check spring.cloud.config.server.git.search-paths if files are in subdirectories. Use GET /config-server/applicationName/profile/label directly and check the sources array in the response — an empty sources array with no error means the file was not found, not that it could not be read.
★ Debug Cheat SheetCopy-paste commands for diagnosing Config Server issues in production
Client not picking up remote config
Immediate action
Verify client can reach config server and see the correct property sources
Commands
curl -s http://config-server:8888/my-service/prod | jq '.propertySources[].name'
curl -s http://my-service:8080/actuator/env | jq '.propertySources[] | select(.name | startswith("configserver"))'
Fix now
Ensure spring.config.import=configserver:http://config-server:8888 and spring.application.name=my-service are set in client application.yml
Refresh not propagating to all pods via Spring Cloud Bus+
Immediate action
Check RabbitMQ binding and manually publish a refresh event
Commands
rabbitmqctl list_bindings | grep springCloudBus
curl -s -X POST http://config-server:8888/actuator/busrefresh -H 'Content-Type: application/json'
Fix now
Confirm spring-cloud-starter-bus-amqp is on all client classpaths and spring.rabbitmq.host points to same broker
Cipher decryption failure+
Immediate action
Test encryption endpoint and verify key availability
Commands
curl -s -X POST http://config-server:8888/encrypt -d 'mysecret' -H 'Content-Type: text/plain'
curl -s -X POST http://config-server:8888/decrypt -d '<cipher-hex>' -H 'Content-Type: text/plain'
Fix now
Set ENCRYPT_KEY env var on config server pod and redeploy; ensure value in Git starts with {cipher} prefix
Git clone fails at Config Server startup+
Immediate action
Validate Git credentials and connectivity from the config server pod
Commands
kubectl exec -it config-server-pod -- git ls-remote https://github.com/org/config-repo.git
kubectl logs config-server-pod | grep -i 'git\|clone\|ssh\|auth' | tail -20
Fix now
Fix credentials in spring.cloud.config.server.git.username/password or mount a valid SSH deploy key secret
Config Server Backend Comparison
BackendAudit TrailDynamic SecretsEncryptionOperational ComplexityBest For
GitFull Git historyNo{cipher} AES/RSALowNon-secret config + small teams
Git + {cipher}Full Git historyNoAES or RSALow-MediumMost production deployments
VaultVault audit logYesVault transit engineHighPCI/SOC2 environments, dynamic DB creds
Composite Git+VaultBothYes (Vault paths)Both mechanismsHighBest of both: versioned non-secrets + dynamic secrets
AWS Parameter StoreCloudTrailNoKMS integrationMediumAWS-native deployments
Native FilesystemNoneNoNone built-inVery LowLocal development and CI only

Key takeaways

1
@EnableConfigServer + Git URI centralizes all microservice config with full audit trail; add clone-on-start, retry, and multiple instances for production readiness
2
@RefreshScope allows runtime config updates without JVM restarts, but only works when beans are accessed through Spring's CGLIB proxy
stored concrete references bypass the refresh mechanism
3
Spring Cloud Bus (RabbitMQ or Kafka) broadcasts refresh events fleet-wide from a single Git webhook trigger, eliminating the need to call /actuator/refresh on each pod individually
4
{cipher} encryption keeps secrets out of plaintext Git while remaining transparent to clients; Vault backend adds dynamic credentials and lease rotation for compliance-driven environments
5
The four-level property hierarchy (application → application-{profile} → {app} → {app}-{profile}) minimizes configuration duplication by defining shared defaults globally and overriding only differences per service or environment
6
fail-fast=true with retry is mandatory in production
without it, services start silently with wrong defaults when Config Server is temporarily unavailable

Common mistakes to avoid

7 patterns
×

Not setting clone-on-start=true

Symptom
First client deployment after Config Server restart takes 30+ seconds and occasionally times out with a Git authentication error that was not caught at startup
Fix
Set spring.cloud.config.server.git.clone-on-start=true and verify Config Server startup logs show a successful clone before declaring it healthy
×

Injecting @RefreshScope beans into singletons via constructor

Symptom
POST /actuator/refresh returns the changed keys but the running application continues using old configuration values
Fix
Inject @RefreshScope beans via ObjectProvider<T> in singletons, or annotate the singleton with @RefreshScope as well
×

Exposing /actuator/busrefresh without authentication

Symptom
Any internet-facing request can trigger a fleet-wide config refresh or busenv update, potentially allowing configuration injection attacks
Fix
Secure /actuator/busrefresh with Spring Security requiring a shared secret header or restrict it to internal network traffic via firewall rules
×

Using fail-fast=false (the default) in production

Symptom
Service starts and appears healthy but uses incorrect defaults because it could not reach Config Server during startup; bugs are discovered hours later
Fix
Always set spring.cloud.config.fail-fast=true with retry configuration so startup fails loudly if Config Server is unreachable
×

Storing unencrypted secrets in application-prod.yml in Git

Symptom
Database passwords, API keys visible in Git history and to anyone with repository read access; fails security audits
Fix
Use {cipher} encryption or Vault backend for any sensitive property; rotate all credentials that were ever committed in plaintext
×

Running only one Config Server instance

Symptom
Config Server pod restart or OOM kill during a deployment wave causes all pods that try to start during that window to fail, creating cascading failures
Fix
Run at least 2 Config Server replicas with a PodDisruptionBudget ensuring at least 1 is always available
×

Not setting refresh-rate on the Git backend

Symptom
Config pushed to Git is not picked up by Config Server even after waiting minutes; calling /actuator/refresh on clients returns the old value
Fix
Set spring.cloud.config.server.git.refresh-rate=30 to control polling frequency; without it the server may serve stale cached config indefinitely
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does @EnableConfigServer do and what are the minimal requirements t...
Q02SENIOR
Explain how @RefreshScope works internally and what can cause it to sile...
Q03SENIOR
How does Spring Cloud Bus propagate refresh events across all microservi...
Q04JUNIOR
What is the resolution order for property files in Config Server, and ho...
Q05SENIOR
How would you implement config encryption for a regulated environment, a...
Q06SENIOR
What is the role of spring.cloud.config.fail-fast and what happens if it...
Q07SENIOR
How would you implement a Git-branch-based config promotion workflow fro...
Q08SENIOR
How do you handle Config Server high availability and what happens to cl...
Q09JUNIOR
What is the difference between /actuator/refresh and /actuator/busrefres...
Q01 of 09JUNIOR

What does @EnableConfigServer do and what are the minimal requirements to make it functional?

ANSWER
@EnableConfigServer activates the Spring Cloud Config Server auto-configuration, turning the Spring Boot application into an HTTP-based configuration server. The minimal requirements are: the spring-cloud-config-server dependency on the classpath, @EnableConfigServer on the main class, and at least one backend configured — for Git, this means spring.cloud.config.server.git.uri pointing to a valid repository. The server will then serve configurations at /{application}/{profile}/{label}.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
Do I need a separate Config Server per environment (dev/staging/prod)?
02
How does Config Server interact with Spring Boot's bootstrap context in version 3.x?
03
Can Config Server serve configuration to non-Spring applications?
04
What happens if two clients have the same spring.application.name?
05
How do I test Config Server integration locally without a real Git repository?
06
Can I use Spring Cloud Config Server with Kubernetes ConfigMaps?
07
How do I roll back a bad config change?
🔥

That's Spring Cloud. Mark it forged?

11 min read · try the examples if you haven't

Previous
Circuit Breaker Pattern with Resilience4j
5 / 8 · Spring Cloud
Next
Distributed Tracing with Zipkin and Micrometer