Repository: percona/percona-xtradb-cluster-operator

Issue:
Add configurable HAProxy backend health check parameters without requiring full configuration override
Currently, to customize HAProxy backend health check intervals and failover behavior, users must provide a complete HAProxy configuration via `haproxy.configuration`, duplicating all the operator's default logic and making maintenance difficult.

## Problem

When a PXC node fails (e.g., during rolling restart), HAProxy takes 20+ seconds to detect the failure with default settings:
- `check inter 10000` (10s interval)
- `fall 2` (2 failures = 20s total)

Worse, existing client connections to the failed backend are NOT terminated, causing them to hang until TCP timeout (potentially minutes), as `on-marked-down shutdown-sessions` is not configured.

## Current Workaround

Provide full HAProxy configuration in `haproxy.configuration` field, but this:
- Duplicates operator logic
- Breaks on operator upgrades
- Is difficult to maintain

## Requested Feature

Add CR fields to configure HAProxy backend health check parameters without full config override:

```yaml
haproxy:
  healthCheck:
    interval: 3000          # check inter 3000 (default: 10000)
    fall: 2                 # fall 2 (default: 2)
    rise: 1                 # rise 1 (default: 1)
    shutdownOnMarkDown: true # on-marked-down shutdown-sessions (default: false)
```

## Benefit
- Fast failover (6s with `interval: 3000`)
- Active connection termination on backend failure
- No maintenance burden
- Survives operator upgrades

## Alternative

If granular options are too complex, at least expose a "fast-failover" preset that applies sensible defaults.

Hints from issue comments:
@gkech: thanks for this issue @timstoop, here's a jira ticket: https://perconadev.atlassian.net/browse/K8SPXC-1734

Solution patch:
diff --git a/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml b/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml
index 0852193f..6af63af5 100644
--- a/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml
+++ b/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml
@@ -1745,6 +1745,21 @@ spec:
                   gracePeriod:
                     format: int64
                     type: integer
+                  healthCheck:
+                    properties:
+                      fall:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                      interval:
+                        format: int32
+                        minimum: 1000
+                        type: integer
+                      rise:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                    type: object
                   hookScript:
                     type: string
                   image:
diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml
index 0392e25d..223def97 100644
--- a/deploy/bundle.yaml
+++ b/deploy/bundle.yaml
@@ -2785,6 +2785,21 @@ spec:
                   gracePeriod:
                     format: int64
                     type: integer
+                  healthCheck:
+                    properties:
+                      fall:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                      interval:
+                        format: int32
+                        minimum: 1000
+                        type: integer
+                      rise:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                    type: object
                   hookScript:
                     type: string
                   image:
diff --git a/deploy/cr.yaml b/deploy/cr.yaml
index d2999c5a..08875cde 100644
--- a/deploy/cr.yaml
+++ b/deploy/cr.yaml
@@ -304,6 +304,10 @@ spec:
 #        rack: rack-22
 #      loadBalancerSourceRanges:
 #        - 10.0.0.0/8
+#    healthCheck:
+#      interval: 10000
+#      rise: 1
+#      fall: 2
 #    runtimeClassName: image-rc
 #    sidecars:
 #    - image: busybox
diff --git a/deploy/crd.yaml b/deploy/crd.yaml
index bd3af185..3d636927 100644
--- a/deploy/crd.yaml
+++ b/deploy/crd.yaml
@@ -2785,6 +2785,21 @@ spec:
                   gracePeriod:
                     format: int64
                     type: integer
+                  healthCheck:
+                    properties:
+                      fall:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                      interval:
+                        format: int32
+                        minimum: 1000
+                        type: integer
+                      rise:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                    type: object
                   hookScript:
                     type: string
                   image:
diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml
index f91636fe..13588b2f 100644
--- a/deploy/cw-bundle.yaml
+++ b/deploy/cw-bundle.yaml
@@ -2785,6 +2785,21 @@ spec:
                   gracePeriod:
                     format: int64
                     type: integer
+                  healthCheck:
+                    properties:
+                      fall:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                      interval:
+                        format: int32
+                        minimum: 1000
+                        type: integer
+                      rise:
+                        format: int32
+                        minimum: 1
+                        type: integer
+                    type: object
                   hookScript:
                     type: string
                   image:
