HAProxy 3.4 is a milestone release that significantly advances HAProxy’s legendary flexibility, performance, security, reliability, and observability.
Dynamic backend management simplifies integration with modern architectures, memory efficiency improves across a broader range of workloads, native cryptographic operations at the proxy layer open new possibilities for API security architectures, and OpenTelemetry support makes HAProxy a first-class participant in distributed tracing pipelines.
Meanwhile, operational improvements in health checking, attack resistance, and log management mean HAProxy remains the best choice for the world's most demanding environments.
These advances extend HAProxy's lead across G2 categories in Load Balancing, API Management, Container Networking, DDoS Protection, and Web Application Firewall (WAF).
What’s new in HAProxy 3.4?
|
Feature |
|
|
Flexibility |
Dynamic backends allow operators to add, publish, and delete backends at runtime without a reload. |
|
QMux enables HTTP/3 and QUIC on TCP-only networks. |
|
|
Performance |
Smarter buffer allocation reduces memory footprint by adapting to workload size. |
|
Up to 20% higher request rate achieved via improved efficiency on large-core-count CPUs. |
|
|
Security and TLS |
Native cryptographic operations at the proxy layer provide built-in JWT decryption and AES encryption/decryption. |
|
Enhanced ACME features simplify domain validation and certificate management. |
|
|
Reliability |
The glitch detector now covers H1 (in addition to H2 and QUIC) and closes misbehaving connections gracefully. |
|
Stream elasticity moderates the number of concurrent H2 and H3 streams per connection depending on system load. |
|
|
Observability |
HAProxy 3.4 introduces OpenTelemetry support as an add-on replacing OpenTracing. |
|
OpenTracing is now officially deprecated and will be completely removed in version 3.5. |
In this blog post, we’ll explore all the latest changes in detail. As always, enterprise customers can expect to find these features included in the next version of HAProxy Enterprise load balancer.
Sign up for our webinar, What's new in HAProxy 3.4, and join our experts as we examine new features and updates.
New to HAProxy?
HAProxy is the world’s fastest and most widely used software load balancer. It provides high availability, load balancing, and best-in-class SSL/TLS processing for TCP, QUIC, and HTTP-based applications.
HAProxy is the open source core that powers HAProxy One, the world’s fastest application delivery and security platform. The platform consists of a flexible data plane (HAProxy Enterprise) for TCP, UDP, QUIC, and HTTP traffic; a scalable control plane (HAProxy Fusion); and a secure edge network (HAProxy Edge).
HAProxy is trusted by leading companies and cloud providers to simplify, scale, and secure modern applications, APIs, and AI services in any environment.
How to upgrade to HAProxy 3.4?
You can install HAProxy 3.4 in any of the following ways:
Install the new HAProxy packages for Ubuntu, Debian, and RHEL.
Run it as a Docker container. View the Docker installation instructions.
Compile it from source. View the compilation instructions.
Flexibility
HAProxy 3.4 delivers greater flexibility than ever, simplifying integration into complex environments and enabling new use cases.
The headline addition is the introduction of dynamic backends, which extends HAProxy’s strengths in modern, automated environments. Building on the dynamic servers capability introduced in HAProxy 2.4, dynamic backends allow backends to be added, published, and deleted at runtime without requiring a reload. The result is fully automated backend lifecycle management, driven directly from your control plane or orchestration layer.
Experimental QMux support also lands in 3.4, enabling HTTP/3 and QUIC over TCP, useful in networks where UDP is blocked or not a suitable transport layer.
Dynamically add and delete backends
New HAProxy Runtime API commands let you add, delete, and publish backend sections. Publish makes the backend available for use.
First, consider this HAProxy configuration:
| global | |
| stats socket /run/haproxy/admin.sock user haproxy group haproxy mode 660 level admin | |
| defaults mydefaults | |
| log global | |
| mode http | |
| option httplog | |
| option dontlognull | |
| timeout connect 10m | |
| timeout client 10m | |
| timeout server 10m | |
| frontend mysite | |
| bind :80 | |
| use_backend %[path,map_beg(virt@paths.map)] | |
| default_backend webservers | |
| backend webservers | |
| server web1 127.0.0.1:8080 |
The global section enables the HAProxy Runtime API, alongside a defaults section named mydefaults and a frontend named mysite. The frontend uses a map file to route requests to the appropriate backend based on the requested URL path. The map file is virtual, meaning it only exists in memory, and is initially empty. If no entry matches the requested URL path, requests are routed to the default backend, webservers.
We use the HAProxy Runtime API to perform the following:
Create a new test-backend backend with a server, inheriting settings from the mydefaults defaults section.
Enable the server and its health checks.
Publish the backend so that the frontend can use it.
Add an entry to our map file to route requests for the path
/testto the new backend.
The corresponding HAProxy Runtime API commands are shown below:
| # Add a backend named 'test-backend' | |
| echo "add backend test-backend from mydefaults mode http" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # 'New backend registered.' | |
| # Add a server to the backend | |
| echo "add server test-backend/server1 127.0.0.1:3000 check" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # 'New server registered.' | |
| # Enable the server | |
| echo "enable server test-backend/server1" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # Enable health checks | |
| echo "enable health test-backend/server1" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # Publish the backend | |
| echo "publish backend test-backend" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # 'Backend published.' | |
| # Add an entry to the map file | |
| echo "add map virt@paths.map /test test-backend" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # Show map displays entry_cnt | |
| echo "show map" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # id (file) description | |
| 5 (virt@paths.map) pattern loaded from file 'virt@paths.map' used by map at file '/etc/haproxy/hapee-lb.cfg' line 45. curr_ver=0 next_ver=0 entry_cnt=1 |
At this point, we've created the backend and populated it with a server, we updated the map file with an entry that routes requests for the URL path /test to the new backend, and the configuration is ready to serve traffic.
To delete the server, backend, and map entry, use the following commands:
| # Place the server into maintenance mode | |
| echo "set server test-backend/server1 state maint" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # Wait for the server to be removable | |
| # then remove the server | |
| echo "wait 2s srv-removable test-backend/server1; del server test-backend/server1" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # 'Server deleted.' | |
| # Unpublish the backend | |
| echo "unpublish backend test-backend" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # 'Backend unpublished.' | |
| # Wait for the backend to be removable | |
| # then delete the backend | |
| echo "wait 2s be-removable test-backend; del backend test-backend" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # 'Backend deleted.' | |
| # Delete the map file entry | |
| echo "del map virt@paths.map /test" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # Show map displays entry_cnt | |
| echo "show map" | sudo socat stdio unix-connect:/run/haproxy/admin.sock | |
| # id (file) description | |
| 5 (virt@paths.map) pattern loaded from file 'virt@paths.map' used by map at file '/etc/haproxy/hapee-lb.cfg' line 45. curr_ver=0 next_ver=0 entry_cnt=0 |
A few considerations worth mentioning when working with dynamic backends:
A backend referenced by the
default_backendoruse_backenddirectives in a frontend, will be skipped if it has been disabled or unpublished. Setforce-be-switchto override and force HAProxy to use the backend.In order to ensure that all named
defaultssections are available to dynamic backends, they are now stored in memory. If you don't intend to use dynamic backends, set the globaltune.defaults.purgedirective to free that memory.
QMux protocol
This version adds experimental support for QMux, a protocol that, according to the draft specification, allows sending QUIC frames over any transport protocol that provides an ordered, reliable, bidirectional, byte-oriented stream. It enables TCP to transport QUIC, offering an alternative for networks where QUIC's processing overhead over UDP outweighs its benefits (e.g., fast and reliable intra-datacenter networks).
To enable QMux, add the alpn h3 argument to the target frontend bind or backend server line and include expose-experimental-directives in the global section. Since the protocol is still in its early stages, one practical way to test this is to chain two HAProxy instances together, as illustrated by the configuration below, which allows QMux to be evaluated on both the frontend and backend.
| frontend mysite | |
| bind :80 | |
| use_backend webservers | |
| # Enable QMux in the backend | |
| backend webservers | |
| server myserver 127.0.0.1:443 ssl verify none alpn h3,h2 | |
| # Enable QMux in the frontend | |
| frontend qmux-fe | |
| bind :443 ssl crt /etc/haproxy/ssl.pem alpn h3,h2 | |
| http-request return status 200 content-type text/plain string "Hello, qmux!" |
Lua
HAProxy can now be built with the latest version of Lua (version 5.5), incorporating five years’ worth of improvements in the language.
A new global directive, tune.lua.openlibs, provides control over which Lua standard libraries are loaded. Omitting unused libraries reduces the attack surface of Lua scripts and helps enforce security practices, particularly when scripts originate from third parties or external customers. For example:
Omitting os disables os.execute() and os.exit().
Omitting io disables io.open() and io.popen().
Omitting package prevents loading native C modules via require().
Omitting debug prevents introspection of HAProxy internals via debug.getupvalue(), debug.getmetatable(), or debug.sethook().
Set timeouts dynamically
The http-request set-timeout directive, introduced in HAProxy 2.4, originally gave the ability to change the timeout server and timeout tunnel values dynamically on a per-request basis. HAProxy 2.9 extended it to cover timeout client. Now, in HAProxy 3.4, http-request set-timeout can also adjust the values of timeout connect, timeout queue, and timeout tarpit. Together, these make it easier to apply application-specific timeouts, especially when combined with map files.
New fetches have been added to return the values of these timeouts: be_connect_timeout, be_queue_timeout, be_tarpit_timeout, cur_connect_timeout, cur_queue_timeout, cur_tarpit_timeout, and fe_tarpit_timeout.
Binary HTTP headers
New HTTP request and response actions manage (add, set, or delete) HTTP headers, storing them as data with a variable-length integer binary encoding. Refer to the documentation for these actions:
add-headers-binset-headers-bindel-headers-bin
Passing headers as binary data is a convenient way to modify them as a group rather than individually. This format is commonly used with the Stream Processing Offload Protocol (SPOP), making these actions particularly useful when communicating with stream processing offload agents. HAProxy also provides the req.hdrs_bin and res.hdrs_bin fetches, which return request and response headers in this format. Captured headers can be stored in variables and restored to their original state when needed.
This simplifies the exchange of multiple HTTP header fields between HAProxy and an SPOE agent: headers can be serialized and deserialized via a single variable, allowing multiple headers to be exchanged with a single declaration when the agent is trusted. A matching prefix can be specified on these actions to isolate the headers that an agent is permitted to manipulate.
QUIC protocol
This release introduces several improvements to HAProxy's QUIC protocol implementation:
The
quic-cc-algoargument is now supported by theserverdirective, whereas it had been supported only by thebinddirective. This argument defines the QUIC congestion control algorithm, allowing the algorithm to be tuned independently for the frontend and backend network topologies. This change has been backported to HAProxy 3.3.The new global directive
tune.quic.fe.stream.max-totallimits the maximum number of requests that a single QUIC connection can handle. Once the limit is reached, HAProxy initiates a graceful shutdown of the connection (a GOAWAY frame in HTTP/3) and the connection is closed when all remaining transfers are completed.
HTTP compression
The syntax for HTTP request and response compression has been revised. Previously, compression was enabled by setting filter compression in a backend, with the option to set the compression direction directive to indicate whether to compress requests, responses, or both. The new model splits this into two filters: filter comp-req for request compression and filter comp-res for response compression. Separating the two simplifies the eventual addition of a decompression filter.
The following example compresses responses:
| backend web_servers | |
| filter comp-res | |
| compression algo gzip | |
| compression type text/css text/html text/javascript application/javascript text/plain text/xml application/json |
Filter sequence
A new directive, filter-sequence, provides explicit control of the order in which filter directives are applied. Previously, filter execution was determined by the order in which filters were declared. With filter-sequence, filters can now be declared in any order and their execution sequence is managed independently. This is especially useful when execution order affects behavior. A good example is traffic shaping configurations that combine bandwidth limiting and compression filters. Placing compression before the limiter causes the limit to be applied on compressed traffic, which changes whether the traffic is actually throttled.
Another practical benefit of the filter-sequence directive is that any filter declared in the configuration, but omitted from the sequence directive is skipped. That's a convenient way to temporarily disable a filter without removing it from the configuration.
do-log action
In HAProxy 3.4, the do-log action now accepts the name of a log profile section as an argument.
The do-log action, introduced in version 3.1, emits custom log messages at various stages of request and response processing. The workflow is straightforward: define a log-profile section with log format strings (templates), then have do-log invoke them. For instance, a log format string might print the value of a variable named req.log_message during the processing of HTTP request rules. In the corresponding frontend, the variable would be set and then invoked with http-request do-log to log its value.
Previously, the log profile was selected per frontend via a log line. That meant that every do-log action in a specific frontend had to use the same log profile. Now each do-log action can specify its own profile, providing more control and flexibility in selecting which log format applies to each stage of the request or response.
| log-profile syslog | |
| log-tag "haproxy" | |
| on close format "$HAPROXY_HTTP_LOG_FMT" | |
| on http-req format "%[var(req.log_message)]" | |
| frontend mysite | |
| bind :80 | |
| acl is_evil_client req.hdr(user-agent) -m sub evil | |
| http-request set-var(req.log_message) str("Blocking evil client") if is_evil_client | |
| http-request do-log profile syslog | |
| http-request deny if is_evil_client |
Performance
HAProxy 3.4 enhances the proven performance of the world’s fastest and most widely used load balancer.
HAProxy's buffer system has been reworked: large buffers can be allocated on demand for body-inspection workloads, eliminating the need to raise the global tune.bufsize and inflate memory consumption across every connection. Small buffers can also be substituted for queued and retried requests, reducing memory pressure under load.
A scheduler overhaul preserves low latency under extreme load, shared stats counters can be split by thread group, and new CPU topology controls deliver further gains on large-core-count hardware.
Tuning buffer size
New buffer size options provide finer-grained control over the amount of memory HAProxy uses for different categories of data. Buffers play a central role in HAProxy's operation and are used in various places to store incoming and outgoing data, including HTTP requests and responses, log messages, health check exchanges, and payload data. A uniform global buffer size often results in suboptimal memory allocation: a large buffer may waste memory when used to store small queued requests, while a smaller buffer might be insufficient to handle larger payloads, such as HTTP message bodies.
The new global directives tune.bufsize.large and tune.bufsize.small allow distinct sizes to be defined for different categories of data. The corresponding directive option use-small-buffers, set in a backend or defaults section, enables the small buffer for queues, L7 retries, and health checks. The large buffer applies to the action wait-for-body, used during HTTP message body processing. These directives enable appropriate buffer sizes for these use cases, while keeping the global buffer size unchanged.
The release also adds tune.cli.max-payload-size, which defines the maximum payload size accepted by the HAProxy Runtime API.
Task scheduler
As a request moves through HAProxy, different stages of processing are handled by short-running functions called tasks. HAProxy's task scheduler determines which task will run next on each thread based on each task's priority and urgency. This release includes some enhancements to the scheduling mechanisms that address inconsistencies in wake, queueing, and prioritization behavior for tasks. These are edge cases that surface under sustained attack traffic or recovery scenarios. Testing confirms reduced latency when processing large queues of tasks and improved responsiveness of the HAProxy Runtime API.
Stats page counters
A new directive, stats calculate-max-counters, controls whether stats max counters are computed. Counters in this category include the max connection rate per second, max session rate per second, and max request rate per second.
Calculating maximums requires an expensive coordination between all threads, and in practice, virtually nobody uses it anymore since it only lasts for the process's lifetime; today, users have external solutions that collect stats and calculate maxes over periods of time instead.
This directive is enabled (on) by default; it may be set to off to disable these counters and save resources.
Automatic CPU binding
HAProxy 3.2 introduced options for tuning the automatic CPU binding, or how HAProxy organizes its threads to make efficient use of the underlying hardware. Version 3.4 adds a global keyword, cpu-affinity, that enables more control over how the threads bind to CPUs. HAProxy organizes its threads based on system topology and assigns each thread group a set of CPUs; threads in a group are only allowed to run on those CPUs.
On NUMA systems, this keeps inter-thread operations within physically adjacent CPUs to reduce latency. The default, per-group, lets any thread in a group run on any CPU assigned to that thread group. While this offers the most OS flexibility in scheduling, this may not always be the best choice for latency. The options for cpu-affinity allow changes to this behavior:
per-core: a thread may run on any hardware thread of a single SMT core (typically two threads per core in modern SMT implementations). The OS retains flexibility in scheduling IRQ activity. For example, from the NIC. HAProxy's threads can run on either hardware thread, keeping latency between HAProxy and the NIC low.per-thread: each thread will be bound to a single, specific hardware thread. Stricter thanper-core, which permits movement between the hardware threads of a core.per-ccx: on systems with multiple CCX, such as AMD EPYC, this setting allows each thread to run on any hardware threads within all the cores of a single CCX.
There is an additional loose option for cpu-affinity per-group (cpu-affinity per-group loose). When a set of CPUs must be split over several thread groups, this allows multiple thread groups to use all CPUs in the list without each thread group being confined to a specific subset of the CPUs. The default, auto, which prevents this sharing by assigning each group to its own subset of CPUs, is usually the better choice. However, loose can perform better when CPU usage is uneven across groups.
This release adds a new threads-per-core option for the cpu-policy global directive, accepting a value of either 1 or the default, auto. Setting the value to 1 constrains HAProxy thread to a single thread per core on SMT-enabled CPUs (such as those implementing Intel's Hyper-Threading), leaving the other thread of the core free for other usage, most commonly the NIC. Improved performance has been observed in situations where there is high network activity on the same CPUs or during periods of frequent reloads that result in multiple HAProxy processes remaining active for extended periods of time. With the default auto setting, HAProxy creates a thread per each hardware thread. When threads-per-core is set to 1 and no explicit cpu-affinity value is set, the affinity defaults to per-core.
The following examples illustrate common configurations.
Intel Xeon with 64 cores with SMT (Hyper-Threading) enabled, where HAProxy will use one thread and the NIC may use the other thread of the same cores:
| global | |
| cpu-policy performance threads-per-core 1 | |
| cpu-affinity per-core |
In this case, max-threads-per-group is set to 16 automatically, which is the default.
The next example involves AMD EPYC with 4 cores per CCX, where each thread in a group may use all hardware threads within a single CCX:
| global | |
| cpu-affinity per-ccx | |
| max-threads-per-group 4 |
In this scenario, cpu-policy performance is set automatically by default.
A new global option, max-threads-per-group, sets the maximum number of threads permitted in a single thread group. HAProxy defines the number of thread groups automatically based on the underlying hardware, and any tuning directives, including cpu-policy and cpu-affinity. On NUMA systems, this value often corresponds to the number of CPUs per CCX, and on systems with a single, unified L3 cache it corresponds to the total number of available cores. Setting max-threads-per-group provides fine-grained control. A higher number of threads in a group can introduce contention, while a lower number can increase the number of sockets required. Internal testing identified 16, the default, as the best overall tradeoff across the majority of systems.
Before adjusting these defaults, it is recommended to evaluate the system’s CPU topology, NUMA characteristics and NIC configurations. The performance tuning guide provides a step-by-step reference.
HTTP/2 performance
New global directives help mitigate HTTP/2 protocol attacks:
tune.h2.fe.max-frames-at-once– Sets the maximum number of HTTP/2 incoming frames processed at once on a frontend connection. Typically, you can leave this at the default value.tune.h2.be.max-frames-at-once– Sets the maximum number of HTTP/2 incoming frames processed at once on a backend connection. Typically, you won't change this.tune.h2.fe.max-rst-at-once– Sets the maximum number of HTTP/2 incoming RST_STREAM frames processed at once on a frontend connection. A low value (1 to 10) is effective for sites that face frequent RST-based attacks. Note that very low values, such as 1, which are the most effective at erasing the impact of such attacks, might slightly increase the perceived latency on highly-interactive sites or gRPC services.tune.h2.fe.max-total-streams– Sets the maximum number of HTTP/2 streams in total processed per incoming connection. Once the limit is reached, the connection will be recycled. This curbs the ability of misbehaving clients to flood connections. Values around 1000 are already very effective without observable impact for the user.tune.streams-elasticity– Defines a target percentage of streams per frontend connection relative to the maximum number of concurrent connections (maxconn) when all connections are established. As the number of concurrent connections grows, the number of per-connection concurrent streams is reduced, dynamically redistributing unallocated streams over existing connections. The result is that the service remains highly responsive at moderate loads and resists overload under extreme loads, while maintaining reasonable resource usage.
Additionally, the global tune.h2.fe.max-concurrent-streams directive, which sets the maximum number of HTTP/2 concurrent streams per incoming connection, now accepts two new arguments: rq-load and min. The rq-load argument dynamically adjusts concurrency based on the executing thread's run-queue load. The min argument sets a floor on the advertised concurrency level when using rq-load, even if this results in a higher load than the configured target.
Reusing idle server connections
The new global directive tune.idle-pool.shared enables sharing idle server connections across threads. Idle connection reuse is a valuable optimization in most deployments, and this directive provides explicit control over the behavior. Accepted values are on (share connections between threads in the same thread group), full (share across all threads), and off (disable sharing entirely, useful for debugging a connection reuse issue). This new directive deprecates tune.takeover-other-tg-connections, which was introduced in version 3.2 and served a similar purpose.
HATerm
The HAProxy GitHub repository now includes haterm, a lightweight HTTP server built on HAProxy. It’s intended for benchmarking and other exercises that require a simple, configurable HTTP server with options for customizing its internal configuration and behavior.
It's the successor to the earlier httpterm utility, which was HTTP/1 only and lacked SSL support. This new utility supports H1/H2/H3 over QUIC, TCP and SSL, and benefits from HAProxy's scalability under extreme load. A complementary client, haload, is under active development and will be released soon to replace h1load.
Learn more in the HATerm documentation.
Security and TLS
HAProxy 3.4 introduces greater flexibility in cryptographic security and TLS management. Native cryptographic operations at the proxy layer (JWT decryption, AES enc/dec) provide additional building blocks for API security architectures. Improvements to ACME configuration, TLS certificate compression, and TLS decryption further strengthen HAProxy’s SSL/TLS processing.
JSON Web Tokens
This release adds new options for validating JSON Web Tokens (JWTs). HAProxy can now decrypt JWE tokens natively at the proxy layer, enabling inspection of encrypted JWT claims before routing or access decisions.
The global directive
jwt.decrypt_alg_listdefines a colon-separated list of permitted algorithms in tokens decrypted by thejwt_decrypt_*converters. This enables you to reject tokens that use an unsupported algorithm for thealgparameter.The global directive
jwt.decrypt_enc_listdefines a colon-separated list of permitted encryption algorithms in tokens decrypted by thejwt_decrypt_*converters. This enables you to reject tokens that use an unsupported encryption algorithm for theencparameter.The converter
jwt_decrypt_certperforms asymmetric decryption with ECDH-ES with EC certificates. When provided a certificate, the converter returns the decrypted contents of the JWT input sample.The converter
jwt_decrypt_secret, when provided with a base64-encoded secret, returns the decrypted contents of the JWT input sample.The converter
jwt_decrypt_jwk, when provided with a JSON Web Key, returns the decrypted contents of the JWT input sample following the JSON Web Encryption format.
AES CBC converters
This release adds new converters relating to AES CBC encryption and decryption, supporting token manipulation, payload masking, and secure session handling natively in HAProxy.
The
aes_cbc_decconverter decrypts the raw byte input using the AES128-CBC, AES192-CBC, or AES256-CBC algorithm, depending on the bits parameter.The
aes_cbc_encconverter encrypts the raw byte input using the AES128-CBC, AES192-CBC, or AES256-CBC algorithm, depending on the bits parameter.
Enhanced ACME features
HAProxy is an early adopter of a new way to validate domain ownership through the ACME protocol for TLS certificate issuance. The DNS-PERSIST-01 challenge works by publishing a TXT record in your DNS server that contains the CA name and ACME account ID to serve as proof of domain ownership and, subsequently, authorizes issuing a TLS certificate. Contrary to DNS-01, which requires periodic updates of the challenge in the DNS record, DNS-PERSIST-01 permits setting a persistent record, so is more suitable for DNS zones managed manually, where rotating a record at each renewal isn’t practical. Rollout of this new challenge type is ongoing at providers like Let's Encrypt with wider availability expected later this year.
Also in this release, the acme configuration section has a new directive, challenge-ready, that sets how HAProxy can determine if the TXT record of a DNS-01 challenge is ready. The available options are:
dnsinstructs HAProxy to resolve the TXT record to ensure that it's ready.cliinstructs HAProxy to use an external tool to check DNS.delayinstructs HAProxy to add a delay period.noneinstructs HAProxy to proceed with validation immediately.
The defaults are sensible for most deployments, so this directive can usually be left unset. Two complementary directives tune the active modes: dns-delay sets the delay wait period under delay, and dns-timeout sets the maximum resolution time for the TXT record under dns.
The acme configuration section also introduces a profile directive that implements the ACME Profiles extension. An ACME profile indicates the type of certificate to request from the certificate authority; valid options are determined by the profiles offered by the CA. For example, Let's Encrypt offers several ACME profiles.
In addition, this release supports the inclusion of IP addresses in the Subject Alternative Name (SAN) field of ACME-issued certificates, configured via the ips argument on the load directive within a crt-store section.
HAProxy 3.4 further introduces support for ACME EAB (External Account Binding), which aims to protect ACME accounts against unauthorized access. You can configure EAB through the following directives:
eab-key-id– Configure the path to the EAB key ID file. The credential is provided by the CA and must be placed at the specified path before starting HAProxy. It's used during account creation only.eab-mac-key– Configure the path to the EAB MAC key file. The credential is provided by the CA and must be placed at the specified path before starting HAProxy. It's used during account creation only.eab-mac-alg– Configure MAC algorithm used for EAB signing. The default is HS256. The EAB MAC key must be large enough to support the specified MAC algorithm. Not all CAs support algorithms other than HS256.
TLS dummy certificate
HAProxy can now generate a self-signed TLS certificate directly, which can be useful in testing and scenarios where certificates might become available only after HAProxy has started. The following arguments are available on the load directive within a crt-store section:
generate-dummy – Sets a self-signed certificate and private key.
keytype – Sets the type of key, either RSA or ECDSA.
bits – Sets the number of bits to use for RSA certificate generation.
curves – Sets the elliptic curve to use for ECDSA certificate generation.
TLS certificate compression
HAProxy now supports TLS certificate compression as defined by RFC 8879. The new global directive tune.ssl.certificate-compression governs the feature. The default value, auto, follows the configuration of the underlying TLS library, while a value of off disables compression entirely. Compressing certificates exchanged between clients and HAProxy reduces transferred bytes and can lead to latency improvements.
Decrypting TLS
This release simplifies decrypting TLS during debug sessions by introducing variables that return the properties required to create a keylog file. Previously, you had to combine several variables into a log format string to produce the keylog output. Two new consolidated variables, HAPROXY_KEYLOG_FC_LOG_FMT and HAPROXY_KEYLOG_BC_LOG_FMT, can be referenced directly in a log format.
Reliability
HAProxy 3.4 builds on HAProxy’s legendary reliability. The glitch detector has been extended to support HTTP/1 in addition to HTTP/2 and QUIC, closing connections gracefully when misbehavior is detected. This release also brings improvements to health check configuration, protocol handling, load balancing algorithms, and error logging.
HTTP/1 glitches
Two updates affect HTTP glitch detection:
HAProxy 3.4 expands the glitch detector to include the HTTP/1 multiplexer. Previously, only HTTP/2 and QUIC were covered.
When HAProxy is configured to terminate connections that have too many glitches, it will now try to gracefully close the connection upon reaching 75% of the configured threshold rather than waiting until the limit is reached. Frontend and backend thresholds are set with
tune.h1.fe.glitches-thresholdandtune.h1.be.glitches-threshold.
HAProxy 3.0 introduced the concept of glitches. The term refers to unusual HTTP messages that could cause problems in the infrastructure if handled. Glitches might signal a malfunctioning client or server, or in some cases it may indicate a protocol attack. Several of the recent HAProxy releases have steadily expanded the glitch detector: the fc_glitches and bc_glitches fetches return the number of glitchy requests and responses; glitch_cnt and glitch_rate stick table data types make it possible to track glitches over time; and global options can terminate connections that exceed a configured glitch threshold. Expanding this functionality to HTTP/1 rounds out this helpful feature.
Health check section
A new healthcheck section promotes defining reusable health-check directives. Directives declared in a healthcheck section are applied to a server via the healthcheck argument on the server line, as shown below:
| healthcheck mycheck | |
| type httpchk | |
| http-check connect alpn h2 | |
| http-check send meth HEAD uri /health ver HTTP/2 hdr Host www.example.com | |
| backend webservers | |
| server web1 10.0.0.1:80 check healthcheck mycheck |
This enables assigning distinct health-check settings to individual servers within the same backend. Also, it allows a single health-check definition to be shared across multiple backends without duplication. The healthcheck section supports all available check types, including HTTP, TCP, SMTP, Redis, and it supports all http-check and tcp-check actions.
Better random algorithm
The random load balancing algorithm, which became the default in version 3.3, replacing roundrobin, now provides improved traffic distribution. The algorithm selects two servers at random from the pool of available servers and chooses the least loaded server, with load measured by the concurrent connection count.
When comparing servers with the same number of concurrent connections, HAProxy now also considers recent traffic history (HTTP requests per second). This produces a more even distribution across large backend pools where many servers sit at identical connection counts. HAProxy can then make a more informed choice when selecting a server.
Fetching the HTTP version
HAProxy 3.4 standardizes the retrieval of the HTTP protocol version associated with a request or response.
Identifying the HTTP version is non-trivial, as HTTP/1, HTTP/2, and HTTP/3 each indicate their versions differently. HAProxy provides several fetches for this purpose, such as req.ver, res.ver, capture.req.ver, and capture.res.ver, but coverage across protocol versions has been inconsistent. In this release, these fetches operate uniformly across all supported HTTP versions. Both req.ver and res.ver return the version as major.minor; the capture variants return HTTP/major.minor.
Prometheus local update metric for stick tables
The HAProxy Prometheus endpoint exposes stick table metrics whenever a stick table is declared in the configuration. HAProxy 3.4 adds a stick table metric named haproxy_sticktable_local_updates. This gauge reports the cumulative number of updates on the stick table, allowing you to monitor the rate of updates over time.
HTTP/2 error logs
While having comprehensive logging is essential, controlling the volume of logs is also important. A new global directive named tune.h2.log-errors defines the scope of error logging for HTTP/2 traffic, accepting values of stream, connection, or no error. The default, stream, is the most verbose. Having the ability to adjust this setting as needed lets you favor efficient resource use while preserving the option to increase verbosity when required.
Debugging
The global directive set-dumpable supports a new value, libs, which instructs HAProxy to embed a copy of the binaries and libraries required for debugging into the resulting core dump. This eliminates the need to locate these files on the filesystem after the fact and removes the risk that they don't match the core. You can then extract the embedded libraries by using the libs-from-core tool, which is published in the HAProxy GitHub repository.
Also, the show profiling HAProxy Runtime API command now provides finer-grained information about runtime memory consumption when invoked with the memory argument, thanks to the notion of execution context.
Observability with OpenTelemetry
HAProxy introduces OpenTelemetry support, making it a native participant in your existing observability stack.
The new OpenTelemetry filter allows HAProxy to generate spans (the individual units of work that make up a distributed trace) alongside logs and metrics, all in the standard OpenTelemetry format. This makes each request's journey through the load balancer directly consumable by any OTLP collector over gRPC, HTTP endpoints, or local files.
HAProxy's event subsystem provided the architectural groundwork for this integration, enabling fine-grained hooks into the load balancer's inner workings.
OpenTelemetry is the industry standard for distributed observability. By adopting it, HAProxy can now surface telemetry data in the same unified view as the rest of the stack, providing full visibility into the many steps a request undergoes as it traverses the infrastructure — without the need for custom integrations or proprietary SDKs.
Enabling the feature requires a new filter opentelemetry directive. The integration is controlled by two configuration files that define which HAProxy events are subscribed to and the endpoints to which telemetry data is forwarded.
Events can be enriched with key-value attributes, custom log messages, and ACL conditions to filter which events are captured.
The OpenTelemetry client library is experimental and ships as a separate add-on via the haproxy-opentelemetry repository and must be compiled into HAProxy to be enabled. The GitHub repository has build instructions and documentation. Configuration tutorials are coming soon.
OpenTracing, the previous distributed tracing integration, has been deprecated as of HAProxy 3.4 and will be removed in version 3.5. Organizations currently using OpenTracing should plan to migrate to the new OpenTelemetry integration ahead of the 3.5 release.
Fetch methods
New fetch methods in this release are as follows:
|
Fetch method |
Description |
|
|
Returns the measured CPU usage over the last polling loop, between 0 and 100, averaged over all threads of the current thread group. |
|
|
Returns the measured CPU usage over the last polling loop, between 0 and 100, averaged over all running threads. |
|
|
Returns the measured CPU usage over the last polling loop, between 0 and 100, for the calling thread. |
|
|
Returns a list of all variables in the specified scope. You can also filter the result by name prefix and customize the delimiter. |
|
|
Returns the configuration value in milliseconds for the connect timeout of the current backend. |
|
|
Returns the configuration value in milliseconds for the queue timeout of the current backend. |
|
|
Returns the configuration value in milliseconds for the queue timeout of the current backend. |
|
|
Returns the currently applied connect timeout in milliseconds for the stream. |
|
|
Returns the currently applied queue timeout in milliseconds for the stream. |
|
|
Returns the currently applied tarpit timeout in milliseconds for the stream. |
|
|
Returns the configuration value in milliseconds for the tarpit timeout of the current frontend. |
|
|
Returns the name of the certificate that was selected for the incoming SSL/TLS connection. |
|
|
Returns an integer value corresponding to the position of the thread group calling the function, between 0 and (global.thread-groups - 1). This is useful for logging and debugging purposes. |
Converters
New converters in this release are as follows:
|
Converter |
Description |
|
|
When provided with a certificate, returns the decrypted contents of the JWT input sample. |
|
|
When provided with a base64-encoded secret, returns the decrypted contents of the JWT input sample. |
|
|
When provided with a JSON Web Key, returns the decrypted contents of the JWT input sample following the JSON Web Encryption format. |
|
|
Decrypts the raw byte input using the AES128-CBC, AES192-CBC, or AES256-CBC algorithm, depending on the bits parameter. |
|
|
Encrypts the raw byte input using the AES128-CBC, AES192-CBC, or AES256-CBC algorithm, depending on the bits parameter. |
|
|
Returns true if a frontend with the name matching the input exists in the configuration, otherwise it returns false. |
Deprecated features
HAProxy 3.4 deprecates these features:
The
compression-directiondirective is deprecated.OpenTracing is deprecated in version 3.4 and will be removed in 3.5.
Breaking changes
HAProxy 3.4 has the following breaking changes:
The Stats page won't display the HAProxy version, but it can be enabled by using
stats show-version.
Conclusion
HAProxy 3.4 introduces a dynamic backend system that streamlines operation in modern architectures, smarter buffer allocation, measurable throughput gains, native JWT decryption and AES processing at the proxy layer, and native OpenTelemetry support — alongside operational improvements in health checking, attack resistance, and log management.
If you love HAProxy and want the ultimate HAProxy experience with next-gen security, plus multi-cloud management and observability, contact us for a demo of HAProxy One, the world’s fastest application delivery and security platform.
As with every release, it wouldn’t have been possible without the HAProxy community. Your feedback, contributions, and passion continue to shape the future of HAProxy. So, thank you!
Ready to upgrade or make the move to HAProxy? Now’s the best time to get started. You can install HAProxy 3.4 in any of the following ways:
Install the new HAProxy packages for Ubuntu, Debian, and RHEL.
Run it as a Docker container. View the Docker installation instructions.
Compile it from source. View the compilation instructions.