Fix SecretStore, Auth, and Sync Failures in External Secrets Operator

Updated on Sep 9, 2026
Kimberly N
9 MINS READ
Table of Contents
Fix External Secrets Operator Errors

External Secrets Operator (ESO) lets Kubernetes pull secrets from tools like AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, and OpenBao. When it works, you forget it's even there. When it breaks, your app won't start because a Secret is missing, empty, or old. This guide is a simple runbook for External Secrets Operator troubleshooting.

How ESO Reconciliation Actually Works

Before troubleshooting, you need to know the flow ESO follows, because every fix in this guide maps to one step in this loop:

  1. The controller reads your ExternalSecret object.
  2. It looks up the SecretStore or ClusterSecretStore named in secretStoreRef.
  3. It authenticates to the provider using the credentials in that store.
  4. It fetches the secret value from the provider.
  5. It applies any template or data transformation.
  6. It writes or updates a Kubernetes Secret object.
  7. It waits for refreshInterval, then repeats the loop.

Every error you see in External Secrets Operator troubleshooting comes from one of these seven steps. Once you know which step failed, the fix becomes easy.

Step 1: Confirm the Controller Is Healthy

The first step is always to check that the controller is healthy. If the controller pod is crashing, nothing will work. To check the controller, run the commands below:

Bash
kubectl get pods -n external-secretskubectl describe pod -n external-secrets -l app.kubernetes.io/name=external-secretskubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets --tail=200

Then, check what version is actually running, because mismatched CRD and controller versions cause silent failures:

Bash
kubectl get deployment external-secrets -n external-secrets -o jsonpath='{.spec.template.spec.containers[0].image}'

If the image tag doesn't match your Helm chart version, upgrade the CRDs and controller together using server-side apply. This has been required since v0.19, and it's still required on v2.x, because the CRDs are too big for the old kubectl size limit:

Bash
kubectl apply -f "https://raw.githubusercontent.com/external-secrets/external-secrets/v2.10.0/deploy/crds/bundle.yaml" --server-side helm repo update external-secretshelm upgrade external-secrets external-secrets/external-secrets \  -n external-secrets --create-namespace \  --version 2.10.0

Step 2: Read the ExternalSecret Status

This is the most important step in External Secrets Operator troubleshooting. It is recommended to start with kubectl describe on the ExternalSecret resource:

Bash
kubectl describe externalsecret <name> -n <namespace>

In the output, you must look for the following fields:

  • Conditions → Type: Ready, Status: Should be True. If it is False, read the Message and Reason fields; they tell you exactly what failed.
  • Events: Any Warning event tells you the error from the last reconcile attempt.

A healthy object looks like this:

Bash
Status:  Conditions:    Type:    Ready    Status:  True    Reason:  SecretSynced    Message: Secret was synced  Refresh Time: 2026-09-04T08:12:00Z

A broken one shows Reason: SecretSyncedError, with a message that tells you the cause, like a missing key or an auth failure. This one field saves you the most time in External Secrets Operator troubleshooting, because it names the exact step that failed in the reconciliation loop.

Step 3: Fix a SecretStore That Is Not Ready

If the ExternalSecret event mentions the store, or if you see errors like could not get secret data from provider, check the store itself:

Bash
kubectl describe secretstore <store-name> -n <namespace># or for a cluster-wide storekubectl describe clustersecretstore <store-name>

A store stuck in Status: False with Reason: Invalid means one of these three things:

  • The provider endpoint in spec.provider is wrong, such as a bad URL, wrong region, or wrong project ID.
  • The referenced auth secret does not exist in the namespace ESO expects.
  • Network access from the cluster to the provider is blocked, such as a firewall, private endpoint, or DNS.

Test the network path directly from a pod in the same namespace as the controller, since ESO cannot reach providers your cluster network blocks:

Bash
kubectl run netcheck --rm -it --image=busybox --restart=Never -- \  nslookup vault.example.com

If DNS or TCP fails here, the SecretStore will never become Ready, no matter how correct your YAML is.

Step 4: Fix Provider Authentication Failures

Once the store is Ready but the ExternalSecret still fails, the problem depends on authentication. Each provider has its own common failure pattern:

Provider Typical Error Message Root Cause Fix
AWS Secrets Manager AccessDeniedException or InvalidSignatureException Wrong IAM policy or expired IRSA token Attach secretsmanager:GetSecretValue to the role; confirm IRSA annotation on the ServiceAccount
HashiCorp Vault permission denied or 403 Vault policy missing path, or wrong Kubernetes auth role Check vault policy read <policy> and confirm the auth role's bound service account and namespace
Azure Key Vault Forbidden or unauthorized_client Managed identity not assigned, or wrong tenant ID Verify the federated identity credential and Key Vault access policy or RBAC role
GCP Secret Manager PermissionDenied Workload Identity binding missing Confirm the GSA has roles/secretmanager.secretAccessor and the KSA is bound correctly
OpenBao authentication error Kubernetes auth backend not enabled or wrong role bound Confirm auth/kubernetes/config and the role's bound service account match the ESO ServiceAccount

Pull the exact authentication error from the controller logs, filtered by your ExternalSecret's namespace:

Bash
kubectl logs -n external-secrets deployment/external-secrets \  | grep -i "auth" | tail -50

For Vault, you must check that the Kubernetes auth role is bound to the correct ServiceAccount and namespace:

Bash
vault read auth/kubernetes/role/<role-name>

Provider authentication failures are the most frequent reason people search for External Secrets Operator troubleshooting help, because the error message from the SDK is generic and does not point to the actual misconfigured field.

Step 5: Fix Missing Keys, Conversion, and Template Errors

