ArgoCD stuck OutOfSync — every cause and fix
Diagnose and fix ArgoCD Applications showing OutOfSync. Manifest drift, RBAC issues, sync policies, and self-heal misconfigurations covered.
ArgoCD showing an Application as OutOfSync means the live cluster state differs from what Git specifies. Sometimes this is expected. Often it indicates a real problem. Here are all the causes and how to fix each.
Understand what OutOfSync actually means
ArgoCD compares:
- Desired state: Kubernetes manifests in your Git repo
- Live state: what actually exists in the cluster
If they differ, status = OutOfSync. Sync (manual or auto) applies the Git version to the cluster.
Healthy status is Synced + Healthy. Anything else needs investigation.
Diagnose first — see the difference
argocd app diff <app-name>
Or in the UI: click the app → “APP DIFF” tab.
This shows exactly what changed between Git and cluster. The fix depends on which side has the “right” version.
Cause 1 — Someone edited resources directly with kubectl
Most common cause. A developer ran kubectl edit deployment to test something. That change isn’t in Git. ArgoCD sees drift.
Two options:
Option A — accept the direct change and update Git:
kubectl get deployment <name> -o yaml > my-deployment.yaml
# edit to remove runtime fields, commit to git
git add my-deployment.yaml
git commit -m "Sync recent deployment change"
git push
Option B — revert cluster to match Git:
argocd app sync <app-name>
Enable self-heal to prevent recurrence:
spec:
syncPolicy:
automated:
selfHeal: true
Cause 2 — CRDs missing when app tries to sync
Application uses Custom Resources (like Prometheus’s ServiceMonitor) but the CRD isn’t installed yet.
Symptom: sync fails with ensure CRDs are installed first.
Fix: install CRDs before the Application. Two approaches:
Approach A — sync waves (order of application):
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1" # applies before wave 0
Approach B — install CRDs manually first, then Application:
kubectl apply -f https://path/to/crds.yaml
argocd app sync <app-name>
Cause 3 — Namespace missing
Application references a namespace that doesn’t exist.
Symptom: sync fails with namespaces "foo" not found.
Fix: enable auto-creation:
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
Or create the namespace manually:
kubectl create namespace foo
Cause 4 — Immutable field changed
Some Kubernetes fields cannot be updated in-place (.spec.selector on Deployments, .spec.storageClassName on PVCs, etc.).
Symptom: sync fails with field is immutable.
Fix: the resource must be replaced. Add sync option:
spec:
syncPolicy:
syncOptions:
- Replace=true
Or manually delete and recreate:
kubectl delete <resource-type> <name> -n <namespace>
argocd app sync <app-name>
Warning: this causes downtime for stateful workloads. Handle carefully.
Cause 5 — RBAC blocking ArgoCD’s ServiceAccount
ArgoCD tries to create resources but its ServiceAccount lacks permissions.
Symptom: sync fails with forbidden: user "system:serviceaccount:argocd:argocd-application-controller" cannot create.
Diagnose:
kubectl auth can-i create deployments \
--as system:serviceaccount:argocd:argocd-application-controller \
-n <target-namespace>
Fix: grant permissions with a ClusterRoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-can-deploy
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
roleRef:
kind: ClusterRole
name: cluster-admin # or a narrower role
apiGroup: rbac.authorization.k8s.io
For production, use a narrower role than cluster-admin. Grant only what’s needed.
Cause 6 — Sync policy is manual (not auto)
Application status says OutOfSync but nothing happens.
Check:
argocd app get <app-name> | grep "sync policy"
If it says Sync Policy: <none>, syncs are manual.
Fix: enable auto-sync:
spec:
syncPolicy:
automated:
prune: true # delete resources not in Git
selfHeal: true # revert manual cluster changes
Cause 7 — Prune disabled — orphaned resources
Removed a manifest from Git but the resource still exists in cluster. ArgoCD shows OutOfSync but won’t delete without prune enabled.
Fix: enable prune in sync policy:
spec:
syncPolicy:
automated:
prune: true
Manual prune:
argocd app sync <app-name> --prune
Cause 8 — Diff on runtime-only fields
Kubernetes adds fields at runtime (.status, .metadata.resourceVersion, .metadata.uid). Sometimes ArgoCD sees these as drift.
Fix: ignore differences using resource-specific overrides:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # ignore HPA-managed replica count
Common candidates:
spec/replicaswhen using HPAmetadata/annotations/deployment.kubernetes.io/revision- Fields written by admission controllers
Cause 9 — Helm chart version mismatch
App references a Helm chart version. Chart was updated in git or repo but not synced.
Symptom: OutOfSync after chart upgrade.
Fix: hard refresh + sync:
argocd app get <app-name> --hard-refresh
argocd app sync <app-name>
Cause 10 — Kustomize / Helm values changed but not detected
Configuration in overlays or values files changed but ArgoCD hasn’t noticed.
Fix: force refresh:
argocd app get <app-name> --refresh
Or in the UI: click “Refresh” button on the Application.
Cause 11 — Waiting on parent Application (App-of-Apps pattern)
Child app waiting for parent to sync first.
Fix: sync the parent Application first:
argocd app sync <parent-app>
Cause 12 — Sync retry limit hit
App tried to sync many times and failed. Now waiting.
Fix: manual retry:
argocd app sync <app-name> --retry-limit 5 --retry-backoff-duration 30s
The universal debug flow
Every OutOfSync issue, run this:
# See status
argocd app get <app-name>
# See what differs
argocd app diff <app-name>
# See sync history
argocd app history <app-name>
# Force refresh
argocd app get <app-name> --refresh
# Try sync with verbose logging
argocd app sync <app-name> --loglevel debug
Root cause usually visible in first 3 commands.
Prevention checklist
Set up new Applications with these defaults:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/user/repo
targetRevision: HEAD
path: apps/myapp
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
This covers 80% of OutOfSync causes preventatively.
Reproduce this yourself
Free ArgoCD environment: install on Killercoda (killercoda.com/playgrounds/scenario/kubernetes):
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml --server-side
# Wait for pods
kubectl -n argocd get pods
# Get admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Port-forward
kubectl port-forward svc/argocd-server -n argocd 8080:443
Create an Application, edit resources with kubectl edit, watch it become OutOfSync. Then try each fix above.
When OutOfSync is actually fine
Some legitimate cases where OutOfSync is expected temporarily:
- Application just deployed, still initializing
- Helm chart update in progress
- Rolling update mid-flight
- HPA-managed replica differences
These resolve on their own within minutes. If OutOfSync persists >5 minutes, investigate.
Bottom line
OutOfSync = Git and cluster disagree. Diagnose which side has the correct version. Sync from the right direction. Enable auto-sync + self-heal to prevent recurrence.
The 12 causes above cover ~99% of real-world scenarios. Match the symptom, apply the fix.
DevOps YAML Pack
36 production-ready configs — Kubernetes, Docker Compose, GitHub Actions, Terraform, Helm, Ansible. Every file heavily commented. Copy, paste, ship.
Get the pack — ₹499 →