External Secrets Operator (ESO) bridges your Kubernetes cluster and external secret stores — AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Azure Key Vault, and more. It continuously fetches secrets from these providers and syncs them into Kubernetes Secret objects that your applications consume. When ESO is healthy, the sync is invisible. When it fails, your pods can't start, applications crash on secret reads, and the error surfaces far from the root cause — a developer sees ImagePullBackOff or CrashLoopBackOff rather than "ESO lost connectivity to Vault." Vigilmon monitors ESO from outside the cluster so you know about sync failures within minutes, before they cascade into application incidents.
What You'll Build
- HTTP monitors for ESO's webhook and controller health endpoints
- A heartbeat monitor to verify secret sync is completing successfully
- Provider-specific connectivity checks
- Alert channels that notify your platform and security teams together
Prerequisites
- External Secrets Operator installed in a Kubernetes cluster (v0.9+)
- At least one
SecretStoreorClusterSecretStoreconfigured and syncing - An Ingress exposing ESO health endpoints over HTTPS
- A free account at vigilmon.online
Why ESO Monitoring Requires an External Perspective
ESO failure modes don't always surface as Kubernetes pod failures:
Provider token expiry is silent. If the IAM role, Vault token, or service account credentials ESO uses to authenticate expire, the controller keeps running — it just can't fetch new secret values. Existing Secret objects persist, so currently-running pods are fine. But the next time ESO tries to refresh a rotating secret, it fails silently. You won't notice until a pod restarts and can't find its credentials.
Network partitions between ESO and the provider are invisible to the cluster. ESO pods report Running and Ready even when they can't reach AWS Secrets Manager or Vault. Kubernetes has no way to know the external connectivity has failed.
Webhook downtime blocks ExternalSecret creation. Like Kyverno and Crossplane, ESO registers a validating/mutating webhook for ExternalSecret and SecretStore resources. If the webhook is unreachable, creating or updating ExternalSecret objects fails with admission errors — which means new deployments that depend on secrets can't launch.
Sync interval drift goes undetected. ESO refreshes secrets on a configurable interval (often 1 hour). If the controller is OOM-killed and restarts frequently, the effective refresh interval can stretch far beyond the configured value without triggering any alert.
Step 1: Identify ESO Health Endpoints
ESO exposes health endpoints on its controller and webhook pods:
# Controller health endpoints (port 8081)
kubectl exec -n external-secrets deploy/external-secrets -- \
wget -qO- http://localhost:8081/healthz
# Webhook health (port 10250)
kubectl exec -n external-secrets deploy/external-secrets-webhook -- \
wget -qO- http://localhost:10250/healthz
Both return HTTP 200 when healthy. Check the deployments running in your cluster:
kubectl get deployments -n external-secrets
# Typically: external-secrets, external-secrets-webhook, external-secrets-cert-controller
Step 2: Expose Health Endpoints via Ingress
Create Services and an Ingress for external monitoring:
# eso-health-ingress.yaml
---
apiVersion: v1
kind: Service
metadata:
name: eso-controller-health
namespace: external-secrets
spec:
selector:
app.kubernetes.io/name: external-secrets
app.kubernetes.io/component: controller
ports:
- port: 8081
targetPort: 8081
---
apiVersion: v1
kind: Service
metadata:
name: eso-webhook-health
namespace: external-secrets
spec:
selector:
app.kubernetes.io/name: external-secrets
app.kubernetes.io/component: webhook
ports:
- port: 10250
targetPort: 10250
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: eso-health
namespace: external-secrets
spec:
rules:
- host: eso-health.internal.yourdomain.com
http:
paths:
- path: /controller/healthz
pathType: Prefix
backend:
service:
name: eso-controller-health
port:
number: 8081
- path: /webhook/healthz
pathType: Prefix
backend:
service:
name: eso-webhook-health
port:
number: 10250
Apply and verify:
kubectl apply -f eso-health-ingress.yaml
curl https://eso-health.internal.yourdomain.com/controller/healthz
# Expected: {"status":"ok"}
Step 3: Add Vigilmon HTTP Monitors
In the Vigilmon dashboard:
- Go to Add Monitor → HTTP
- Create the following monitors:
| Monitor Name | URL | Severity |
|---|---|---|
| ESO controller /healthz | https://eso-health.yourdomain.com/controller/healthz | High |
| ESO webhook /healthz | https://eso-health.yourdomain.com/webhook/healthz | Critical |
| ESO webhook TLS cert | https://eso-webhook.yourdomain.com | Critical |
Settings for each monitor:
- Check interval: 1 minute
- Timeout: 10 seconds
- Expected status code: 200
- Alert after: 2 consecutive failures
For the webhook TLS cert monitor, enable SSL certificate expiry alerts with a 14-day warning threshold. ESO's webhook TLS certificate is managed by its cert-controller — if that component fails, the certificate eventually expires and the webhook stops accepting connections.
Step 4: Heartbeat Monitor for Secret Sync
The most important thing to verify isn't that ESO's pods are alive — it's that secrets are actually being synced successfully. Create a CronJob that checks ExternalSecret sync status across your namespaces:
# eso-sync-heartbeat.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: eso-sync-heartbeat
namespace: external-secrets
spec:
schedule: "*/15 * * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
serviceAccountName: eso-sync-heartbeat
containers:
- name: heartbeat
image: bitnami/kubectl:latest
env:
- name: HEARTBEAT_URL
valueFrom:
secretKeyRef:
name: vigilmon-secrets
key: eso-heartbeat-url
- name: CHECK_NAMESPACES
value: "production,staging"
command:
- /bin/sh
- -c
- |
set -e
FAILED=0
for NS in $(echo "$CHECK_NAMESPACES" | tr ',' ' '); do
# Count ExternalSecrets that are NOT Ready
NOT_READY=$(kubectl get externalsecrets -n "$NS" \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}' \
2>/dev/null | grep -v "True" | grep -v "^$" | wc -l)
if [ "$NOT_READY" -gt 0 ]; then
echo "WARN: $NOT_READY ExternalSecrets not Ready in namespace $NS"
kubectl get externalsecrets -n "$NS" \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.conditions[?(@.type=="Ready")].status}{" "}{.status.conditions[?(@.type=="Ready")].message}{"\n"}{end}' \
| grep -v "True"
FAILED=$((FAILED + NOT_READY))
fi
done
if [ "$FAILED" -gt 0 ]; then
echo "FAIL: $FAILED ExternalSecrets are not syncing correctly"
exit 1
fi
wget -qO- "$HEARTBEAT_URL"
echo "ESO sync heartbeat sent — all ExternalSecrets are Ready"
Set up RBAC and the secret:
# Create a ClusterRole with read access to ExternalSecrets
kubectl create clusterrole eso-heartbeat-reader \
--verb=get,list \
--resource=externalsecrets.external-secrets.io
kubectl create serviceaccount eso-sync-heartbeat -n external-secrets
kubectl create clusterrolebinding eso-heartbeat-reader \
--clusterrole=eso-heartbeat-reader \
--serviceaccount=external-secrets:eso-sync-heartbeat
kubectl create secret generic vigilmon-secrets \
--from-literal=eso-heartbeat-url='https://vigilmon.online/heartbeat/YOUR_TOKEN' \
-n external-secrets
In Vigilmon, create the heartbeat monitor:
- Add Monitor → Heartbeat
- Name:
ESO secret sync check - Expected interval: 15 minutes
- Grace period: 5 minutes
If any ExternalSecret fails to sync — whether due to provider connectivity loss, token expiry, or a misconfigured secret path — the heartbeat won't fire, and Vigilmon opens an incident.
Step 5: Provider Connectivity Checks
ESO supports many secret providers. Add a dedicated connectivity check for your primary provider:
AWS Secrets Manager
# Verify ESO can reach AWS by checking ClusterSecretStore status
kubectl get clustersecretstores -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}'
Create a separate heartbeat that specifically validates the ClusterSecretStore is Ready:
# In the heartbeat script, add:
STORE_READY=$(kubectl get clustersecretstores \
-o jsonpath='{range .items[*]}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}' \
| grep -c "True")
STORE_TOTAL=$(kubectl get clustersecretstores --no-headers | wc -l)
if [ "$STORE_READY" -lt "$STORE_TOTAL" ]; then
echo "FAIL: Only $STORE_READY/$STORE_TOTAL ClusterSecretStores are Ready"
exit 1
fi
HashiCorp Vault
Add a separate Vigilmon HTTP monitor directly against Vault's health endpoint (if Vault is self-hosted within your infrastructure):
URL: https://vault.internal.yourdomain.com/v1/sys/health
Expected status: 200
Response body contains: "initialized":true
This monitors Vault independently of ESO — if Vault goes down, you know it before ESO starts failing sync.
Step 6: Configure Alert Channels
Route ESO alerts to the right teams:
Webhook downtime — page on-call (blocks new deployments):
- Alert Channels → Add Channel → PagerDuty
- Assign to:
ESO webhook /healthz,ESO webhook TLS cert
Sync failures and controller issues — notify platform and security (Slack):
- Alert Channels → Add Channel → Slack Webhook
- Paste your
#secrets-alertsor#platform-alertswebhook URL - Assign to:
ESO controller /healthz,ESO secret sync checkheartbeat
Complete Monitoring Coverage
| Component | Monitor Type | What It Detects |
|---|---|---|
| ESO controller | HTTP /healthz | Controller pod crash |
| ESO webhook | HTTP /healthz | Webhook pod crash (blocks ExternalSecret creation) |
| Webhook TLS cert | SSL expiry | Certificate expiry causes admission failures |
| ExternalSecret sync | Heartbeat | Sync failures, provider connectivity loss |
| ClusterSecretStore | Heartbeat (embedded) | Provider auth expiry, network issues |
| Vault (if self-hosted) | HTTP /v1/sys/health | Upstream secret store availability |
Conclusion
External Secrets Operator is a critical piece of security infrastructure — it's the reason your pods don't need to embed credentials directly. That criticality means its failure modes deserve the same monitoring rigor as your primary API services. With Vigilmon, you get external monitoring that validates not just that ESO's pods are running, but that secrets are actually being synced to the applications that depend on them.
Set up your first ESO monitor free at vigilmon.online. The heartbeat-based sync check gives you meaningful coverage in under 10 minutes of setup.