diff --git a/pkg/apis/pxc/v1/pxc_types.go b/pkg/apis/pxc/v1/pxc_types.go
index a2b3ed3e..9a395661 100644
--- a/pkg/apis/pxc/v1/pxc_types.go
+++ b/pkg/apis/pxc/v1/pxc_types.go
@@ -676,8 +676,9 @@ type ProxySQLSchedulerSpec struct {
 
 type HAProxySpec struct {
 	PodSpec        `json:",inline"`
-	ExposePrimary  ServiceExpose          `json:"exposePrimary,omitempty"`
-	ExposeReplicas *ReplicasServiceExpose `json:"exposeReplicas,omitempty"`
+	ExposePrimary  ServiceExpose           `json:"exposePrimary,omitempty"`
+	ExposeReplicas *ReplicasServiceExpose  `json:"exposeReplicas,omitempty"`
+	HealthCheck    *HAProxyHealthCheckSpec `json:"healthCheck,omitempty"`
 
 	// Deprecated: Use ExposeReplica.Enabled instead
 	ReplicasServiceEnabled *bool `json:"replicasServiceEnabled,omitempty"`
@@ -685,6 +686,21 @@ type HAProxySpec struct {
 	ReplicasLoadBalancerSourceRanges []string `json:"replicasLoadBalancerSourceRanges,omitempty"`
 }
 
+type HAProxyHealthCheckSpec struct {
+	// Interval in milliseconds between health checks (default: 10000)
+	// +kubebuilder:validation:Minimum=1000
+	// +optional
+	Interval *int32 `json:"interval,omitempty"`
+	// Fall is the number of consecutive failed checks before marking server down (default: 2)
+	// +kubebuilder:validation:Minimum=1
+	// +optional
+	Fall *int32 `json:"fall,omitempty"`
+	// Rise is the number of consecutive successful checks before marking server up (default: 1)
+	// +kubebuilder:validation:Minimum=1
+	// +optional
+	Rise *int32 `json:"rise,omitempty"`
+}
+
 type ReplicasServiceExpose struct {
 	ServiceExpose `json:",inline"`
 	OnlyReaders   bool `json:"onlyReaders,omitempty"`
diff --git a/pkg/apis/pxc/v1/zz_generated.deepcopy.go b/pkg/apis/pxc/v1/zz_generated.deepcopy.go
index d898bec9..684461a5 100644
--- a/pkg/apis/pxc/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/pxc/v1/zz_generated.deepcopy.go
@@ -243,6 +243,36 @@ func (in *ComponentStatus) DeepCopy() *ComponentStatus {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HAProxyHealthCheckSpec) DeepCopyInto(out *HAProxyHealthCheckSpec) {
+	*out = *in
+	if in.Interval != nil {
+		in, out := &in.Interval, &out.Interval
+		*out = new(int32)
+		**out = **in
+	}
+	if in.Fall != nil {
+		in, out := &in.Fall, &out.Fall
+		*out = new(int32)
+		**out = **in
+	}
+	if in.Rise != nil {
+		in, out := &in.Rise, &out.Rise
+		*out = new(int32)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HAProxyHealthCheckSpec.
+func (in *HAProxyHealthCheckSpec) DeepCopy() *HAProxyHealthCheckSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(HAProxyHealthCheckSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *HAProxySpec) DeepCopyInto(out *HAProxySpec) {
 	*out = *in
@@ -253,6 +283,11 @@ func (in *HAProxySpec) DeepCopyInto(out *HAProxySpec) {
 		*out = new(ReplicasServiceExpose)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.HealthCheck != nil {
+		in, out := &in.HealthCheck, &out.HealthCheck
+		*out = new(HAProxyHealthCheckSpec)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.ReplicasServiceEnabled != nil {
 		in, out := &in.ReplicasServiceEnabled, &out.ReplicasServiceEnabled
 		*out = new(bool)
diff --git a/pkg/pxc/app/statefulset/haproxy.go b/pkg/pxc/app/statefulset/haproxy.go
index b6475ad1..8912f253 100644
--- a/pkg/pxc/app/statefulset/haproxy.go
+++ b/pkg/pxc/app/statefulset/haproxy.go
@@ -299,6 +299,13 @@ func (c *HAProxy) SidecarContainers(spec *api.PodSpec, secrets string, cr *api.P
 		})
 	}
 
+	// Add health check configuration env vars for v1.19.0+
+	if cr.CompareVersionWith("1.19.0") >= 0 {
+		if cr.Spec.HAProxy != nil {
+			container.Env = append(container.Env, buildHAProxyHealthCheckEnvVars(cr.Spec.HAProxy.HealthCheck)...)
+		}
+	}
+
 	if cr.CompareVersionWith("1.18.0") >= 0 {
 		// PEER_LIST_SRV_PROTOCOL is configured through the secret: EnvVarsSecretName
 		container.Args = append(container.Args, "-protocol=$(PEER_LIST_SRV_PROTOCOL)")
@@ -560,3 +567,41 @@ func (c *HAProxy) UpdateStrategy(cr *api.PerconaXtraDBCluster) appsv1.StatefulSe
 		}
 	}
 }
+
+// buildHAProxyHealthCheckEnvVars builds environment variables for HAProxy health check configuration
+// from the health check spec. These control how quickly HAProxy detects and responds to backend failures.
+//
+// Default values:
+//   - interval: 10000ms (10s between health checks)
+//   - rise: 1 (1 successful check to mark server up)
+//   - fall: 2 (2 failed checks to mark server down, total 20s with default interval)
+//
+// The function always includes "on-marked-down shutdown-sessions" to ensure existing connections
+// are terminated when a backend is marked down.
+//
+// The generated HA_SERVER_OPTIONS environment variable is used by haproxy_add_pxc_nodes.sh
+// to configure the "server" lines in the HAProxy backend configuration.
+func buildHAProxyHealthCheckEnvVars(healthCheck *api.HAProxyHealthCheckSpec) []corev1.EnvVar {
+	interval := int32(10000)
+	rise := int32(1)
+	fall := int32(2)
+
+	if healthCheck != nil {
+		if healthCheck.Interval != nil {
+			interval = *healthCheck.Interval
+		}
+		if healthCheck.Rise != nil {
+			rise = *healthCheck.Rise
+		}
+		if healthCheck.Fall != nil {
+			fall = *healthCheck.Fall
+		}
+	}
+
+	return []corev1.EnvVar{
+		{
+			Name:  "HA_SERVER_OPTIONS",
+			Value: fmt.Sprintf("resolvers kubernetes check inter %d rise %d fall %d weight 1 on-marked-down shutdown-sessions", interval, rise, fall),
+		},
+	}
+}



Test patch:
diff --git a/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy-k127.yml b/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy-k127.yml
index aa6148db..42c83d52 100644
--- a/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy-k127.yml
+++ b/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy-k127.yml
@@ -126,6 +126,8 @@ spec:
               value: cluster1-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: cluster1-env-vars-haproxy
diff --git a/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy.yml b/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy.yml
index 5811b376..2c2ed03d 100644
--- a/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy.yml
+++ b/e2e-tests/default-cr/compare/statefulset_cluster1-haproxy.yml
@@ -123,6 +123,8 @@ spec:
               value: cluster1-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: cluster1-env-vars-haproxy
diff --git a/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy-k127.yml b/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy-k127.yml
index fa96ab9a..926e7e11 100644
--- a/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy-k127.yml
+++ b/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy-k127.yml
@@ -123,6 +123,8 @@ spec:
               value: minimal-cluster-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: minimal-cluster-env-vars-haproxy
diff --git a/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy.yml b/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy.yml
index 70ccbb3e..112cede7 100644
--- a/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy.yml
+++ b/e2e-tests/default-cr/compare/statefulset_minimal-cluster-haproxy.yml
@@ -120,6 +120,8 @@ spec:
               value: minimal-cluster-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: minimal-cluster-env-vars-haproxy
diff --git a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-k127.yml b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-k127.yml
index 014ba114..d68556ce 100644
--- a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-k127.yml
+++ b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-k127.yml
@@ -123,6 +123,8 @@ spec:
               value: haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: haproxy-env-vars-haproxy
diff --git a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-oc.yml b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-oc.yml
index e2d43460..bf347c66 100644
--- a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-oc.yml
+++ b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-oc.yml
@@ -120,6 +120,8 @@ spec:
               value: haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: haproxy-env-vars-haproxy
diff --git a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-k127.yml b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-k127.yml
index 5dfd0f90..fe8a012b 100644
--- a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-k127.yml
+++ b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-k127.yml
@@ -123,6 +123,8 @@ spec:
               value: haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: haproxy-env-vars-haproxy
diff --git a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-oc.yml b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-oc.yml
index c60c2a62..c872630b 100644
--- a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-oc.yml
+++ b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret-oc.yml
@@ -120,6 +120,8 @@ spec:
               value: haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: haproxy-env-vars-haproxy
diff --git a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret.yml b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret.yml
index b3446763..de6560dc 100644
--- a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret.yml
+++ b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy-secret.yml
@@ -111,6 +111,8 @@ spec:
               value: haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: haproxy-env-vars-haproxy
diff --git a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy.yml b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy.yml
index c9d79cdb..4d91ddd0 100644
--- a/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy.yml
+++ b/e2e-tests/haproxy/compare/statefulset_haproxy-haproxy.yml
@@ -114,6 +114,8 @@ spec:
               value: haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: haproxy-env-vars-haproxy
diff --git a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-k127.yml b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-k127.yml
index 098228b5..17b2b8e0 100644
--- a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-k127.yml
+++ b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-k127.yml
@@ -266,6 +266,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml
index b172451f..7b24d142 100644
--- a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml
+++ b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml
@@ -266,6 +266,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml
index 94f5eb3b..7f0576ff 100644
--- a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml
+++ b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml
@@ -263,6 +263,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix.yml b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix.yml
index 4fc0cc62..cd3e73ae 100644
--- a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix.yml
+++ b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-no-prefix.yml
@@ -257,6 +257,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-oc.yml b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-oc.yml
index 76066627..ea52b5ba 100644
--- a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-oc.yml
+++ b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy-oc.yml
@@ -263,6 +263,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy.yml b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy.yml
index 567da2d5..b76a52f6 100644
--- a/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy.yml
+++ b/e2e-tests/monitoring-2-0/compare/statefulset_monitoring-haproxy.yml
@@ -257,6 +257,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-k127.yml b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-k127.yml
index 872f3fca..39a402b6 100644
--- a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-k127.yml
+++ b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-k127.yml
@@ -251,6 +251,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml
index 7b380b1e..2e0e4380 100644
--- a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml
+++ b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-k127.yml
@@ -251,6 +251,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml
index dca02cca..9131f7b0 100644
--- a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml
+++ b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix-oc.yml
@@ -248,6 +248,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix.yml b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix.yml
index 48bf9301..0b696533 100644
--- a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix.yml
+++ b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-no-prefix.yml
@@ -242,6 +242,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-oc.yml b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-oc.yml
index eb1c216f..c083831d 100644
--- a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-oc.yml
+++ b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy-oc.yml
@@ -248,6 +248,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy.yml b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy.yml
index f3cacf8a..82936eef 100644
--- a/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy.yml
+++ b/e2e-tests/monitoring-pmm3/compare/statefulset_monitoring-haproxy.yml
@@ -242,6 +242,8 @@ spec:
               value: monitoring-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: my-env-var-secrets
diff --git a/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-k127.yml b/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-k127.yml
index 40c35fca..3237c0e2 100644
--- a/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-k127.yml
+++ b/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-k127.yml
@@ -119,6 +119,8 @@ spec:
               value: "yes"
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: proxy-protocol-env-vars-haproxy
diff --git a/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-oc.yml b/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-oc.yml
index 0ab425fd..d53f0305 100644
--- a/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-oc.yml
+++ b/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy-oc.yml
@@ -116,6 +116,8 @@ spec:
               value: "yes"
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: proxy-protocol-env-vars-haproxy
diff --git a/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy.yml b/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy.yml
index 6096d7c4..e22943ac 100644
--- a/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy.yml
+++ b/e2e-tests/proxy-protocol/compare/statefulset_proxy-protocol-haproxy.yml
@@ -110,6 +110,8 @@ spec:
               value: "yes"
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: proxy-protocol-env-vars-haproxy
diff --git a/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy-oc.yml b/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy-oc.yml
index fa0462e3..0bc144bd 100644
--- a/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy-oc.yml
+++ b/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy-oc.yml
@@ -126,6 +126,8 @@ spec:
               value: upgrade-haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: upgrade-haproxy-env-vars-haproxy
diff --git a/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy.yml b/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy.yml
index fa0462e3..0bc144bd 100644
--- a/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy.yml
+++ b/e2e-tests/upgrade-haproxy/compare/statefulset_upgrade-haproxy-haproxy.yml
@@ -126,6 +126,8 @@ spec:
               value: upgrade-haproxy-pxc
             - name: REPLICAS_SVC_ONLY_READERS
               value: "false"
+            - name: HA_SERVER_OPTIONS
+              value: resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions
           envFrom:
             - secretRef:
                 name: upgrade-haproxy-env-vars-haproxy
diff --git a/pkg/pxc/app/statefulset/haproxy_test.go b/pkg/pxc/app/statefulset/haproxy_test.go
index 14df533a..6d52d060 100644
--- a/pkg/pxc/app/statefulset/haproxy_test.go
+++ b/pkg/pxc/app/statefulset/haproxy_test.go
@@ -5,6 +5,7 @@ import (
 	"testing"
 
 	api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
+	"github.com/percona/percona-xtradb-cluster-operator/pkg/version"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
@@ -108,3 +109,108 @@ func pointerToTrue() *bool {
 	b := true
 	return &b
 }
+
+func TestHAProxyHealthCheckEnvVars(t *testing.T) {
+	tests := map[string]struct {
+		healthCheck     *api.HAProxyHealthCheckSpec
+		expectedEnvVars map[string]string
+	}{
+		"default values": {
+			healthCheck: nil,
+			expectedEnvVars: map[string]string{
+				"HA_SERVER_OPTIONS": "resolvers kubernetes check inter 10000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions",
+			},
+		},
+		"custom interval only": {
+			healthCheck: &api.HAProxyHealthCheckSpec{
+				Interval: func() *int32 { i := int32(3000); return &i }(),
+			},
+			expectedEnvVars: map[string]string{
+				"HA_SERVER_OPTIONS": "resolvers kubernetes check inter 3000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions",
+			},
+		},
+		"custom fall only": {
+			healthCheck: &api.HAProxyHealthCheckSpec{
+				Fall: func() *int32 { i := int32(3); return &i }(),
+			},
+			expectedEnvVars: map[string]string{
+				"HA_SERVER_OPTIONS": "resolvers kubernetes check inter 10000 rise 1 fall 3 weight 1 on-marked-down shutdown-sessions",
+			},
+		},
+		"custom rise only": {
+			healthCheck: &api.HAProxyHealthCheckSpec{
+				Rise: func() *int32 { i := int32(2); return &i }(),
+			},
+			expectedEnvVars: map[string]string{
+				"HA_SERVER_OPTIONS": "resolvers kubernetes check inter 10000 rise 2 fall 2 weight 1 on-marked-down shutdown-sessions",
+			},
+		},
+		"all custom values": {
+			healthCheck: &api.HAProxyHealthCheckSpec{
+				Interval: func() *int32 { i := int32(3000); return &i }(),
+				Fall:     func() *int32 { i := int32(2); return &i }(),
+				Rise:     func() *int32 { i := int32(1); return &i }(),
+			},
+			expectedEnvVars: map[string]string{
+				"HA_SERVER_OPTIONS": "resolvers kubernetes check inter 3000 rise 1 fall 2 weight 1 on-marked-down shutdown-sessions",
+			},
+		},
+	}
+
+	for name, tt := range tests {
+		t.Run(name, func(t *testing.T) {
+			cr := &api.PerconaXtraDBCluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "test-cluster",
+				},
+				Spec: api.PerconaXtraDBClusterSpec{
+					CRVersion: version.Version(),
+					HAProxy: &api.HAProxySpec{
+						PodSpec: api.PodSpec{
+							Image:             "test-image",
+							ImagePullPolicy:   corev1.PullIfNotPresent,
+							EnvVarsSecretName: "test-secret",
+						},
+						ExposeReplicas: &api.ReplicasServiceExpose{OnlyReaders: false},
+						HealthCheck:    tt.healthCheck,
+					},
+					PXC: &api.PXCSpec{
+						PodSpec: &api.PodSpec{
+							Configuration: "config",
+						},
+					},
+				},
+			}
+
+			haproxy := &HAProxy{cr: cr}
+
+			containers, err := haproxy.SidecarContainers(&cr.Spec.HAProxy.PodSpec, "test-secret", cr)
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if len(containers) != 1 {
+				t.Fatalf("expected 1 container, got %d", len(containers))
+			}
+
+			c := containers[0]
+
+			// Check that all expected env vars are set correctly
+			for expectedName, expectedValue := range tt.expectedEnvVars {
+				found := false
+				for _, env := range c.Env {
+					if env.Name == expectedName {
+						found = true
+						if env.Value != expectedValue {
+							t.Errorf("expected %s=%q, got %q", expectedName, expectedValue, env.Value)
+						}
+						break
+					}
+				}
+				if !found {
+					t.Errorf("%s env var not found in container", expectedName)
+				}
+			}
+		})
+	}
+}