← Back to Home

What NGINX to HAProxy Migration Taught Us About Config Blast Radius

Switching ingress controllers is not a lift-and-shift operation. NGINX and HAProxy are built on different architectural assumptions, and those differences compound at every layer — from how configuration is loaded to how certificates are selected to how the system behaves when a single rule is malformed.

This is a post-migration review of what we found, what broke, and what needs to be in place before any team runs this in production.

Executive Summary

Finding Risk
Single config file with large blast radius High
TLS certificate update requires pod restart Medium
Certificate priority determined implicitly by secret name ordering High
Advanced regex and rewrite rules not fully supported Medium
All NGINX annotations require manual remapping Medium
Config reload fails entirely on any error High
Observability and debugging are more complex Medium

The Architectural Divide

NGINX and HAProxy have fundamentally different design philosophies, and understanding this is the prerequisite for a successful migration.

NGINX is modular, flexible, and L7-heavy. Configuration is split across files, and most behavior can be extended through annotations and server blocks. HAProxy is centralized, high-performance, and rigid. There is one config file, one reload path, and one failure mode when something goes wrong.

Three problem categories define most of the friction in this migration:

  1. Reload and certificate handling
  2. Blast radius from config centralization
  3. Feature limitations in regex and annotation support

Findings

TLS Certificate Update Requires Pod Restart

TLS secret updates are not hot-reloaded by the HAProxy ingress controller. When a certificate is rotated in Kubernetes, the change does not propagate until the HAProxy pod is restarted.

This eliminates the possibility of zero-downtime certificate rotation without additional tooling. During a rolling restart, there is a short window where traffic is handed off between pods, which may cause brief disruption depending on how connections are drained.

The practical implication is that certificate rotation must be treated as a planned operation. Schedule it outside peak traffic, implement preStop hooks to drain connections gracefully, and verify readiness probes are configured to prevent traffic from hitting a pod before it is ready to serve.

Single Configuration File

All ingress rules are compiled into a single haproxy.cfg. There is no equivalent to NGINX’s conf.d/ pattern where each domain or service gets its own isolated file.

This matters for failure isolation. In NGINX, a bad config in one file typically prevents that file from loading while leaving the rest untouched. In HAProxy, a single invalid ingress resource causes the entire reload to fail. Every route served by that controller goes down simultaneously.

The blast radius is cluster-wide by default.

Mitigating this requires treating config validation as a mandatory gate. Add haproxy -c -f haproxy.cfg as a required step in the deployment pipeline before any ingress change is applied. Consider an admission webhook to reject invalid ingress resources at submission time, before they can reach the controller. Replicate the full production ingress config in staging to catch errors before they reach production.

TLS Secret Ordering and Lexicographical Priority

When multiple certificates cover the same FQDN, HAProxy selects between them based on the lexicographical sort order of the Kubernetes secret name.

This is not surfaced anywhere in the ingress resource definition. The behavior is implicit, undocumented in most operator documentation, and easy to trigger accidentally. Renaming a secret — even to something semantically equivalent — can silently change which certificate is served to end-users. In the worst case, this means serving an expired certificate in production with no configuration change and no visible error.

The fix is enforcing a one-to-one mapping between certificate secrets and FQDNs. Establish a naming convention for TLS secrets and treat deviations as a deployment error. Set up monitoring to compare served certificate CN/SAN values against expected values so any mismatch is caught before users notice.

Advanced Regex Not Fully Supported

HAProxy does not support complex PCRE patterns. Ingress rules that rely on lookaheads, backreferences, or advanced path rewriting through regex will either fail silently or produce incorrect routing behavior.

NGINX annotations like nginx.ingress.kubernetes.io/use-regex: "true" combined with complex path patterns have no direct equivalent in HAProxy. Path rewrite capabilities are also more limited.

This needs to be assessed before migration starts, not during. Audit every ingress rule that uses regex and categorize it: can it be simplified into a prefix or exact match, or does the logic need to move into the application layer or an API gateway such as Kong or Envoy? Discovering this mid-cutover creates pressure to push incomplete configurations to production.

Annotation Incompatibility

Every nginx.ingress.kubernetes.io/* annotation is ignored by the HAProxy ingress controller. There is no fallback, no warning, and no partial support. Annotations that have been silently handling SSL redirect, rate limiting, CORS, or custom headers for months simply stop working.

The equivalent annotations exist under the haproxy.org/* prefix, but behavior is not always semantically equivalent even when the mapping appears straightforward. Connection timeout handling, rewrite behavior, and header forwarding can differ in ways that are only visible under load or in specific edge cases.

Build a reference mapping table of NGINX-to-HAProxy annotations as part of migration prep. Run dry-run deployments and compare observed behavior against expected outcomes systematically across each service before cutover.

Config Reload Sensitivity

HAProxy enforces strict config validation at reload time. Any syntax or semantic error — including errors introduced by a single ingress resource that generates invalid config — causes a complete reload failure with no partial recovery.

NGINX can tolerate some config errors by isolating the affected block. HAProxy cannot. An operator managing 200 ingress rules in a shared cluster has a high-risk surface area. One misconfigured annotation in one team’s ingress definition can prevent all 200 services from updating their routes.

Managing this risk requires the following at minimum: mandatory haproxy -c validation in the pipeline, canary deployments for high-risk ingress changes, and a known-good config snapshot for fast rollback. Without these, a single bad deploy can take down the entire ingress tier.

Observability and Debugging Complexity

HAProxy logs are lower-level and not structured per ingress resource. Diagnosing a routing issue requires manually inspecting the full generated haproxy.cfg to trace how a request would be matched and forwarded.

Compared to NGINX ingress, where logs are per-request and relatively self-explanatory, debugging HAProxy behavior requires a deeper understanding of the generated config structure and ACL evaluation order. This increases the time to root cause, particularly for engineers who are not familiar with HAProxy internals.

Normalize log output through a parsing layer (Fluentd, Logstash, or similar). Write a troubleshooting runbook that covers the most common failure scenarios: reload failures, certificate mismatches, missing annotation mappings, and ACL evaluation order. Integrate with distributed tracing or APM to close the gap on end-to-end request visibility.

Conclusion

HAProxy is a high-performance proxy with strong operational guarantees. But those guarantees come with a governance model that is stricter than NGINX. Centralized config means higher blast radius. Implicit behaviors around certificate selection and strict reload semantics mean more room for silent failures.

Three areas must be production-ready before cutover:

  1. Config validation pipeline — mandatory before every ingress change is applied. No exceptions.
  2. TLS rotation procedure — documented, tested, and rehearsed with real traffic patterns, not just in theory.
  3. Full ingress audit — every regex pattern, annotation mapping, and certificate assignment reviewed individually. Not at a service level. At a rule level.

The migration is worth doing if the performance characteristics and load balancing capabilities of HAProxy justify the operational cost. But it is not a default drop-in replacement for NGINX. Treat it as an architectural change, not a configuration swap.