Skip to content
Intellira
ArgoCDmedium severity

ProgressingStuck

An ArgoCD app stuck Progressing is moving toward Healthy but never arrives. Sync status and health status are orthogonal — fix the workload, not the sync.

Written by Intellira Engineering, Editorial team

What "stuck Progressing" means

ArgoCD reports two independent things about every resource: sync status (does live state match Git?) and health status (is the resource actually healthy?). Progressing is a health status — it means the resource "is not yet healthy but is making progress toward becoming healthy" (ArgoCD: Resource Health). A Deployment rolling out new pods is Progressing. So is a LoadBalancer Service waiting for an external IP. Stuck Progressing means that transition never completes. The fix is almost always in the workload, not in re-syncing.

Be pedantic here, because the two axes get conflated constantly:

  • Sync status (Synced / OutOfSync) compares live manifests to Git. It does not measure health.
  • Health status (Healthy / Progressing / Degraded / Suspended / Missing / Unknown) measures whether the applied resource is working.

They are orthogonal. An app can be Synced and Progressing — ArgoCD applied everything correctly, but the resulting workload hasn't reached Healthy yet. Re-syncing a Synced app does nothing for a health problem.

Progressing also differs from Degraded: Progressing is "not there yet," Degraded is "failed." For a Deployment, the boundary between them is a timer — progressDeadlineSeconds (see below). Before the deadline, a stalled rollout reads Progressing; after it, ArgoCD maps the resulting condition to Degraded.

Diagnose it

Start at the app and walk down to the specific resource and its pods:

argocd app get <app-name>
# Read TWO columns separately: Sync (Synced/OutOfSync) and Health (Progressing/...).
# Find the child resource whose Health is Progressing.

argocd app resources <app-name>
# Lists managed resources with their health — confirm which one is stuck.

kubectl get pods -n <namespace> -l app=<app>
# Are pods Running and Ready? Or Pending / ImagePullBackOff / CrashLoopBackOff?

For a Deployment, read its conditions — this is the single most useful signal:

kubectl get deploy <deploy> -n <namespace> -o yaml
# Look under .status.conditions:
#   type: Progressing  status: "True"   reason: NewReplicaSetAvailable  -> done/Healthy
#   type: Progressing  status: "True"   reason: ReplicaSetUpdated        -> still rolling
#   type: Progressing  status: "False"  reason: ProgressDeadlineExceeded -> stalled (Degraded)
#   type: Available    status: "False"                                  -> not enough ready replicas
kubectl describe deploy <deploy> -n <namespace>
# The Conditions block plus events (scaled up replica set, etc.)

kubectl describe deploy <deploy> -n <namespace> | grep -A5 Conditions

A Deployment stays Progressing until either it satisfies its rollout (becomes Healthy) or exceeds progressDeadlineSeconds, at which point Kubernetes surfaces a condition of type: Progressing, status: "False", reason: ProgressDeadlineExceeded in the resource status; the controller takes no further action itself (Kubernetes: Failed Deployment). That field defaults to 600s (Kubernetes API: DeploymentSpec). ArgoCD then maps that failed condition to Degraded, not Progressing — so a Deployment that's still Progressing after several minutes is, by definition, inside that deadline window. That's your clue that the rollout is stalled and about to flip.

If ArgoCD shows Progressing but Kubernetes shows the resource as fine, force a hard refresh — the controller can serve a stale cached status:

argocd app get <app-name> --hard-refresh

How ArgoCD decides a resource is Progressing

ArgoCD's built-in Deployment health check passes (Healthy) only when observed generation equals desired generation and updated replicas match desired replicas. Until both hold, the Deployment is reported Progressing (ArgoCD: Resource Health).

The application's health is the worst health among its immediate child resources, ranked Healthy > Suspended > Progressing > Missing > Degraded > Unknown (ArgoCD: Resource Health). So one Progressing child holds the whole app at Progressing as long as nothing is worse — your first job is to find which resource and why.

Causes, each end to end

