Hi,
HAProxy 3.4.5 was released on 2026/09/24. It added 85 new commits
after version 3.4.4.
This release fixes a number of bugs in the HTTP/3 and QUIC stacks, in the
HTTP message processing, in the Lua integration and in the internal HTTP
client used by the ACME and OCSP features, along with several smaller fixes
and two performance improvements.
* h3: Truncated HTTP/3 frames are now rejected.
HTTP/3 connections are now rejected when a stream ends before all the
bytes announced in a frame have arrived. Previously, an incomplete frame
could be accepted, creating a risk of request-content smuggling when the
request was forwarded. This is an important fix for deployments accepting
HTTP/3 traffic. This issue was reported by Rajat Raghav.
In relation to this issue, the H1 multiplexer was hardened so that an
incomplete message can never be emitted to a server as a complete one:
trailers are no longer sent while the last chunk is unfinished, and a
connection on which the message body was not fully sent is now always
closed instead of being reused, whether the body was announced with a
Content-Length or chunked.
* quic: Connection and stream leaks on QUIC frontends were fixed.
Two issues could prevent QUIC connections from being properly released.
A client sending a RESET_STREAM frame as the very first frame of a stream
left that stream half-closed and accounted forever: stream instances
accumulated in memory for the whole lifetime of the connection, and the
connection itself could refuse to close even after the idle timeout had
expired. Such streams are now closed and purged immediately.
Second, the connection timeout was only armed once the first bytes were
received, so a connection on which nothing was ever received had no
timeout at all and could linger indefinitely. "timeout client" is now
armed from connection initialization, which guarantees that idle
connections are always closed as expected, and further reduces the risk of
leaked connections, including the case above.
* h3/qpack: Several stalled transfers and compliance issues were fixed.
A transfer ending with an empty DATA frame, empty trailers, or an unknown
extension frame could wait until timeout instead of completing. These
cases now correctly signal the end of the message. Invalid empty headers
and certain control frames missing their mandatory payload are also
handled according to the protocol.
HTTP/3 header-compression control streams now release the space occupied
by data already processed, rather than becoming permanently stalled.
Invalid references to an unsupported dynamic compression table are also
rejected more strictly. Finally, HTTP/3 frame counters no longer count a
large frame several times just because it arrived in multiple pieces,
making the reported statistics more reliable.
* quic: More robust key updates and error recovery.
A failed memory allocation during a key update could cause a worker crash
on a subsequent key-phase change. HAProxy now retries key preparation and,
if necessary, drops the packet so that the peer can retransmit it, rather
than using missing keys. Delayed packets from the initial key phase are
also accepted correctly after the first key update, avoiding unnecessary
packet loss and retransmissions on connections with reordered traffic.
Error handling after the handshake now cleans up the correct connection
identifiers and preserves their numbering for a retry. A small memory leak
on token-generation failure has also been fixed. Additional defensive
handling for ChaCha20 with AWS-LC is included, although its problematic
cleanup path is not currently reachable.
* htx: Header length limits are now enforced on updates.
A header name is limited to 255 bytes and a header value to 1 MB. These
limits were checked when a header was added, but not when an existing one
was rewritten with "replace-header" or "replace-value" rules. When
exceeded, the length silently overflowed into the block type, corrupting
the message. Since the header must first fit in a buffer, only setups with
"tune.bufsize" above 1 MB are affected; a client sending a large enough
header is then enough to trigger the issue. The same checks are now
applied on updates. The issue was reported by Adam Crawford.
A 3.4 regression in HTX transfers was also fixed: with L7 retries and
small buffers, the end-of-message flag could be lost on the request
forwarded to the server, which then waited for more data. Two more HTX
fixes were backported as a precaution (zero-sized blocks reservation and a
wrong position returned after defragmentation); their only known trigger
is the decompression filter which only exists in 3.5-dev, so no released
version was affected. The "tune.bufsize" documentation was clarified
regarding the 256 MB hard limit and the fact that it applies to request
and response headers.
* cli: A crash and a buffer overflow on the command line parser were fixed.
When a command line exactly filled the input buffer and ended with the
payload pattern ("<<"), the payload buffer was allocated with a size of 0,
leading to a crash. If payload data then arrived in a subsequent read, a
length computation wrapped around and the line was copied past the end of
the buffer. Both issues are fixed by never reading into a full buffer.
Only users with access to the stats socket could trigger them. Setting
"tune.cli.max-payload-size" to 0 was a valid mitigation. In addition, a
payload pattern without any command is now rejected with an error instead
of being silently ignored.
* hlua: A use-after-free in Lua cosockets was fixed.
Garbage collection of Lua sockets could release resources from the wrong
thread or access resources already freed, potentially crashing a worker.
This notably affected scripts loaded with "lua-load", whose state is
shared between threads. Socket cleanup is now handed back to the owning
thread. Closing a Lua socket that was never connected is also safe.
Loading "mailers.lua" with "lua-load-per-thread" no longer fails at
startup with "use_native_mailers_config: not available outside of body
context". HTTP message duplication consistently returns nil when there is
no data to copy. Finally, memory associated with Lua rules is released
during configuration teardown; this was not a leak accumulating on each
request during normal operation.
* http-client/ocsp/acme: Stuck OCSP updates and ACME transactions were fixed.
A regression in response reception could block automated OCSP updates and
ACME operations whenever a response arrived in more than one part.
Instead of receiving the rest, HAProxy waited until the server timeout and
the operation failed. Responses are now collected correctly, restoring
these certificate-maintenance operations for affected deployments.
The same regression could leave the Runtime API's "httpclient --htx"
command stuck just after the response headers. It now continues to read
and display the response. A possible worker crash during internal HTTP
client shutdown has also been fixed.
* ssl: TLS session resumption failures and several smaller issues were fixed.
With OpenSSL 3.1 or older, resuming a TLS session at the end of its
configured lifetime could fail with an internal-error alert instead of
falling back to a full handshake. Expired sessions are now discarded
correctly, allowing the connection to proceed with a fresh handshake while
still respecting "tune.ssl.lifetime". Newer OpenSSL versions were not
affected.
On 64-bit big-endian systems such as s390x and ppc64, the AES decryption
converters and JWE decryption could corrupt memory and crash a worker when
processing input. The output-length handling has been corrected. This
particular issue does not affect the usual little-endian systems.
A small memory leak when deleting CA-file entries has also been fixed.
Finally, receiving TLS data when the SSL context is no longer available no
longer risks a crash from accessing that missing context.
* sample: Strings carrying an embedded NULL byte could bypass ACLs.
The "url_dec", "json_query", "jwt_header_query" and "jwt_payload_query"
converters could produce a string containing a NUL byte (from "%00",
"\u0000" or a base64-decoded JSON). Such a string was then compared by "-m
str" on its prefix only, so "/public%00/admin" matched "/public" and an
allow-list could be bypassed. These converters now fail on such input,
like they already do on invalid sequences. Legitimate URLs and tokens are
not affected. Along with this, the configuration manual now describes the
exact contents allowed for each sample type.
* map: A crash with map_regm inside an ACL was fixed.
Using the "map_regm" converter in an ACL applied on a fetch that iterates
over several values, such as "req.hdr(name)" without an occurrence number,
crashed the process as soon as the first value did not match. The
converter was overwriting the internal iteration cursor of the fetch. A
single request was enough to trigger it, and the bug has existed since
1.7. The issue was reported by @Michael-JRead.
* http rules: capture rules evaluated in the backend context could crash.
Capture rules cannot be used on the backend side. However it is possible
to have such rules on a listener. In that case, when the listener was used
as a backend only, a crash could be experienced or the memory pools could
be corrupted because the capture slots were used in the context of a
frontend that never referenced them. Since such backend relationship can
only be resolved at run time, this could not be detected at startup. The
rules are now ignored for requests coming from another frontend, and the
documentation was updated to state that these capture slots only exist in
the section declaring them.
* http-htx: Rewriting the "Host" header of large requests could corrupt them.
When a "replace-header" rule rewrites the "Host" header of a request whose
URI contains an authority (all HTTP/2 and HTTP/3 requests, and HTTP/1
requests in absolute form), the URI's authority is updated to stay in sync
with the new value. This update was performed using on of rotating trash
chunk and could overwrite the new host value iteself, corrupting this way
the request. The authority update is now performed in a dedicated buffer,
so the forwarded request always remains consistent.
* http rules: Conditional "normalize-uri" rules could apply the wrong
normalizer.
When "percent-to-uppercase", "percent-decode-unreserved" or
"path-strip-dotdot" was used without its optional argument and directly
followed by an "if"/"unless" condition, the rule silently ended up as
"path-merge-slashes". Such rules now perform the configured
normalization. If you use them, be aware that URIs will now be rewritten
as intended, while they were previously left mostly untouched. The issue
was fixed by Youngkwang Lee.
* tcp rules: "tcp-request content" rules restarted from scratch on yield.
A regression introduced in 3.2 made the evaluation of "tcp-request
content" rules restart from the first rule each time a rule yielded more
than once (for instance while waiting for more data), instead of resuming
at the current one. Previous actions could then be re-executed. Evaluation
now resumes at the waiting rule, avoiding these repeated actions.
* filters: The "filter-sequence" directive was removed.
This directive was added in 3.4 to define a different filter order for
requests and responses. Several bugs were found in it, and fixing them
properly requires a change of syntax and a deeper redesign, which will be
done in 3.5. Given its state, it was very unlikely to be used in
production, so it was reverted from 3.4. A configuration still containing
it will now be rejected at startup; the line must simply be removed. The
earlier fix for a startup crash with this directive is superseded by the
revert.
* mux-h2/mux-spop: Two protocol handling issues were fixed.
An H2 stream that only subscribed for sending without actually trying to
send data was never woken up again. This could happen with health checks
using the PROXY protocol on H2 connections when the handshake was not yet
complete, leaving the check stuck until its timeout. These checks now
resume correctly once the connection is ready.
SPOE connections to agents were rejected when the HELLO frame arrived
split across several TCP segments, because the frame header was parsed
twice. HELLO and DISCONNECT frames are now correctly reassembled.
* task: A race in the destruction of shared tasks was fixed.
Destroying a task that could still be scheduled by another thread could
lead to the task being run after being freed, with a risk of crash. Such
tasks are now killed asynchronously instead, avoiding this race on
multithreaded deployments.
Other few minor issues were also addressed:
* mux-h2: trailers received on a tunneled stream are now rejected.
* TLS inspection: Bounds checks in the TLS ClientHello parser were fixed.
* cache: An issue about possible hash collisions on the primary key was
fixed.
* http fetch: "http_auth_bearer" used with a custom header was fixes to
work as expected in all cases
* http analysis: The tunnel timeout was not applied on streasms with
data filters, this was fixed. In addition, two issues with 1xx interim
responses were fixed. The transaction status code after early hints is
now properly restored to properly deal with internal responses, such
as deny rules. And processing of 1xx interim responses in H2 was fixed
to not truncate the final responses with no known length. Finally, the
"http_fail_cnt" counter was wrongly incremented on error during
response forwarding. Only actual server failures are now counted, once
each.
* log: CBOR-encoded integers and booleans could overflow the log buffer
by one byte. This was fixed by reserving the trailing byte for the
terminating-null byte. In addition, the "+json" log encoding now
always produces valid JSON by escaping all bytes outside the printable
range (for instance control characters or non-UTF-8 bytes).
* debug: Crashes while dumping streams were fixed.
* proxy: Backends inheriting a mode through chained defaults were
wrongly rejected. The inherited mode is now recognized, so there is no
need to repeat it in each intermediate defaults section.
* resolvers: Truncated DNS responses were misclassified. These responses
are now classified correctly, making DNS failures easier to diagnose.
* checks: HTTP health checks with several "host" headers could get
corrupted because an outdated request-line could be used. It is now
refreshed after each update.
* ring: Not really a bugfix but an improvement. "show events" gets a
"-s" flag to sanitize its output. It replaces control and non-ASCII
characters with '?' and tabs with spaces. The default output is
unchanged so that scripts and the Data Plane API keep receiving the
original bytes
Finally, two optimizations were backported:
* Adding many servers with "add server" without an explicit ID was
quadratic because the lowest unused ID was searched from scratch each
time. It is now remembered: adding 20,000 servers went from 14.4
seconds to 66 milliseconds in tests. Reported by Tiburce Giroux.
* Logging scales better on many-core machines: In glibc, localtime() and
gmtime() take a global lock, which severely limited performance on
many-core machines as soon as a date was logged ("option httplog"
alone lost half of its throughput on 64 cores, and configs using the
"ltime"/"utime" converters even more). A small per-thread cache of
recent conversions removes this contention. This issue was reported
and co-authored by Jinho Kong.
Thanks to everyone for your help on this release!
Please find the usual URLs below :
Site index : https://www.haproxy.org/
Documentation : https://docs.haproxy.org/
Wiki : https://github.com/haproxy/wiki/wiki
Discourse : https://discourse.haproxy.org/
Slack channel : https://slack.haproxy.org/
Issue tracker : https://github.com/haproxy/haproxy/issues
Q&A from devs : https://github.com/orgs/haproxy/discussions
Sources : https://www.haproxy.org/download/3.4/src/
Git repository : https://git.haproxy.org/git/haproxy-3.4.git/
Git Web browsing : https://git.haproxy.org/?p=haproxy-3.4.git
Changelog : https://www.haproxy.org/download/3.4/src/CHANGELOG
Dataplane API :
https://github.com/haproxytech/dataplaneapi/releases/latest
OpenTelemetry : https://github.com/haproxytech/haproxy-opentelemetry
Pending bugs : https://www.haproxy.org/l/pending-bugs
Reviewed bugs : https://www.haproxy.org/l/reviewed-bugs
Code reports : https://www.haproxy.org/l/code-reports
Latest builds : https://www.haproxy.org/l/dev-packages
---
Complete changelog :
Amaury Denoyelle (8):
BUG/MAJOR: h3: reject H3 truncated frames
BUG/MINOR: h3: reject truncated frames with mandatory payload
BUG/MEDIUM: h3: do not block FIN on empty DATA frame
BUG/MINOR: h3: handle empty HEADERS frame as specified
BUG/MINOR: h3: handle unknown frame type on request stream as specified
BUG/MINOR: h3: only increment frame type counter on new header
BUG/MAJOR: mux_quic: fix leak on RESET_STREAM reception
BUG/MEDIUM: mux_quic: activate timeout on FE init
Christopher Faulet (37):
BUG/MEDIUM: mux-spop: Properly handle parsing of split HELLO/DISCONNECT
frames
BUN/MINOR: hlua: reset hlua_body variable after per-thread files loading
BUG/MEDIUM: hlua: Never release a cosocket applet from the GC
BUG/MINOR: mux-h1: Return an error on trailers if last chunk is unfinished
BUG/MINOR: hlua: Always return nil if there is no data to dup for HTTP
messages
BUG/MEDIUM: htx: Don't reset flags when source must be preserved during
transfer
BUG/MEDIUM: cli: Don't parse next command if input buffer is empty
BUG/MEDIUM: cli: Don't read a command or payload line into a full buffer
BUG/MINOR: cli: Reject payload with no command
BUG/MEDIUM: htx: Reserve a block descriptor for zero-sized blocks
CLEANUP: stream: Remove an excess newline in the stream dump
BUG/MEDIUM: tcp-rules: Don't restart tcp-request content evaluation on
yield
MAJOR: filters: Revert the filter-sequence directive
BUG/MEDIUM: http-client: Don't use the httpclient context if it was
released
MINOR: http-client: Add an option to not stop the reception of the
response
BUG/MEDIUM: ssl/ocsp: Set HTTPCLIENT_O_RES_ACCUM option on the http-client
BUG/MEDIUM: acme: Set HTTPCLIENT_O_RES_ACCUM option on the http-client
MINOR: http-client: Add a function to notify some data were consumed
BUG/MEDIUM: http-client/cli: Notify the http-client when the response is
consumed
BUG/MEDIUM: htx: Fix the position returned by htx_defrag()
BUG/MINOR: tools: Don't try to anonymize a NULL string
BUG/MAJOR: htx: Check the header/trailer length limits when one is updated
BUG/MINOR: ssl: Fix possible null deref on the SSL context in
ssl_sock_to_buf()
BUG/MINOR: http-client: Convert server timeout ticks when setting it
BUG/MINOR: mux-h2: Don't expect more HTX data on 1xx interim responses
REG-TESTS: http-messaging: Add a script to test interim responses for
H1/H2
BUG/MEDIUM: log: reserve the trailing 0 in CBOR int and bool encoders
BUG/MEDIUM: http-htx: don't build the new authority in a rotating trash
chunk
BUG/MEDIUM: http-act: ignore "capture len" rules evaluated from a backend
BUG/MEDIUM: tcp-rules: ignore "capture len" rules evaluated from a backend
BUG/MINOR: http-ana: count response body failures only once in
http_fail_cnt
BUG/MINOR: http-fetch: http_auth_bearer() must not match a missing header
BUG/MINOR: http-fetch: fix the space check of http_auth_bearer(<hdr>)
BUG/MINOR: mux-h1: only mark C-L and T-E as sent once the header is
emitted
BUG/MINOR: tcpcheck: refresh the start-line after updating the authority
BUG/MINOR: http-ana: restore the transaction status after early hints
BUG/MINOR: mux-h2: Reject trailers received on a tunneled stream
Dragan Dosen (1):
BUG/MEDIUM: filters: check the filter name before comparing it
Frederic Lecaille (11):
BUG/MEDIUM: quic: crash on key update phase change after a failed one
BUG/MINOR: quic: late packets of the first key phase are dropped
MINOR: quic: protect the ChaCha20 header protection context from being
freed
BUG/MINOR: qpack: encoder and decoder stream data is never consumed
BUG/MINOR: qpack: accept a Required Insert Count which cannot be reached
BUG/MINOR: quic: delete the wrong CIDs when post-handshake frames fail
BUG/MINOR: quic: leak of the NEW_TOKEN frame on token generation failure
BUG/MEDIUM: map: do not overwrite the fetch context in pat_match_regm()
REGTESTS: map: check map_regm inside an ACL over an iterating fetch
BUG/MINOR: sample: reject a \0 byte in url_dec, json_query and jwt_*_query
REGTESTS: http-rules: check "-m str" on a sample with an embedded \0
M9nx (1):
BUG/MINOR: hlua: release private Lua rule data on teardown
Manu Nicolas (3):
BUG/MINOR: resolvers: classify empty truncated responses
CLEANUP: resolvers: stop clearing the DNS response buffer
BUG/MEDIUM: http-ana: apply tunnel timeout with data filters
Olivier Houchard (3):
MEDIUM: mux-h1: Harden test for short data at end of stream
BUG/MEDIUM: mux-h2: Make sure we remove H2_SF_NOTIFIED on subscribe
BUG/MEDIUM: task: Do not destroy a task that is not ours in task_destroy
Remi Tricot-Le Breton (4):
BUG/MINOR: cache: Manage collisions on primary key
BUG/MINOR: cache: Seed cache primary hash
BUG/MINOR: ssl: don't pass the address of a size_t as an int* to OpenSSL
BUG/MINOR: ssl: Fix leak of ca_list in cafile_entry
William Lallemand (3):
BUG/MINOR: payload: fix the cipher_len bound check in
smp_client_hello_parse()
BUG/MINOR: payload: bound ClientHello extension lists by the extension
length
BUG/MEDIUM: ssl: don't set a 0 timeout on an expired TLS session
Willy Tarreau (13):
OPTIM: server: remember the lowest known unused server ID
MINOR: ring: add "-s" flag to "show events" to sanitize the output
REGTESTS: log: add a test for "show events -s"
REGTESTS: log: check which log-format tags escape control characters
DOC: config: mention that section 8.6 does not apply to the cbor encoding
REGTESTS: log: check that the +cbor output passes the logged bytes through
DOC: config: clarify tune tune.bufsize doc regarding various limits
BUG/MINOR: stream/debug: harden the stream dump function regarding signals
DOC: configuration: clarify the validity ranges of sample types
BUG/MINOR: proxy: pass PR_FL_DEF_EXPLICIT_MODE through cascaded defaults
OPTIM: tools: keep a cache of recent localtime() and gmtime()
BUG/MEDIUM: log: encode the whole non-printable range in the +json output
REGTESTS: log: check the escaping performed by the +json option
Youngkwang Lee (1):
BUG/MEDIUM: http_act: fix normalize-uri normalizer selection with a
condition
--
Christopher Faulet