Custom health checks in Argo CD: when built-in status is not enough
A team often sees a green Application in Argo CD while a custom resource in that Application is still not working, or sees a red parent application caused by a single child. Both situations come from the same gap: Argo CD decides health from rules it knows, and a custom resource type has no rules unless someone writes them. Health is a separate question from sync status. Sync status tells you whether the live state matches Git, while health tells you whether the live resources are actually doing their job. Argo CD ships with built-in health checks for Deployment, ReplicaSet, StatefulSet, DaemonSet, Service, Ingress, CronJob, Job and PersistentVolumeClaim. For any other kind, Argo CD has no built-in opinion, so the resource can report as healthy simply because it exists.
Custom Lua checks fill that gap. You define them under resource.customizations.health.<group>_<kind> in the argocd-cm ConfigMap, and a custom check takes precedence over a built-in check for the same group and kind. That precedence matters: it means you can also override the built-in logic for a standard kind if your team has a stricter definition of ready. Before you write any script, decide which status fields prove the resource is usable for your workloads, such as a condition that reports success or a phase that reaches a running state, and confirm those fields with the owning operator's documentation.
The practical decision is where the health signal should live. Use a custom check when a resource's own status is trustworthy and a child failure should surface at the parent. Use the exclusion mechanism described below when a child is noisy and should not drag the parent down. Review every custom check when you upgrade the operator that owns the resource, because status fields can change between versions. The Argo CD resource health guide documents the expected script format and the list of built-in checks, and it is the reference to confirm against your Argo CD version before you roll changes out.
Health check options and when each fits
This table compares the four ways to change how Argo CD judges health: relying on built-in checks, writing a Lua check for a custom resource, restoring Application health for app of apps, and excluding a noisy child. Read each row as a question: what does the option change, and does your situation match when it fits? The main takeaway is that a custom check changes what Argo CD believes, so each one needs a test that proves it reports the states you care about.
| Option | What it changes | When it fits | Main risk |
|---|---|---|---|
| Built-in checks | Argo CD evaluates Deployment, StatefulSet, Service, Job and other supported kinds with its own logic and reports their health without extra configuration. | Your resources are standard Kubernetes kinds that Argo CD already understands, and their default health meaning matches your expectations. | Built-in logic may not reflect your rollout or readiness rules, so a resource can look healthy while your service is not ready. |
| Lua check for a custom resource | A Lua script under resource.customizations.health.<group>_<kind> in argocd-cm maps the fields of a custom resource to a health status, and it takes precedence over any built-in check for that kind. | A CRD reports its readiness in status conditions or fields that Argo CD cannot read by default, and you can state which values mean healthy, progressing or degraded. | A script that maps the wrong field or misses an error state can hide real failures, and it must be retested after the CRD changes. |
| Restored Application health for app of apps | A custom check for argoproj.io/Application restores the health status that Argo CD removed for that CRD in v1.8, so a parent Application reflects its children. | A parent Application manages child Applications and you want the parent to show degraded when any child is unhealthy. | Every child's health now flows upward, so one failing child can mark the whole parent degraded and trigger alerts across the tree. |
| Ignore-healthcheck annotation on a child | The argocd.argoproj.io/ignore-healthcheck annotation on a resource excludes that child from the health of its parent Application. | A child is known to stay unhealthy for reasons that do not affect the service, such as an optional job or a component under migration. | Excluding a child hides its real state from parent health, so a genuine failure in that child may go unnoticed. |
How to add a custom health check and verify it
This sequence takes a custom resource from an unclear health state to a Lua check that Argo CD evaluates correctly, with each step confirmed before the next one begins.
Choose the custom resources whose health matters
The platform team, with the owners of the workloads, lists the custom resources that Argo CD currently reports as Healthy or Progressing without a meaningful reason. Record each resource's API group, kind and the status condition that shows it is ready, such as a Ready or Available condition. Confirm the list is limited to resources where a wrong health answer would affect a sync decision or an alert. Done when each entry names a group, kind and one condition you can point to on a live object.
Read the status fields from a live object
Use kubectl to fetch one live instance of the custom resource in YAML form and inspect its status block. Note the exact field names, the condition types, the reason values and the observedGeneration field if the controller writes it. Compare these values across a healthy and an unhealthy instance so you know what each state looks like. Done when you have a written example of the status for both states, copied from the cluster.
Write the Lua script against sample objects
Write a Lua script that sets the health status and message from the status fields you recorded, using the Healthy, Progressing, Degraded and Suspended values Argo CD accepts. Save a healthy sample object and a failing sample object as test inputs, and run the script against each one outside the cluster. Check that the Lua script returns a status and a message for every branch, including the case where the status block is missing. Done when both samples return the expected status and the missing-status case returns Progressing rather than an error.
Add the script under the resource key in argocd-cm
Edit the argocd-cm ConfigMap in the Argo CD namespace and add a key named resource.customizations.health.<group>_<kind>, using the API group and kind exactly as they appear in the resource manifest. Put the Lua script under that key and apply the change through your normal GitOps or admin process. Confirm the key name matches the group and kind character for character, because a mismatch leaves the built-in behavior in place without an error. Done when the ConfigMap shows the new key and the Argo CD application controller has been restarted or has reloaded the configuration.
Sync a test Application that uses the resource
Create or select a test Application whose manifests include the custom resource, and sync it from Argo CD. Open the resource in the Argo CD UI or run argocd app get against the Application and read the health status shown for the custom resource. Confirm the health value matches the healthy sample object from the earlier step. Done when the custom resource shows the health state you expected for a healthy object, not the generic fallback.
Drive the resource into a failing state
Change the test resource in a non-production cluster so its status reports the failing condition, for example by setting a dependency to an invalid value or by scaling down a backing workload in a controlled way. Watch the Argo CD view and confirm the resource moves to Degraded or Progressing with the message you wrote in the script. Done when the status and message change in Argo CD within the time your controller takes to update the resource status, and the change matches the failing sample.
Confirm recovery and the parent Application roll-up
Restore the test resource to its working state and confirm Argo CD returns the custom resource to Healthy. If the resource is a child of an app of apps, check the parent Application's health, because it reflects the worst health of its immediate children. Record the before and after states in your change record so reviewers can see that both transitions work. Done when the resource and its parent Application both return to Healthy, and the evidence is saved with the change.
Restoring Application health for app of apps
Argo CD removed health reporting for the argoproj.io/Application custom resource in v1.8, so a parent Application that manages child Applications shows no meaningful health by default. You can restore that signal with a custom check under resource.customizations.health.argoproj.io_Application in argocd-cm. The script should read the status of each child Application and return a health state that reflects the worst child, because Argo CD computes an Application's health from its immediate children. Write the script so it handles children that have not reported status yet, since a missing status should read as progressing rather than healthy.
- Match the key exactly: The ConfigMap key uses the group and kind joined by an underscore, so an error in the name means the built-in behavior stays in place.
- Handle missing status: Newly created children may have no health field yet, and the script should return a progressing state for them.
- Test with real children: Run the script against a parent with one healthy, one degraded and one progressing child before relying on it.
- Plan for the exclusion annotation: Children marked with
argocd.argoproj.io/ignore-healthcheckare skipped in the roll-up, so document which teams may set it. - Confirm the behavior on your version: Check the current health guide for your Argo CD release before copying any sample script.
After deployment, confirm that a parent Application changes state when one child degrades and returns to healthy when that child recovers.
As an application owner, the parent Application is the view you watch when a release spans several child Applications, such as a database, a service and an ingress. Its health is meant to answer one question: is anything under this parent not working? When the restored check is in place, a parent that shows degraded points you to a child that needs attention, and you should open that child before changing the parent. Treat the parent state as a summary, not as the place where the fault is found.
- Count the core workloads: Children that carry user-facing services, data stores or ingress should affect the parent state.
- Expect some children to be excluded: Batch helpers, one-time jobs or noisy diagnostic Applications may be marked to be ignored in the roll-up, and the platform team should tell you which ones.
- Read the child first: Use the child's resource tree and events to find the failing object before escalating.
- Avoid chasing transient progressing states: A child that is syncing after a change may show progressing for a short time, which is normal.
- Ask who owns exclusions: An excluded child is invisible in the parent, so it needs its own alerting.
Before relying on the parent status for release decisions, confirm with your platform team which children are included and which are excluded.
Not every child should move the parent status. The useful rule is to include children whose failure means the application set is not serving its purpose, and exclude children whose failure is expected, temporary or purely informational. For a typical app of apps, a child that deploys the production workload belongs in the roll-up, while a child that runs a one-time migration or an optional monitoring add-on may not. Decide this per child, not per cluster, and write the decision into your repository so reviewers can see it.
- Include core services: Applications carrying live workloads, storage or ingress should affect the parent.
- Exclude one-time work: Migration or bootstrap Applications that finish and stay idle can add false degraded states.
- Exclude noisy optional add-ons: Non-critical tools that flap should use the ignore annotation, paired with their own alerts.
- Review the list on change: When a child is added or removed, update the decision so the parent keeps its meaning.
- Keep the rule auditable: Store the annotation choices in Git so the change history explains why a child was excluded.
For each parent, confirm the included and excluded children with the owning team before the check goes live.
Excluding a noisy child from application health
Exclude a child from application health only when its status is known to be noisy and someone else already watches it. Argo CD rolls an Application's health up from its immediate children, so one child that flaps can turn the whole Application Degraded or Progressing even when everything the parent owns is working. The argocd.argoproj.io/ignore-healthcheck annotation removes a child from that rollup. Typical candidates are a batch resource that is expected to run to completion and then sit in a state the Lua check reads as unhealthy, or a component owned by another team that your Application deploys but does not operate. Before you add the annotation, confirm the child's health is a poor signal for your Application, not a symptom of a fault in what you deploy.
The exclusion hides real failures, and that is the cost you accept. A child that stays broken will no longer change the parent's status, so dashboards and sync notifications built on application health will stay green. Record an owner for each excluded resource, a review date on which the owner confirms the exclusion is still needed, and a separate alert on the child's own condition, such as a monitoring rule in your metrics stack or a notification on the resource's events. Keep the list of excluded children in Git next to the manifests so reviewers see each new exclusion in a pull request. Ask your partner or internal team to show where the owner, review date and alert are recorded before the annotation reaches production.
Security and operational risks of custom health scripts
A health script decides when Argo CD reports a resource as healthy, so it carries real weight in deployment decisions. The risks below cover who can change that logic, how scripts can hide failures, and how they drift from the resources they judge.
Control who can edit argocd-cm
Custom checks live under resource.customizations.health in the argocd-cm ConfigMap, so anyone who can write that ConfigMap can change how every matching resource is judged. Limit write access to the cluster admins who already manage Argo CD configuration, and remember that the documentation recommends keeping the built-in admin user for initial setup only and disabling it with admin.enabled set to false once SSO or local users exist. Confirm who holds configmaps write permission in the Argo CD namespace.
Scripts that mark broken resources healthy
A Lua script that returns Healthy too early, for example by checking only that a status field exists, will hide a failing rollout behind a green application. Write each script to test the condition that proves readiness, such as a matching generation and an available condition, and test it against resources in the failed state as well as the good state. Reviewers should ask what the script returns when the status is missing or empty.
Fields that change between CRD versions
Custom resource status schemas change as operators ship new versions, so a script that reads a field from one API version may silently stop matching after an upgrade. The result is often Progressing forever or a false Healthy. Pin the group, version and kind each script targets, and re-test scripts whenever the operator that owns the CRD is upgraded.
Hidden failures from excluded children
The argocd.argoproj.io/ignore-healthcheck annotation removes a child from its parent's health calculation, which is useful for a noisy job but means a real failure in that child no longer reaches the parent. Record every ignored child, the reason it is ignored, and who reviews it, and check periodically that the exclusion still applies. A parent that shows Healthy should not be treated as proof that every child works.
Repo-server runs content it receives
Health scripts are delivered through configuration, but the repo-server runs Helm and Kustomize on repository content, and the security documentation calls out untrusted repositories and out-of-tree file access as risks to control. Health logic changes should move through the same review path as application manifests, so a script added alongside a chart does not reach the cluster without review. Confirm which repositories the repo-server can read.
Review health scripts in Git
Keeping the argocd-cm changes only in a live cluster leaves no history of who changed health judgment and why. Manage the ConfigMap through a Git repository that Argo CD syncs, require pull request review for changes to Lua logic, and attach a test manifest for each script so reviewers can see the expected outcome. This also gives auditors a clear trail for configuration changes.
Questions teams ask before writing their own health checks
How much effort does a first custom health check take?
Effort depends mainly on how many custom resource types you need to cover, how well each controller reports status, and how much testing the check needs before it is trusted. A single CRD with clear status conditions is a small, contained change: one Lua script under resource.customizations.health.<group>_<kind> in argocd-cm, plus sample objects to test against. Effort rises when a CRD reports status inconsistently across versions or when several CRDs need checks. Ask any partner to split the quote by CRD, and to include the test cases each script must pass.
Can our team write custom health checks without outside help?
Yes, for one CRD with good upstream documentation, a platform engineer who knows Lua and the resource's status fields can usually write and test the script alone. The work gets harder across many CRDs, where each one has its own status model and version differences, and where mistakes show up as applications stuck in Progressing or wrongly marked Healthy. The Argo CD resource health guide documents the Lua contract and the built-in checks, which is the right starting point before deciding who writes the scripts.
What does the work need from our team?
Plan for three inputs. First, sample objects from each CRD in several states, such as creating, ready, failed and deleting, so the script can be tested against real status fields rather than assumptions. Second, cluster access with enough rights to read those objects and to apply the argocd-cm change in a non-production Argo CD instance. Third, a Git review path, since health scripts change how every Application that uses the resource is judged. Your reviewers should be the people who own the CRD.
Do custom health checks need upkeep after they are deployed?
Yes. A health script reads fields from a CRD's status, so it needs review when the CRD adds, renames or changes the meaning of those fields. Upkeep also follows Argo CD upgrades, because the upgrade notes may change how health behaves for affected resources. Treat the scripts as versioned configuration with an owner, a test set kept with them, and a check in your upgrade runbook, rather than a one-time change.
How do custom health checks relate to Argo CD Notifications?
Health checks and Notifications answer different questions. Health tells Argo CD whether a resource is Healthy, Progressing, Degraded or Missing, and that status appears in the UI, the CLI and any Application-level rollup. Notifications then act on changes, for example by sending an alert when an Application becomes Degraded. A wrong health script therefore produces wrong alerts as well as wrong dashboards, so test the script before subscribing alerts to it. The separate Notifications topic covers the alert setup.
Should we restore Application health for app of apps?
Restore it when a parent Application should report a degraded child, which is the usual reason for an app of apps layout. Argo CD removed health for the argoproj.io/Application CRD in v1.8, so the parent shows no meaningful health unless you add a custom check. The check rolls up the worst health of immediate children. If one child is known to be noisy, mark it with argocd.argoproj.io/ignore-healthcheck instead of weakening the rule for every child. Confirm this behavior against the health guide for your Argo CD version before relying on it.
Is a custom health script a security risk?
A health script runs inside Argo CD and only reads the resource it evaluates, so it does not need broad write access. The risk is in who can change it. Anyone who edits argocd-cm can change how Argo CD judges health across the instance, which can hide failures. Limit write access to that ConfigMap, keep scripts in Git with review, and check that the Argo CD RBAC model only lets the right people change cluster-wide configuration.
How DeployOpen scopes and delivers custom health checks
DeployOpen approaches custom resource health checks in Argo CD as a scoped piece of work on your own clusters and repositories. We define which resources need a Lua check, which Application rollups need restoring, and which noisy children should be excluded, then deliver the change under version control with documentation your team can maintain.
How we scope
- We inventory the custom resources whose health Argo CD currently reports incorrectly, including CRDs with no built-in check.
- We agree which Application rollups, such as app of apps parents, need a restored health check and which children must be excluded.
- The written scope names the Argo CD version, the cluster set, the Git repositories and the resource kinds covered by the work.
- Engagements are fixed-scope, with deliverables and timelines agreed in writing before work starts.
How we build
- We write Lua health scripts under resource.customizations.health for each target group and kind, and keep them in Git.
- We test each script against healthy, progressing, degraded and missing states drawn from real manifests in a non-production cluster.
- We apply the argocd-cm changes through the same review path as other Argo CD configuration, so every change is traceable.
- We check that built-in checks still apply to the resources they already cover and that custom checks take the intended precedence.
How we hand over
- You receive the Lua scripts, the argocd-cm configuration and the exclusion annotations, with the reasons each one exists.
- Documentation records how each resource state maps to Argo CD health, so the next engineer can extend the checks without guessing.
- We walk your platform team through verifying Application health after a sync and through reproducing a degraded state.
- Acceptance criteria in the written scope define what the handover must demonstrate before the engagement closes.
How we support
- Ongoing support, upgrades and continuous testing are available as a retainer after handover.
- When Argo CD releases change health behavior, we review affected checks against the upgrade notes before your rollout.
- Health check changes are retested when you add new CRDs or move to a newer Argo CD minor version.
- Ask any provider to confirm how they handle script regressions, rollback of argocd-cm changes and who owns the Git repository.

