1.41.0 (Pending)
Incompatible behavior changes
Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required
dynamic_forward_proxy: The dynamic forward proxy cluster now replaces a host with a new host instance when DNS re-resolution changes its address, publishing the change as a regular cluster membership update (old host removed, new host added). Connection pools bound to the old address are therefore drained, instead of existing connections continuing to use the stale address indefinitely. This change can be reverted by setting the runtime guard
envoy.reloadable_features.dfp_cluster_replace_host_on_address_changetofalse, which restores the previous behavior of mutating the existing host’s address in place.geoip: The HTTP geoip filter now removes every configured geolocation header from the incoming request before the lookup, so a header a client sent is never forwarded upstream as if the lookup had set it. Previously a client-supplied header was kept whenever the lookup had no value for it: an address absent from the database, a database error, a field the database lacks for that address, or a request whose lookup was skipped because the downstream address was not an IP address. Geolocation providers report the headers they are configured to write through the new
Driver::geoHeaderNames()method, which defaults to empty for providers that do not implement it. This behavior can be reverted by setting the runtime guardenvoy.reloadable_features.geoip_sanitize_geo_headerstofalse.router: A TCP upstream that half closes its connection, such as the origin of a terminated CONNECT tunnel, no longer causes an HTTP/2 or HTTP/3 downstream stream to be reset. The half close is propagated downstream as an end of stream, and the stream is closed once the downstream client half closes its own side. Previously the stream was reset, which discarded any response data that had not yet been written to the downstream connection. This change can be reverted by setting the runtime guard
envoy.reloadable_features.tcp_tunnel_allow_upstream_half_closetofalse.stats: The tag-extracted names of per-worker stats now keep
workeras a separate name segment when theworker_idtag is extracted:server.worker_1.watchdog_missis now extracted toserver.worker.watchdog_missinstead ofserver.worker_watchdog_miss, and the same applies to the per-worker stats underlistener.<address>.,listener_manager.andthread_local_cluster_manager.. Prometheus names are unchanged, because.and_are both sanitized to_there. Metric names change only for sinks that emit the tag-extracted name verbatim, such asdog_statsd,metrics_serviceand the OpenTelemetry sink.tls: Upstream TLS connections now default to a maximum TLS version of TLS 1.3 instead of TLS 1.2 when tls_maximum_protocol_version is not set, which also lets them use the default post-quantum key exchange. When the upstream server supports TLS 1.3, an mTLS client certificate rejected by the upstream is reported as a reset after the connection is established rather than as a connection failure. In that case,
connect-failureHTTP retries and TCP proxy max_connect_attempts don’t retry it on another endpoint, connect-only TCP health checks pass, and the rejection isn’t counted in theupstream_cx_connect_failcluster statistic. Outlier detection also records the connection as successful, so the host isn’t ejected for consecutive local origin failures caused by TCP proxy connections, or by HTTP requests when split_external_local_origin_errors is enabled. This behavior can be reverted by setting the runtime guardenvoy.reloadable_features.default_client_tls_max_version_is_1_3tofalse, or per cluster by settingtls_maximum_protocol_versiontoTLSv1_2.upstream: A host health change now re-partitions only the priority that holds the host instead of every priority. EDS clusters are unchanged. This behavior can be reverted by setting the runtime guard
envoy.reloadable_features.incremental_health_transitiontofalse.
Minor behavior changes
Changes that may cause incompatibilities for some users, but should not for most
build: GCC builds (
--config=gcc) now use toolshed’s hermetic GCC 13.4.0 toolchain instead of a GCC installed in the build image. GCC uses Envoy’s hermetic sysroot (the same as Clang), static libstdc++, and links with hermetic LLD instead of gold. It supports Linux x86_64 only, and--config=gccnow fails on other platforms instead of building with Clang. The GCC build image,--config=libstdc++andBAZEL_USE_LIBSTDCPPare removed. To build with a system GCC, setgcc_extension.host(path = "...", toolchains = ["gcc_linux_x86_64"])from@envoy_toolshed//toolchains/gcc:extensions.bzlin the root module.cares: Changes the default value of
envoy.restart_features.shared_cares_dns_resolvertofalse. Turn this on ifqcache_max_ttlis set in c-ares config and sharing the query cache across DNS clusters is desired.config: xDS resource types that may be subscribed to while a config update is being applied are now paused centrally for every subscription, regardless of its transport (gRPC, REST or filesystem), instead of by each xDS API. As part of this change, Virtual Host Discovery Service (VHDS) requests are now also paused while a Route Discovery Service (RDS) update is being applied, so that the VHDS subscriptions created by the update are batched into a single request.
contrib: The Istio peer metadata conversion now takes a peer’s app name and version from the
appandversionlabels again, and usesapp.kubernetes.io/nameandapp.kubernetes.io/versiononly when those labels are absent or empty. A workload’s own stats always useappandversion, so giving theapp.kubernetes.io/*labels precedence madesource_app,destination_app,source_versionanddestination_versionreported by a peer differ from the values the workload reports for itself, and the same workload showed up under two names. This restores the label precedence Istio proxies used beforeapp.kubernetes.io/*labels were preferred.dns_resolver: Updated the
webpki-rootsandrustls-webpkicrates used by the built-in Hickory DNS resolver dynamic module, so that DNS-over-TLS and DNS-over-HTTPS upstreams no longer trust the compromised eMudhra intermediate CA certificates (see https://emudhra.we-have-a-problem.com/).dynamic_modules: The dynamic modules route specifier no longer records
specifier_durationfor a request outside runtime_fraction.filter_chain: The filters of a filter chain that is configured per route in the filter chain filter now get the stats prefix of the enclosing context instead of a hard-coded
filter_chain.prefix. As route specific configurations have no stats prefix today, their stats move fromfilter_chain.<name>.*to<name>.*.http: The gRPC statistics filter now applies replace_dots_in_grpc_service_name to allowlisted methods. Previously, this option only affected statistics emitted with
stats_for_all_methodsset totrue. When enabled withindividual_method_stats_allowlist, dots in service names are now replaced with underscores in emitted metric names. Allowlist entries continue to use the original service names, including dots. Dashboards and alerts that refer to the previous metric names need to be updated.jwt_authn: jwt_authn: when a JWT has no
kid, keys whosex5t#S256(or, failing that,x5t) differs from the JWT header’s are skipped. Keys without a thumbprint are still tried. This behavior can be reverted by setting runtime guardenvoy.reloadable_features.jwt_authn_select_key_by_x5ttofalse.listener: A TCP listener that has filter chains from the Filter Chain Discovery Service (FCDS) and no filter_chains is now updated in place in the same cases as the other listeners, instead of always being drained and replaced. The connections on the FCDS filter chains that the update keeps remain open. This behavior can be temporarily reverted by setting runtime guard
envoy.reloadable_features.listener_in_place_address_updatetofalse. An in place update of any listener also drains its connections on the FCDS filter chains that the update drops. When theenvoy.reloadable_features.use_connection_event_drainruntime guard is disabled, these connections are only closed once the drain completes.listener: A TCP listener update that adds or removes addresses, and otherwise changes at most the filter chains, no longer drains the entire listener. The listener keeps accepting connections on the remaining addresses, and the connections accepted on the removed addresses are drained along with those of the removed filter chains. The new total_listener_addresses_draining listener manager gauge counts the removed addresses until their drain completes. Such an update of a listener with enable_reuse_port disabled is also no longer rejected with a duplicate address error. If the listener fails to listen on an added address, it drops the update and keeps serving its addresses instead of removing the listener. See filter chain only update for the requirements. This behavior can be temporarily reverted by setting runtime guard
envoy.reloadable_features.listener_in_place_address_updatetofalse.mcp: Removed the 10MB (10485760 bytes) upper bound on the MCP filter’s max_request_body_size and the per-route max_request_body_size. Any
uint32value is now accepted; the 8KB default is unchanged.on_demand: On-demand cluster discovery (ODCDS) can now answer repeated requests for a cluster the server has already reported as missing immediately from that remembered answer, instead of starting another discovery and waiting for the response or the discovery timeout. The answer is remembered per config source, only for explicitly requested clusters, and is withdrawn as soon as an update delivers the cluster. Requests answered this way are counted in the new cluster_manager.odcds.known_missing_answers counter. This behavior is off by default and can be enabled by setting the runtime guard
envoy.reloadable_features.odcds_missing_cluster_cachetotrue.quic: QUIC listeners now create the connection ID generator factory and apply its worker-routing socket option when the socket factory config is loaded, instead of when the listener config is loaded. This behavior can be reverted by setting runtime guard
envoy.restart_features.defer_worker_routing_inittofalse.rbac: The default stats tag-extraction rules now extract tags from the RBAC network filter’s stats (
<stat_prefix>.rbac.*): the rules/shadow rules stat prefix is extracted as the newenvoy.rbac_rules_prefixtag, and the policy name of per-policy stats is extracted asenvoy.rbac_policy_name. The policy name is now also extracted for the upstream HTTP RBAC filter’s per-policy stats (cluster.<name>.rbac.*). Previously these tags were only extracted for the downstream HTTP filter’s stats. Flat stat names are unchanged; only the tag-extracted names and tags visible to tagged stats sinks (e.g. Prometheus) change.spiffe_validator: The SPIFFE certificate validator now rejects a
trust_domainsentry whosetrust_bundleparses but contains no certificate, for example a bundle holding only a CRL. Such a trust domain previously loaded successfully but had no trust anchor, so every handshake for it failed at verification time instead. This matches the behavior of thetrust_bundles(SPIFFE bundle map) configuration, which already rejects a trust domain with no certificate. This change can be reverted by setting the runtime guardenvoy.reloadable_features.spiffe_validator_reject_empty_trust_bundletofalse.stats: Added the new
envoy.upstream_from_zoneandenvoy.upstream_to_zonetags, extracted by the default stats tag-extraction rules from a cluster’s upstream per-zone stats (cluster.<name>.zone.<from_zone>.<to_zone>.*). Flat stat names are unchanged.tls: Removed the
AAA Certificate ServicesandemSign ECC Root CA - G3roots from the root certificate bundle built into Envoy Mobile. Both roots issued or cross-signed eMudhra intermediate CA certificates whose private keys were compromised (see https://emudhra.we-have-a-problem.com/). Servers whose certificate chains only lead to these roots are no longer trusted by Envoy Mobile when it uses its built-in bundle.udp: Updated the GSO UDP packet writer, used by QUIC and UDP listeners, to send datagrams larger than 1452 bytes through ordinary UDP sends instead of failing with
EMSGSIZE. Multi-slice datagrams also use ordinary sends, after flushing any older batch. Blocked writers now returnEAGAINwithout accepting new datagrams. Theinternal_buffer_sizegauge now sums buffered bytes across writers sharing a statistics scope instead of reporting the last writer’s value.vhds: When a virtual host discovered through VHDS has the same name as a virtual host defined in the RDS virtual_hosts, the VHDS virtual host now replaces the RDS one in the merged route configuration. Previously both virtual hosts were kept. Removing the VHDS virtual host restores the RDS one. This behavior change can be reverted by setting the runtime guard
envoy.reloadable_features.vhds_overrides_rds_virtual_hosts_with_same_nametofalse.
Bug fixes
Changes expected to improve the state of the world and are unlikely to have negative effects
alts: Fixed ALTS connections holding their handshaker service stream until the connection closed. With a gRPC client that limits concurrent calls per connection (gRFC A105), each open ALTS connection used up one slot on the handshaker channels, so once enough connections were open, new handshakes waited until their deadline, blocking the worker thread, and then failed. The handshaker is now released as soon as the handshake completes.
compressor: Fixed compression of empty request and response bodies when no
content-lengthheader is present. The compressor now waits for nonempty body data before enabling compression for these streams. This change can be temporarily reverted by setting the runtime guardenvoy.reloadable_features.compressor_skip_empty_bodytofalse.contrib: Fixed a crash in the
rocketmq_proxynetwork filter (contrib) where a request whose custom header failed to decode (for example a request frame with the RPC flag set, which routes decoding to the response-header path) left the typed custom header null. The request handlers then dereferenced the null header. The handlers now reject such requests with a system error response instead of crashing.contrib: Fixed a use-after-free in the
kafka_meshnetwork filter (contrib). A Fetch request that finished while its downstream connection was closing delivered its answer through a dispatcher callback that held a raw pointer to the request and called the filter directly, so the callback ran on the freed request and filter. The callback now only runs if the request is still alive, and reaches the filter only while the filter has not abandoned the request.contrib: Fixed a use-after-free in the
sip_proxynetwork filter (contrib). The per-worker transaction table only marked an ended transaction as deleted and removed it later, so an upstream response with that transaction’s branch, arriving on an upstream connection shared with another downstream connection, was delivered to the freed transaction. Ended transactions are now removed right away, an upstream request no longer refers to a transaction that has moved away from it or ended, and a late response after all transactions on an upstream connection have ended is dropped instead of dereferencing a null pointer.dns_resolver: Fixed a data race and cross-thread resolver sharing introduced with the
envoy.restart_features.shared_cares_dns_resolverruntime guard. The shared resolver cache lived on the process wide c-ares DNS resolver factory and was consulted by every caller, without synchronization. Callers that create resolvers on worker thread could therefore race on the cache, and could be handed a resolver bound to another thread’s dispatcher. Moving the shared logic into the upstream cluster similar to the default shared resolver eliminates any future issues and avoid locking in the worker thread. Also switch default forenvoy.restart_features.shared_cares_dns_resolverto false.dynamic_forward_proxy: Fixed a race in the dynamic forward proxy cluster where a request arriving while DNS resolution for a cold host was completing could receive a spurious 503 (
UH) with the successful resolver detail string even though resolution succeeded. The cluster now re-selects the host from its host map after observing a completed cache entry instead of treating a cache hit following a host-map miss as a failure. As part of this change, host selection that completes with no DNS host info now reportsno_hostas the response code details instead of an empty string.dynamic_modules: Fixed dynamic module metric IDs. The first metric defined with labels got the same ID as the first one defined without labels, and metrics of different types also shared IDs. A module that recorded a metric with the wrong number of label values, or through the call of another metric type, then changed a different metric instead of getting an error. Every metric now has its own ID.
dynamic_modules: Fixed the gRPC status of local replies sent by HTTP dynamic modules.
send_responsepassedgrpc-status: 0(OK) for every status code, so a gRPC client denied with, for example, a 403 was told the call succeeded. A 200 reply still maps to OK; any other code now maps to the matching gRPC status, as for other local replies (403 toPERMISSION_DENIED, 429 toUNAVAILABLE).formatter: Fixed a performance issue where metadata command operators such as
%DYNAMIC_METADATA(...)%copied the whole metadata each time they produced a typed value, for example for the fields of a json_format access log.golang: Fixed the Go HTTP filter dropping buffered body data when an asynchronous
DecodeDataorEncodeDatareturnedStopAndBufferafter the trailers had arrived and the trailers were then handled synchronously. The trailers were forwarded without the buffered body.golang: Fixed the Go HTTP filter forwarding buffered body data with
end_streamset when the filter resumed processing the trailers, for example after an asynchronousStopAndBufferreturned once the trailers had already arrived. The trailers were then sent after the stream had ended, which asserted in debug builds.golang: Fixed the Go HTTP filter’s access log callbacks not seeing the request headers when an earlier filter sent a local reply before the Go filter decoded them. Reading the request headers in
OnLogpanicked with an invalid phase error, and request properties such asrequest.refererwere not found.grpc_field_extraction: Fixed the
grpc_field_extractionfilter corrupting streaming gRPC requests when the data chunk completing the first message also carried the head of the next pipelined message. The filter buffers request data internally to extract fields from the first message but never forwarded the buffered leftover bytes once extraction completed, so the remainder of the next message was delivered without its gRPC frame header and the stream was corrupted. The leftover bytes are now forwarded after extraction completes.http: Fixed downstream span finalization to run after
onStreamCompleteso filter-set span tags are exported instead of silently dropped.http: grpc: fixed core retry policy conversion so
retry_back_off.max_intervalis no longer used as the per-try timeout. A dedicated per_try_timeout field can now be configured independently.http2: Fixed oghttp2 (
envoy.reloadable_features.http2_use_oghttp2) dropping trailers when the body exactly exhausted the peer’s flow-control window before the trailers were encoded, for example a 65535-byte gRPC response to a client using the default initial window. The stream was ended by an empty DATA frame and the client never received the trailers. This behavior can be reverted by setting the runtime guardenvoy.reloadable_features.http2_trailers_after_exhausted_windowto false.http2: Fixed oghttp2 (
envoy.reloadable_features.http2_use_oghttp2) sending outbound DATA frames as large as the peer’sSETTINGS_MAX_FRAME_SIZE, which caused legitimate flow-controlWINDOW_UPDATEtraffic from clients that acknowledge data in small increments to be misclassified as a flood (http2.inbound_window_update_frames_flood) and the connection to be closed. Outbound DATA frame payloads are now capped at 16 KiB, matching nghttp2. This behavior can be reverted by setting the runtime guardenvoy.reloadable_features.http2_oghttp2_cap_data_frame_payloadto false.listener: Fixed a use-after-free during worker shutdown. When a connection handler was destroyed with connections still open, closing those connections could re-enter the handler, e.g. an upstream connection pool reconnecting to an internal listener, and look up the already destroyed internal listener map. This could crash Envoy or make the worker thread spin forever and hang shutdown.
logging: Fixed fatal signal backtraces re-entering the logger mutex, which could abort or deadlock when the mutex was already held on the crashing thread or inherited locked across a fork. Native and coroutine backtraces now write directly to stderr in fatal signal handlers, preserving fault descriptions and backtrace headers and stack decoder compatibility without using the logging infrastructure. The alternate signal stack is sized using the runtime
_SC_SIGSTKSZminimum with headroom for nested fatal signals.mcp: Fixed handling of MCP
2026-07-28requests whoseparams._meta["io.modelcontextprotocol/protocolVersion"]is missing or is not a string. The MCP filter inREJECT_NO_MCPmode and themcp_json_rest_bridgeHTTP filter now reject them with HTTP400and JSON-RPC error-32602. Previously, the MCP filter accepted missing metadata and rejected non-string metadata as a-32020header mismatch counted inheader_mismatch, and the bridge handled requests with anMCP-Protocol-Version: 2026-07-28header but without valid metadata as legacy requests.mcp_json_rest_bridge: Fixed MCP header Base64 decoding so values with an incomplete Base64 wrapper are treated as literals, and an empty Base64 payload correctly decodes to an empty string.
mcp_json_rest_bridge: Fixed the
mcp_json_rest_bridgeHTTP filter per-route configuration validation to reject duplicate(host, path)endpoint configurations instead of silently merging tools and ignoring subsequenttool_list_http_ruleortool_list_localsettings.mcp_json_rest_bridge: Fixed the
mcp_json_rest_bridgeHTTP filter so that aPOSTrequest that ends at the headers, such as an HTTP/1 request withContent-Length: 0or an HTTP/2 request whoseHEADERSframe carriesEND_STREAM, is rejected with HTTP400 Bad Requestand a JSON-RPC-32700parse error, as requests with an empty body already were. Previously, such requests hung until the stream idle timeout.mcp_json_rest_bridge: Fixed the
mcp_json_rest_bridgeHTTP filter to respond with HTTP404 Not Foundinstead of200 OKwhen a stateless MCP2026-07-28request uses a method that the bridge does not implement, as required by the Streamable HTTP transport. The JSON-RPC error code is unchanged (-32601). This also applies to the legacyinitializeandnotifications/initializedmethods sent as stateless requests. Requests that use MCP2025-11-25or earlier still receive200 OK.mcp_router: Added optional
session_signing_keyto themcp_routerHTTP filter. When set, composite session IDs minted by the filter carry an HMAC-SHA256 of the session blob and a presented session whose MAC does not verify is rejected with a 400 error. Previously the session blob was Base64 encoded only, so theENFORCEsession identity check compared the authenticated request identity against a subject read out of the client-supplied, editable blob, and did not bind the session to the authenticated principal. The ENFORCE integration tests were also fixed: the test helper inserted the subject unencoded, so the parsed subject was always empty and the accept path was never exercised.memory: Fixed jemalloc memory accounting so that reserved memory includes retained pages, matching tcmalloc semantics and preventing underflow in physical memory calculations.
overload_manager: Fixed a use-after-free that could crash a worker thread during Envoy shutdown when a TCP listener is configured with exact_balance and the
envoy.resource_monitors.global_downstream_max_connectionsoverload manager resource monitor is configured. A connection accepted on one worker could be handed off to a different worker via connection balancing while still holding a reference into the originating worker’s thread-local overload state; because worker threads shut down independently with no ordering guarantee relative to one another, the destination worker could destroy the connection after the originating worker had already torn down its own thread-local state, dereferencing freed memory. The connection’s overload state reference is now rebound to the receiving worker’s own thread-local state once the connection has settled on its final worker, so it is always valid for as long as the connection itself is.quic: Fixed a use-after-free in HTTP/3 upstream connections after their cluster was removed. The per-cluster QUIC state (including the clock and alarm factory the connections use) was destroyed with the cluster, while the cluster’s connection pools were only drained, so a connection with active streams read freed memory on its next write or timer and crashed the process. The state now lives as long as any connection pool using it.
ratelimit: Fixed local cluster rate limit sharing when the shared cost of a request is larger than
max_tokens, for example when the local cluster has more members thanmax_tokens. Before this fix, every such request was rejected. Now, a request that fits in the bucket without sharing passes when the bucket holdsmax_tokenstokens. The request still consumes its full shared cost, so the bucket can go below zero and the next request waits until the bucket refills. The aggregate rate across the local cluster stays at the configured rate, but the aggregate burst can be one request for each member. Requests that cost more thanmax_tokenswithout sharing remain rejected, andmax_tokens: 0continues to reject every request. This behavior can be temporarily reverted by setting runtime guardenvoy.reloadable_features.local_ratelimit_local_cluster_preserve_one_requesttofalse.router: Fixed a bug in Scoped RDS (SRDS) where a single invalid scope in an update batch aborted processing of the remaining scopes in the push and left already-processed scopes in the main-thread map without propagating them to worker threads. When enabled, valid scopes in a partially rejected push are applied to worker threads while failed scopes are skipped and NACKed. Guarded by runtime guard
envoy.reloadable_features.srds_partial_acceptance(disabled by default).router: Fixed the stats prefix that the router passes to its upstream HTTP filters to keep its trailing dot (
http.<stat_prefix>.). Upstream HTTP filters that appended their own name to it directly, such as the Lua, compressor and admission control filters, emitted stats under names likehttp.<stat_prefix>lua.*instead ofhttp.<stat_prefix>.lua.*.server: Fixed a deadlock between the two main threads of a hot restart: the parent forwarded UDP/QUIC packets to the child with a blocking send from its main thread, while the child’s main thread waited for the parent’s replies (stats merge, listen socket hand-off) with a blocking receive and read no forwarded packets meanwhile, so once the child’s forwarding socket filled up both processes stopped handling admin, xDS, timers and signals for good. The parent now forwards through a bounded non-blocking queue, the child keeps servicing forwarded packets while it waits and gives up on a parent that stops answering, and the hot restart sockets carry send/receive timeouts. New
server.hot_restart_udp_forwarding_datagrams,server.hot_restart_udp_forwarding_retriesandserver.hot_restart_udp_forwarding_droppedstatistics report that path.server: Fixed a heap buffer overflow in the hot restart IPC receive path: a datagram whose length prefix is within 8 bytes of
UINT64_MAXwrapped the receive buffer size computation to a near-zero value, causing the nextrecvmsg()to overflow the buffer. Oversized datagrams are now dropped and the stream state reset, so both blocking and non-blocking receives continue with the next datagram.server: Fixed the
server.seconds_until_first_ocsp_response_expiringstatistic, which kept reporting the expiration of the last OCSP response once no TLS context had an OCSP response, for example after a certificate with an OCSP staple was replaced by one without a staple or its listener was removed. It now drops to 0, its value before the first OCSP response.sockets: Fixed a listener with a
network_namespace_filepathleaving the main thread in that network namespace when setting up its socket failed, for example because the address was already in use. The main thread then kept creating its sockets in that namespace, such as those of other listeners and of xDS and health check connections. Worker threads started afterwards inherited it as well.sockets: Fixed internal listener connections retaining response buffers when a blocked write flush outlived its peer. Closing a user-space socket now wakes its peer’s pending write so the connection can finish closing.
sockets: sockets: fixed shutdown hangs in the opt-in experimental
io_uringsocket interface when a peer stops reading or a partial write races with cancellation, and premature socket close while cancellation completions remain outstanding. The write timeout must be greater than zero when configured.tls: Fixed TLS session resumption across server certificates or filter chains whose names differ but concatenate to the same bytes, for example the DNS SANs
a.exampleandb.exampleversusa.exampleb.example. The session context ID that scopes resumption to a TLS context hashed the common name and subject alternative names of each certificate, and each server name, without their types or lengths. It now hashes the type and length of every field, which changes every session context ID once, so a session established before the upgrade, or by an Envoy instance without this change that shares the session ticket keys, takes a full handshake. This change can be temporarily reverted by setting the runtime guardenvoy.reloadable_features.unambiguous_tls_session_context_idtofalse.tls: Fixed a bug in the TLS inspector’s
JA4fingerprint implementation where GREASE values in thesignature_algorithmsextension were included in theJA4_chash input, contrary to the JA4 specification. GREASE values insignature_algorithmsare now excluded, matching the treatment already applied to cipher suites, extension type IDs, and supported versions. This behavior can be temporarily reverted by setting the runtime guardenvoy.reloadable_features.ja4_filter_grease_signature_algorithmstofalse.tls: Fixed a crash when a custom TLS certificate selector switched a handshake from a TLS context with session ticket keys to one without them, for example a context that the selector built before the keys arrived over SDS. Such a handshake now completes without a session ticket. The selectors that shipped with earlier Envoy releases were not affected, since they only switched to separately built TLS contexts when session resumption was disabled.
tls: Fixed a crash when an upstream TLS context with a custom certificate selector had an invalid
common_tls_context, for example an invalidkey_logaddress range or an unknown static secret. Envoy created the selector from the partly created configuration, so a selector that built TLS contexts while it was created, such as theenvoy.tls.certificate_selectors.on_demand_secretselector prefetching a secret that was already available, read settings that were never set. Envoy now rejects the configuration before it creates the selector.udp: Fixed UDP listeners enabling the
UDP_GROsocket option when prefer_gro is false or unset. Coalesced datagrams could previously be forwarded as a single datagram or dropped as truncated.udp: Fixed UDP replies to IPv4 clients through IPv6 dual-stack listeners on macOS, including HTTP/3 listeners using the default UDP packet writer.
udp: Fixed a hot restart child reading packets from an inherited UDP socket while its listener was still paused. The QUIC listener injects a read event on its listener to process the handshakes it buffered from packets the parent forwarded, and the listener then read the socket the parent was still serving, so the packets of the parent’s connections were dequeued by the child and answered with stateless resets until the clients had reconnected. A paused listener now runs its read-ready callback on such an event without reading the socket.
upstream: Fixed a crash (SIGSEGV) in load stats reporting and health discovery responses when EDS removed the last host from a locality after active health checking failed. Both reporting paths now skip empty locality groups.
Removed config or runtime
Normally occurs at the end of the deprecation period
config: Removed runtime flag
envoy.reloadable_features.xds_legacy_delta_skip_subsequent_nodeand legacy code paths.decompression: Removed runtime flag
envoy.reloadable_features.enable_compression_bomb_protectionand legacy code paths.dynamic_forward_proxy: Removed runtime flag
envoy.reloadable_features.dfp_cluster_resolves_hostsand legacy code paths.dynamic_forward_proxy: Removed runtime flag
envoy.reloadable_features.skip_dns_lookup_for_proxied_requestsand legacy code paths.header_mutation: Removed runtime flag
envoy.reloadable_features.header_mutation_url_encode_query_paramsand legacy code paths.health_check: Removed runtime flag
envoy.reloadable_features.health_check_after_cluster_warmingand legacy code paths.http: Removed runtime flag
envoy.reloadable_features.codec_client_enable_idle_timer_only_when_connectedand legacy code paths.http: Removed runtime flag
envoy.reloadable_features.http1_close_connection_on_zombie_stream_completeand legacy code paths.http: Removed runtime flag
envoy.reloadable_features.allow_multiplexed_upstream_half_closeand its code paths. HTTP/2 and HTTP/3 upstreams can no longer half close the stream before the downstream: the stream is reset when the response completes before the request, as it is for HTTP/1 upstreams. Independent half close is kept for TCP upstreams, seeenvoy.reloadable_features.tcp_tunnel_allow_upstream_half_close.listener: Removed runtime flag
envoy.reloadable_features.strict_stats_matcher_unpackedand legacy code paths.mcp: Removed the runtime guard
envoy.reloadable_features.mcp_filter_use_new_metadata_namespaceand the legacy code path it guarded. The MCP filter always publishes its dynamic metadata under theenvoy.filters.http.mcpnamespace (and the MCP router reads it from there); the legacymcp_proxynamespace is no longer available.on_demand: Removed runtime flag
envoy.reloadable_features.on_demand_cluster_no_recreate_streamand legacy code paths.proxy_protocol: Removed runtime flag
envoy.reloadable_features.proxy_protocol_allow_duplicate_tlvsand legacy code paths.rbac: Removed runtime flag
envoy.reloadable_features.rbac_match_headers_individuallyand legacy code paths.
New features
access_log: Added the
%DOWNSTREAM_PEER_CERT_CHAIN%substitution formatter which returns the full downstream peer certificate chain in URL-encoded PEM format.ai_protocol_manager: Added external_buffer_config to the AI Protocol Manager filter (alpha), making the store that offloaded payloads are written to a pluggable
envoy.ai_protocol_manager.external_bufferextension. When unset, envoy.ai_protocol_manager.external_buffer.in_memory is used; it holds the payload in memory and does not bound its size.bootstrap: Added runtime_stats_eviction_interval to read the stats eviction interval from a runtime key, allowing it to be changed without restarting Envoy.
config: YAML configuration now honors the explicit
!!floattag: a scalar such as!!float 5000.0is loaded as a JSON number instead of a string, so it stays a number inside agoogle.protobuf.Struct(e.g. atyped_configpacked as aStruct). Untagged float literals are unchanged and still load as strings. Non-finite values are loaded as the stringsInfinity,-InfinityandNaN. A!!floatscalar that cannot be decoded as a double now fails configuration loading. See special YAML usage.contrib: Added experimental Istio HBONE CONNECT wait extensions that defer internal connection readiness and application I/O until HTTP/2 CONNECT acceptance. Transient establishment failures retain connection-failure retries; permanent CONNECT rejections terminate HTTP or TCP requests without retry or outlier failure reporting. CONNECT 401 is exposed as an HTTP 403 with the
UFresponse flag, retaining the original CONNECT status in failure diagnostics. Single and double HBONE origination are supported, including wrapped TLS and Istio’s baggage peer-metadata exchange.dynamic_modules: Added built_route_cache to the dynamic modules route specifier, a per worker cache of the routes a module builds from route templates. A module looks a route up by key with
lookup_built_route, also in the Rust SDK, after selecting a template, so a route that is a function of a small key is built once per worker rather than once per request. Templates whose cluster is resolved per request are not cached, and a sampled share of hits set by verify_one_in is verified against what the module records. Withconfig_has_built_route_cache,has_built_route_cachein the Rust SDK, a module whose routes rely on the cache can reject a configuration without one.dynamic_modules: Added HTTP filter dynamic module callback
envoy_dynamic_module_callback_http_drain_connection_upon_completionand corresponding C++, Go, and Rust SDK methods to initiate graceful draining of the downstream connection when the current HTTP stream completes.dynamic_modules: Added
envoy_dynamic_module_callback_cluster_config_use_persistent_host_partitionsso a cluster module can opt its cluster into use_persistent_host_partitions duringenvoy_dynamic_module_on_cluster_config_new, without the field in the cluster’s configuration.dynamic_modules: Added
set_dynamic_metadata_structandset_dynamic_typed_metadatato dynamic module network filters, listener filters and cluster load balancers. A module can merge a serializedgoogle.protobuf.Structinto a dynamic metadata namespace or set the typed metadata of a namespace from a serializedgoogle.protobuf.Any. Unlike the HTTP filter callbacks of the same names, they return whether the call succeeded, and the struct setter does not create the namespace for an empty struct. The typed setter also replaces an existing entry instead of merging into it and rejects anAnywithout a type URL. The Rust SDK exposes them onEnvoyNetworkFilter,EnvoyListenerFilterandClusterLbContext. The C++ SDK exposes them assetMetadataStructandsetTypedMetadataonNetworkFilterHandleand assetDynamicMetadataStructandsetDynamicTypedMetadataonListenerFilterHandle. The Go SDK exposes them asSetMetadataStructandSetTypedMetadataonNetworkFilterHandleand asSetDynamicMetadataStructandSetDynamicTypedMetadataonListenerFilterHandle.dynamic_modules: Added
set_prefix_rewrite_ruleto the dynamic modules route specifier and its Rust SDK. It records a prefix rewrite that Envoy applies to the path the request carries when the request headers are finalized, the way a configured route applies its prefix_rewrite, so the route it produces is not specific to the request it was built for. Requests for which neither the rule nor the regex rewrite after it yields a path at that point are counted inrewrite_not_applied.dynamic_modules: Added support for implementing a custom TLS certificate selector as a dynamic module through the dynamic module TLS certificate selector extension. For each handshake the module selects one of the configured certificates or lets Envoy select one. With allow_dynamic_certificates set, the module can also select certificates that it provides at runtime, and a handshake can wait for one that is not provided yet. Modules can schedule work on the main thread and record metrics. This works for both downstream and upstream TLS contexts. The dynamic certificates that are served are listed by the
/certsadmin endpoint and count in theserver.days_until_first_cert_expiringandserver.seconds_until_first_ocsp_response_expiringstatistics, and the selector emits statistics about them.dynamic_modules: Added the
envoy_dynamic_module_callback_health_checker_set_health_metadatacallback to the dynamic module health checker. A module can attach string key/value pairs to each health check result, and HDS forwards them to the management server in health_metadata. The pairs are replaced on every check. Thehttp_status_codekey is reserved and rejected.dynamic_modules: Added the filter config handle to
HttpFilterConfig::on_scheduledin the Rust SDK, so that a module can emit the metrics defined on its config from an event scheduled to the main thread, with no stream involved.dynamic_modules: Added the optional
on_route_specifier_user_data_discardhook to the dynamic modules route specifier,on_user_data_discardin the Rust SDK. Envoy calls it with the user data a module recorded when the decision builds no route to carry it, such as a failed decision, a decision other thanUnspecifiedor aHitfromlookup_built_route, so that a module can release what it leased for the route. Theuser_data_discardstatistic counts the values handed back.dynamic_modules: dynamic modules: added use_persistent_host_partitions to the dynamic modules cluster. When set, adding, removing, or changing the health of hosts updates only those hosts instead of rebuilding the host set, and workers adopt the result as a snapshot. It requires
lb_policy: CLUSTER_PROVIDED.dynamic_modules: dynamic modules: added
envoy_dynamic_module_callback_cluster_lb_get_member_update_health_changed_host_countandenvoy_dynamic_module_callback_cluster_lb_get_member_update_health_changed_host, which report the hosts whose health changed since the previous update a worker applied, so a cluster load balancer can patch its healthy set instead of rereading every host. The host count getters now read the counts without building the host vectors.ext_proc: Added max_streamed_body_chunk_size and per-route max_streamed_body_chunk_size to limit the body payload size of individual
ProcessingRequestmessages sent inSTREAMEDandFULL_DUPLEX_STREAMEDbody processing modes.formatter: Added the
%ENVOY_VERSION%substitution formatter which returns the version string of the Envoy binary.http: Added per_stream_buffer_limit_bytes to the Envoy gRPC client, matching the Google gRPC field of the same name. It sets the buffer limit of each stream, which bounds both the request data buffered for retries and the data buffered before the upstream is ready, above which the stream reports that it is above its write buffer high watermark. Without it, the 64 KiB default applies, so a request or stream that sends more than 64 KiB before the response headers is no longer retried, for example a gRPC access log stream configured with
grpc_stream_retry_policy.http: Added downstream watermark callbacks support to the HTTP async client to enable downstream flow control backpressure on sidestream requests. This behavior is disabled by default and can be enabled by setting the runtime guard
envoy.reloadable_features.async_client_downstream_flow_control2totrue.http: Added support for configuring formatter extensions used in early request header mutation values. This allows formatter-specific options such as CEL string functions to be enabled.
http2: Added the
metadata_droppedHTTP/2 codec stat. It counts outbound metadata that is dropped becauseallow_metadataisn’t enabled on the connection.jwt_authn: Added allowed_algorithms to restrict the signing algorithms a provider accepts. A JWT whose
algis not listed is rejected before any key is tried.matching: Added the URI template input matcher, which matches a path against a URI template with the same semantics as route path_match_policy.
mcp_json_rest_bridge: Added support for configurable cache metadata (
ttlMsandcacheScope) in locally-generated statelesstools/listresponses via cache_config.mcp_json_rest_bridge: Added validation of
Mcp-Param-{name}request headers for MCP2026-07-28tools/call requests, configured per tool with the new mcp_param_headers field. Each mapping names the header, the literal property path of the argument, and itsSTRING,INTEGERorBOOLEANtype. A missing, unexpected, malformed, or mismatched header is rejected with aHeaderMismatcherror.mcp_json_rest_bridge: Added validation that stateless MCP
2026-07-28requests includeparams._meta["io.modelcontextprotocol/clientCapabilities"]as a JSON object. Requests where the field is missing or is not an object are rejected with a400Invalid params(-32602) JSON-RPC error. Individual capability contents are not validated.overload_manager: Added load shed point
envoy.load_shed_points.http2_server_reset_stream_on_begin_headersthat rejects new HTTP/2 streams at the codec level on the initial header frame by sending aRST_STREAM(REFUSED_STREAM)before allocating stream or header objects. When aRST_STREAMframe is submitted by this load shed point, the counterhttp2.stream_refused_errorswill be incremented.overload_manager: Added the new HTTP priority load shed filter envoy.filters.http.priority_load_shed, which maps request header integer ranges to overload manager load shed points. It supports fallback shedding via a default load shed point.
ratelimit: Added shadow_over_limit to the rate limit service descriptor status. When any descriptor sets it, the HTTP, network and Thrift rate limit filters increment the
shadow_over_limitcounter, so descriptors over limit in shadow mode can be observed.set_filter_state: Added
life_spanconfiguration parameter to the HTTP set_filter_state. This allows the filter to update filter state withConnection,FilterChain(the default) orRequestlife times.set_filter_state: Added support for using the set filter state HTTP filter as an upstream HTTP filter, so filter state can be set per upstream attempt from the selected host, for example with
%UPSTREAM_METADATA(...)%.tracing: Added the
tracing.opentelemetry.spans_export_failedcounter to the OpenTelemetry tracer, which counts spans whose export request to the trace collector could not be sent.udp: Added upstream_packet_writer_config to support optional GSO batching on upstream UDP sockets.
upstream: Added
cluster_manager.odcds.discovery_attempt,cluster_manager.odcds.discovery_success,cluster_manager.odcds.discovery_missing, andcluster_manager.odcds.discovery_timeoutcounters for on-demand cluster discovery. Each counter increments once per discovery, regardless of how many requests were waiting for that cluster.upstream: Added an adaptive LIFO queue policy for pending requests. The policy uses FIFO ordering until a connection pool’s queue reaches a configured high watermark and LIFO ordering until the size reaches a lower configured watermark. Separate watermarks avoid repeated order changes near the overload boundary.
upstream: Extended wait_for_warm_on_init to all cluster types. Setting it to
falsenow opts the cluster out of pausing CDS while it warms, so a cluster that warms slowly or indefinitely (for example one waiting on an SDS secret withinitial_fetch_timeout: 0s) no longer holds back the CDS ACK and other cluster updates.wasm: Added the
wasm.<runtime>.memory_sizeandwasm.<name>.vm_memory_sizegauges, reporting the linear memory size in bytes of the Wasm VM instances of a runtime and of a plugin. See Wasm runtime statistics.watchdog: Added log_level to the
envoy.watchdog.backtrace_actionwatchdog action to make the level of the emitted backtrace logs configurable. The level defaults to critical.