1. Deployment never reaches desired replicas (pods won't start)

The most common path. The Deployment's updated replicas never reach desired, so the built-in check never passes and the resource stays Progressing — until progressDeadlineSeconds (default 600s) is exceeded, after which Kubernetes sets Progressing=False / ProgressDeadlineExceeded and ArgoCD flips it to Degraded (Kubernetes: Failed Deployment). The usual underlying reasons are pods stuck in ImagePullBackOff, CrashLoopBackOff, or Pending (unschedulable).

Diagnose:

kubectl get pods -n <namespace> -l app=<app>
# ImagePullBackOff / ErrImagePull -> registry/tag/secret problem
# CrashLoopBackOff / Error        -> the container starts then dies
# Pending                         -> unschedulable

kubectl describe pod <pod> -n <namespace>
# Events name the exact reason: Failed to pull image / Back-off restarting / FailedScheduling

kubectl logs <pod> -n <namespace> --previous
# crash output from before the last restart

Fix: depends on which one the events name — correct the image tag/digest or attach a valid imagePullSecret; fix the bad config/env the crash logs point at; or lower resource requests / raise quota / fix affinity so Pending pods can be scheduled. Once pods become Ready and updated replicas reach desired, the Deployment goes Healthy on its own. These each have a dedicated page: ImagePullBackOff, CrashLoopBackOff, Pod Pending.

2. Readiness probe never passes

The pods are Running, but the readiness probe keeps failing, so the pods never count as Ready, the Deployment's endpoints never fill, updated-ready replicas never reach desired, and the rollout sits Progressing indefinitely (then Degraded once the deadline trips). This is distinct from case 1 — the container is alive; it just never reports ready.

Diagnose:

kubectl get pods -n <namespace> -l app=<app>
# READY column shows 0/1 while STATUS is Running -> readiness, not liveness

kubectl describe pod <pod> -n <namespace>
# Events: Readiness probe failed: HTTP probe failed with statuscode: 503 / connection refused

kubectl get endpoints <service> -n <namespace>
# empty ENDPOINTS confirms no pod is Ready behind the Service

Fix: correct the probe path/port/scheme, raise initialDelaySeconds or failureThreshold if the app is just slow to warm up, or fix the application endpoint the probe targets. Related: Service has no endpoints.

3. Argo Rollouts canary paused awaiting promotion (intentional Progressing)

If you use Argo Rollouts, a canary that has reached a pause step is deliberately holding for a manual promotion. ArgoCD detects a paused Rollout and marks the Application as Suspended, not Progressing (Argo Rollouts FAQ). But mid-step — while the Rollout is shifting traffic or running analysis between pauses — it reports Progressing, and that is expected, not a fault. Don't "fix" a healthy canary that is simply mid-rollout; promote it (or let analysis complete).

Diagnose:

kubectl argo rollouts get rollout <name> -n <namespace>
# Status: ॥ Paused -> awaiting promotion (ArgoCD: Suspended)
# Status: ◌ Progressing -> mid-step, expected

Fix (if promotion is what you actually want):

kubectl argo rollouts promote <name> -n <namespace>

A genuinely failed/aborted Rollout maps to Degraded, not Progressing — see the Degraded page.

4. LoadBalancer Service waiting on an external IP

A Service of type LoadBalancer is Healthy only when its status.loadBalancer.ingress list is non-empty — at least one hostname or IP (ArgoCD: Resource Health). Until the cloud provider provisions the load balancer, that list is empty and ArgoCD reports the Service (and therefore the app) Progressing. If no cloud LB controller is installed, or the provider is rejecting the request (quota, subnet, annotations), it stays Progressing forever.

Diagnose:

kubectl get svc <service> -n <namespace>
# EXTERNAL-IP stuck on <pending> -> no ingress assigned yet

kubectl describe svc <service> -n <namespace>
# Events: EnsuringLoadBalancer / SyncLoadBalancerFailed <provider reason>

Fix: make sure a cloud load-balancer controller is running and the provider can fulfil the request (quota, subnet tags, required annotations). On bare metal, install something like MetalLB or switch the Service to NodePort/ClusterIP + an Ingress.

5. Custom resource (CRD) with no health check, or a Lua check that returns Progressing

ArgoCD ships no built-in health check for arbitrary CRDs. When it can't assess a resource, it has nothing to mark it Healthy with — and a custom Lua health check that doesn't explicitly return Healthy or Degraded defaults to Progressing (ArgoCD: Resource Health). So a CRD with no health customization, or one whose Lua logic falls through its conditions, sits Progressing indefinitely even though the underlying object may be perfectly fine.

Diagnose:

kubectl get <crd-kind> <name> -n <namespace> -o yaml
# Read .status / .status.conditions — does the operator report Ready=True?
# If the object is actually fine, the gap is ArgoCD's health assessment, not the workload.

Fix: add a resource customization (a resource.customizations.health entry keyed by the CRD's group and kind) in argocd-cm, whose Lua reads the CRD's own status and returns Healthy when the operator reports ready, Degraded on failure conditions — see ArgoCD: Custom Health Checks. Many common CRDs already have bundled checks in ArgoCD's resource_customizations directory; contribute or copy one rather than writing from scratch.

Work the two axes separately and in order. First confirm it really is a health problem and not a sync problem (argocd app get — read the Sync and Health columns independently; re-syncing a Synced/Progressing app is wasted effort). Then identify the single child resource that's Progressing and classify it: a Deployment in its deadline window (cases 1–2), an intentional canary pause (case 3), an infrastructure wait like a LoadBalancer (case 4), or a CRD ArgoCD simply can't assess (case 5). The classification dictates the fix — chasing the pod when the real gap is a missing Lua health check wastes the whole investigation.

Tradeoff to weigh: you can mask a slow-but-legitimate rollout by raising progressDeadlineSeconds, but that also delays the moment a genuinely failed rollout turns Degraded and gets noticed. Tune it to your real startup time plus headroom, not to silence an alert.

Validate the fix

kubectl rollout status deploy/<deploy> -n <namespace>
# "successfully rolled out" -> the rollout completed

kubectl get deploy <deploy> -n <namespace> \
  -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
# Want: Available=True  and  Progressing=True NewReplicaSetAvailable

argocd app get <app-name>
# Health column should read Healthy

Prevention

  • Set realistic readiness probes and initialDelaySeconds so slow-starting apps aren't read as stalled.
  • Keep progressDeadlineSeconds tuned to real startup time — long enough to avoid false Degraded, short enough to surface real failures.
  • Add (or adopt) custom health checks for every CRD you sync through ArgoCD, so a healthy custom resource doesn't sit Progressing forever.
  • Alert on apps that stay Progressing beyond an expected window, so a stuck rollout is caught before it silently flips to Degraded.

How Intellira diagnoses this

Intellira reads the per-resource Sync and Health from the ArgoCD MCP server, keeps the two axes separate, and follows the Progressing resource down into Kubernetes via the read-only Kubernetes connector — reading the actual Deployment .status.conditions, pod status, Service loadBalancer.ingress, and CRD status. That lets it tell a Deployment still inside its progress deadline from a readiness probe that will never pass, a paused canary, a LoadBalancer waiting on the cloud, or a CRD with no health check — and tie the stall to the change that introduced it, with evidence. It never re-syncs, scales, or edits anything.

Sources

By Intellira Engineering. AI-assisted draft; claims cited inline; last verified 2026-06-02. Pending technical review.

Frequently asked questions

Progressing vs Degraded vs Synced — what is the difference?
Progressing and Degraded are health statuses: Progressing means the resource is not yet healthy but still making progress toward Healthy, while Degraded means it failed. Synced is a sync status — it only says live state matches Git, and says nothing about whether the resource is healthy. Sync status and health status are orthogonal: an app can be Synced and Progressing at the same time.
My app has been Progressing for 20 minutes — is that a bug?
Usually no. Progressing is a real, valid state while replicas roll out, a LoadBalancer waits for an external IP, or a canary awaits promotion. It only becomes a problem when it never resolves. For a Deployment, Kubernetes gives it progressDeadlineSeconds (default 600s) — once exceeded, the Deployment flips to a Progressing=False/ProgressDeadlineExceeded condition and ArgoCD maps that to Degraded, not Progressing.
Why does my custom resource stay Progressing forever?
ArgoCD has no built-in health check for arbitrary CRDs. Without a custom Lua health check, ArgoCD cannot tell the resource is healthy and the default is Progressing. Add a resource customization that reads the CRD status and returns Healthy/Degraded.

Related errors

Find the root cause of ProgressingStuck on your stack

Connect read-only and Intellira correlates the change behind it across Bitbucket, Jenkins, ArgoCD and Kubernetes — with the evidence to prove it.