If auth succeeds but the ExternalSecret still shows SecretSyncedError, the message names a missing key or a bad template.

Missing key error looks like key not found or secret does not have key <name>. This means the remoteRef.key or remoteRef.property in your ExternalSecret spec does not match what actually exists in the provider. Confirm the exact key name directly in the provider, not from memory:

Bash
aws secretsmanager get-secret-value --secret-id <name> --query SecretString --output textvault kv get secret/<path>

Conversion or decoding error happens when ESO tries to base64-decode a value that isn't valid base64, or when dataFrom.extract expects JSON but gets plain text instead. Check spec.dataFrom in your ExternalSecret. And if you're pulling multiple keys from one secret, make sure that secret is actually stored as JSON.

Template error shows up as failed to execute template when spec.target.template uses wrong Go template syntax, like pointing to a field name that doesn't exist in the fetched data. Test the template locally before applying it. Always match field names with the real keys returned by dataFrom.extract.

Step 6: Fix Stale Secrets and Refresh Failures

A secret that still shows the old value is one of the most confusing issues in External Secrets Operator troubleshooting. Because the ExternalSecret can report Ready: True while the actual Kubernetes Secret content is old.

First, you must check spec.refreshPolicy. If it is set to CreatedOnce, ESO creates the Secret once and never updates it again. Set it to Periodic, which is the default, if you want ongoing updates.

Second, you must check the refreshTime field to confirm a sync attempt happened recently:

Bash
kubectl get externalsecret <name> -n <namespace> -o yaml | grep refreshTime

Third, remember that ESO updates the Secret object, not your running pod's environment variables. If your app reads secrets through env or envFrom, it only picks up new values on pod restart. Roll the deployment after a secret rotation if your app does not reload secrets from mounted files:

Bash
kubectl rollout restart deployment <your-app>

To force an immediate refresh instead of waiting for refreshInterval, annotate the resource:

Bash
kubectl annotate externalsecret <name> -n <namespace> force-sync=$(date +%s) --overwrite

For a ClusterExternalSecret, use the namespaced annotation instead:

Bash
kubectl annotate clusterexternalsecret <name> external-secrets.io/force-sync=$(date +%s) --overwrite

Step 7: Fix RBAC Problems

RBAC issues show up as forbidden errors in the controller logs, or as an ExternalSecret that never even attempts to reconcile. ESO's own ServiceAccount needs permission to read Secrets and write to ExternalSecret status; this is bundled by the Helm chart by default. So you must check whether someone has overridden the default ClusterRole:

Bash
kubectl get clusterrolebinding | grep external-secretskubectl describe clusterrole external-secrets-controller

If you use namespace-scoped SecretStore resources with a serviceAccountRef for provider auth, confirm the referenced ServiceAccount exists in the same namespace and has the correct annotations:

Bash
kubectl get serviceaccount <sa-name> -n <namespace> -o yaml

A common RBAC mistake in External Secrets Operator troubleshooting is setting up the cloud IAM role correctly, but forgetting to bind it to the right Kubernetes ServiceAccount. ESO actually uses the ServiceAccount named inside the SecretStore, not its own controller ServiceAccount.

Step 8: Read Controller Events and Logs Together

When status conditions don't give you enough detail, you can match the ExternalSecret's Kubernetes events with the controller logs from the same timestamp:

Bash
kubectl get events -n <namespace> --field-selector involvedObject.name=<externalsecret-name> --sort-by='.lastTimestamp'kubectl logs -n external-secrets deployment/external-secrets --since=10m

Match the timestamps. The event tells you what the controller decided; the log tells you why. For example, if you see a SecretSyncedError event paired with a log line showing an HTTP 429 from the provider, the real fix is rate limiting or backoff tuning, not a credentials change.

If reconciliation seems to loop endlessly without settling, check whether the provider's error message keeps changing slightly on every attempt. This is a known issue with some providers, and it forces ESO to keep retrying instead of reaching a stable Ready state. Once you fix the provider-side issue, restarting the controller pod clears a stuck reconcile loop:

Bash
kubectl rollout restart deployment external-secrets -n external-secrets

Key Commands for External Secrets Operator Troubleshooting

Once you've found the failing step, these are the exact commands to check it, fix it, or force a retry. Save these commands as your quick reference for everyday ESO work:

Task Command
Check ExternalSecret status kubectl describe externalsecret <name>
Check SecretStore status kubectl describe secretstore <name>
Check controller logs kubectl logs -n external-secrets deployment/external-secrets
Force a refresh kubectl annotate externalsecret <name> force-sync=$(date +%s) --overwrite
List all ESO resources kubectl get secretstores,clustersecretstores,externalsecrets -A
Check last sync time kubectl get es <name> -o yaml \| grep refreshTime

Keep ESO in an Isolated Environment

Provider auth and network errors are hard to diagnose when your cluster shares noisy traffic or unpredictable DNS with other workloads. You can use an isolated PerLod environment to keep secret-provider networking and cluster traffic predictable.

Explore dedicated server hosting at PerLod for an isolated environment to run and test your Kubernetes and ESO setup.

Conclusion

External Secrets Operator troubleshooting depends on checking things in order. Most failures appear in the status field before you ever need to touch the provider logs. 

If you're setting up ESO from scratch, you can follow our guide to the External Secrets Operator with OpenBao. Then, use this runbook for when something breaks.

Your refreshPolicy may be set to CreatedOnce, or your pod is not restarting to pick up the new environment variables.

Add or update the force-sync annotation on the ExternalSecret with kubectl annotate.

Because of a wrong provider URL, a missing auth secret, or a blocked network path to the provider.

Wrong IAM role, expired token, wrong Vault auth role, or a ServiceAccount that is not bound correctly.