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_change to false, 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 guard envoy.reloadable_features.geoip_sanitize_geo_headers to false.

  • 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_close to false.

  • stats: The tag-extracted names of per-worker stats now keep worker as a separate name segment when the worker_id tag is extracted: server.worker_1.watchdog_miss is now extracted to server.worker.watchdog_miss instead of server.worker_watchdog_miss, and the same applies to the per-worker stats under listener.<address>., listener_manager. and thread_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 as dog_statsd, metrics_service and 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-failure HTTP 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 the upstream_cx_connect_fail cluster 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 guard envoy.reloadable_features.default_client_tls_max_version_is_1_3 to false, or per cluster by setting tls_maximum_protocol_version to TLSv1_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_transition to false.

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=gcc now fails on other platforms instead of building with Clang. The GCC build image, --config=libstdc++ and BAZEL_USE_LIBSTDCPP are removed. To build with a system GCC, set gcc_extension.host(path = "...", toolchains = ["gcc_linux_x86_64"]) from @envoy_toolshed//toolchains/gcc:extensions.bzl in the root module.

  • cares: Changes the default value of envoy.restart_features.shared_cares_dns_resolver to false. Turn this on if qcache_max_ttl is 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 app and version labels again, and uses app.kubernetes.io/name and app.kubernetes.io/version only when those labels are absent or empty. A workload’s own stats always use app and version, so giving the app.kubernetes.io/* labels precedence made source_app, destination_app, source_version and destination_version reported 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 before app.kubernetes.io/* labels were preferred.

  • dns_resolver: Updated the webpki-roots and rustls-webpki crates 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_duration for 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 from filter_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_methods set to true. When enabled with individual_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 whose x5t#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 guard envoy.reloadable_features.jwt_authn_select_key_by_x5t to false.

  • 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_update to false. An in place update of any listener also drains its connections on the FCDS filter chains that the update drops. When the envoy.reloadable_features.use_connection_event_drain runtime 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_update to false.

  • 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 uint32 value 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_cache to true.

  • 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_init to false.

  • 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 new envoy.rbac_rules_prefix tag, and the policy name of per-policy stats is extracted as envoy.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_domains entry whose trust_bundle parses 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 the trust_bundles (SPIFFE bundle map) configuration, which already rejects a trust domain with no certificate. This change can be reverted by setting the runtime guard envoy.reloadable_features.spiffe_validator_reject_empty_trust_bundle to false.

  • stats: Added the new envoy.upstream_from_zone and envoy.upstream_to_zone tags, 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 Services and emSign ECC Root CA - G3 roots 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 return EAGAIN without accepting new datagrams. The internal_buffer_size gauge 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_name to false.

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-length header 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 guard envoy.reloadable_features.compressor_skip_empty_body to false.

  • contrib: Fixed a crash in the rocketmq_proxy network 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_mesh network 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_proxy network 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_resolver runtime 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 for envoy.restart_features.shared_cares_dns_resolver to 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 reports no_host as 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_response passed grpc-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 to PERMISSION_DENIED, 429 to UNAVAILABLE).

  • 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 DecodeData or EncodeData returned StopAndBuffer after 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_stream set when the filter resumed processing the trailers, for example after an asynchronous StopAndBuffer returned 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 OnLog panicked with an invalid phase error, and request properties such as request.referer were not found.

  • grpc_field_extraction: Fixed the grpc_field_extraction filter 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 onStreamComplete so filter-set span tags are exported instead of silently dropped.

  • http: grpc: fixed core retry policy conversion so retry_back_off.max_interval is 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 guard envoy.reloadable_features.http2_trailers_after_exhausted_window to false.

  • http2: Fixed oghttp2 (envoy.reloadable_features.http2_use_oghttp2) sending outbound DATA frames as large as the peer’s SETTINGS_MAX_FRAME_SIZE, which caused legitimate flow-control WINDOW_UPDATE traffic 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 guard envoy.reloadable_features.http2_oghttp2_cap_data_frame_payload to 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_SIGSTKSZ minimum with headroom for nested fatal signals.

  • mcp: Fixed handling of MCP 2026-07-28 requests whose params._meta["io.modelcontextprotocol/protocolVersion"] is missing or is not a string. The MCP filter in REJECT_NO_MCP mode and the mcp_json_rest_bridge HTTP filter now reject them with HTTP 400 and JSON-RPC error -32602. Previously, the MCP filter accepted missing metadata and rejected non-string metadata as a -32020 header mismatch counted in header_mismatch, and the bridge handled requests with an MCP-Protocol-Version: 2026-07-28 header 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_bridge HTTP filter per-route configuration validation to reject duplicate (host, path) endpoint configurations instead of silently merging tools and ignoring subsequent tool_list_http_rule or tool_list_local settings.

  • mcp_json_rest_bridge: Fixed the mcp_json_rest_bridge HTTP filter so that a POST request that ends at the headers, such as an HTTP/1 request with Content-Length: 0 or an HTTP/2 request whose HEADERS frame carries END_STREAM, is rejected with HTTP 400 Bad Request and a JSON-RPC -32700 parse 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_bridge HTTP filter to respond with HTTP 404 Not Found instead of 200 OK when a stateless MCP 2026-07-28 request 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 legacy initialize and notifications/initialized methods sent as stateless requests. Requests that use MCP 2025-11-25 or earlier still receive 200 OK.

  • mcp_router: Added optional session_signing_key to the mcp_router HTTP 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 the ENFORCE session 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_connections overload 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 than max_tokens. Before this fix, every such request was rejected. Now, a request that fits in the bucket without sharing passes when the bucket holds max_tokens tokens. 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 than max_tokens without sharing remain rejected, and max_tokens: 0 continues to reject every request. This behavior can be temporarily reverted by setting runtime guard envoy.reloadable_features.local_ratelimit_local_cluster_preserve_one_request to false.

  • 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 like http.<stat_prefix>lua.* instead of http.<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_retries and server.hot_restart_udp_forwarding_dropped statistics 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_MAX wrapped the receive buffer size computation to a near-zero value, causing the next recvmsg() 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_expiring statistic, 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_filepath leaving 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_uring socket 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.example and b.example versus a.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 guard envoy.reloadable_features.unambiguous_tls_session_context_id to false.

  • tls: Fixed a bug in the TLS inspector’s JA4 fingerprint implementation where GREASE values in the signature_algorithms extension were included in the JA4_c hash input, contrary to the JA4 specification. GREASE values in signature_algorithms are 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 guard envoy.reloadable_features.ja4_filter_grease_signature_algorithms to false.

  • 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 invalid key_log address 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 the envoy.tls.certificate_selectors.on_demand_secret selector 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_GRO socket 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_node and legacy code paths.

  • decompression: Removed runtime flag envoy.reloadable_features.enable_compression_bomb_protection and legacy code paths.

  • dynamic_forward_proxy: Removed runtime flag envoy.reloadable_features.dfp_cluster_resolves_hosts and legacy code paths.

  • dynamic_forward_proxy: Removed runtime flag envoy.reloadable_features.skip_dns_lookup_for_proxied_requests and legacy code paths.

  • header_mutation: Removed runtime flag envoy.reloadable_features.header_mutation_url_encode_query_params and legacy code paths.

  • health_check: Removed runtime flag envoy.reloadable_features.health_check_after_cluster_warming and legacy code paths.

  • http: Removed runtime flag envoy.reloadable_features.codec_client_enable_idle_timer_only_when_connected and legacy code paths.

  • http: Removed runtime flag envoy.reloadable_features.http1_close_connection_on_zombie_stream_complete and legacy code paths.

  • http: Removed runtime flag envoy.reloadable_features.allow_multiplexed_upstream_half_close and 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, see envoy.reloadable_features.tcp_tunnel_allow_upstream_half_close.

  • listener: Removed runtime flag envoy.reloadable_features.strict_stats_matcher_unpacked and legacy code paths.

  • mcp: Removed the runtime guard envoy.reloadable_features.mcp_filter_use_new_metadata_namespace and the legacy code path it guarded. The MCP filter always publishes its dynamic metadata under the envoy.filters.http.mcp namespace (and the MCP router reads it from there); the legacy mcp_proxy namespace is no longer available.

  • on_demand: Removed runtime flag envoy.reloadable_features.on_demand_cluster_no_recreate_stream and legacy code paths.

  • proxy_protocol: Removed runtime flag envoy.reloadable_features.proxy_protocol_allow_duplicate_tlvs and legacy code paths.

  • rbac: Removed runtime flag envoy.reloadable_features.rbac_match_headers_individually and 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_buffer extension. 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 !!float tag: a scalar such as !!float 5000.0 is loaded as a JSON number instead of a string, so it stays a number inside a google.protobuf.Struct (e.g. a typed_config packed as a Struct). Untagged float literals are unchanged and still load as strings. Non-finite values are loaded as the strings Infinity, -Infinity and NaN. A !!float scalar 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 UF response 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. With config_has_built_route_cache, has_built_route_cache in 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_completion and 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_partitions so a cluster module can opt its cluster into use_persistent_host_partitions during envoy_dynamic_module_on_cluster_config_new, without the field in the cluster’s configuration.

  • dynamic_modules: Added set_dynamic_metadata_struct and set_dynamic_typed_metadata to dynamic module network filters, listener filters and cluster load balancers. A module can merge a serialized google.protobuf.Struct into a dynamic metadata namespace or set the typed metadata of a namespace from a serialized google.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 an Any without a type URL. The Rust SDK exposes them on EnvoyNetworkFilter, EnvoyListenerFilter and ClusterLbContext. The C++ SDK exposes them as setMetadataStruct and setTypedMetadata on NetworkFilterHandle and as setDynamicMetadataStruct and setDynamicTypedMetadata on ListenerFilterHandle. The Go SDK exposes them as SetMetadataStruct and SetTypedMetadata on NetworkFilterHandle and as SetDynamicMetadataStruct and SetDynamicTypedMetadata on ListenerFilterHandle.

  • dynamic_modules: Added set_prefix_rewrite_rule to 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 in rewrite_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 /certs admin endpoint and count in the server.days_until_first_cert_expiring and server.seconds_until_first_ocsp_response_expiring statistics, and the selector emits statistics about them.

  • dynamic_modules: Added the envoy_dynamic_module_callback_health_checker_set_health_metadata callback 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. The http_status_code key is reserved and rejected.

  • dynamic_modules: Added the filter config handle to HttpFilterConfig::on_scheduled in 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_discard hook to the dynamic modules route specifier, on_user_data_discard in 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 than Unspecified or a Hit from lookup_built_route, so that a module can release what it leased for the route. The user_data_discard statistic 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_count and envoy_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 ProcessingRequest messages sent in STREAMED and FULL_DUPLEX_STREAMED body 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_control2 to true.

  • 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_dropped HTTP/2 codec stat. It counts outbound metadata that is dropped because allow_metadata isn’t enabled on the connection.

  • jwt_authn: Added allowed_algorithms to restrict the signing algorithms a provider accepts. A JWT whose alg is 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 (ttlMs and cacheScope) in locally-generated stateless tools/list responses via cache_config.

  • mcp_json_rest_bridge: Added validation of Mcp-Param-{name} request headers for MCP 2026-07-28 tools/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 its STRING, INTEGER or BOOLEAN type. A missing, unexpected, malformed, or mismatched header is rejected with a HeaderMismatch error.

  • mcp_json_rest_bridge: Added validation that stateless MCP 2026-07-28 requests include params._meta["io.modelcontextprotocol/clientCapabilities"] as a JSON object. Requests where the field is missing or is not an object are rejected with a 400 Invalid 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_headers that rejects new HTTP/2 streams at the codec level on the initial header frame by sending a RST_STREAM(REFUSED_STREAM) before allocating stream or header objects. When a RST_STREAM frame is submitted by this load shed point, the counter http2.stream_refused_errors will 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_limit counter, so descriptors over limit in shadow mode can be observed.

  • set_filter_state: Added life_span configuration parameter to the HTTP set_filter_state. This allows the filter to update filter state with Connection, FilterChain (the default) or Request life 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_failed counter 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, and cluster_manager.odcds.discovery_timeout counters 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 false now 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 with initial_fetch_timeout: 0s) no longer holds back the CDS ACK and other cluster updates.

  • wasm: Added the wasm.<runtime>.memory_size and wasm.<name>.vm_memory_size gauges, 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_action watchdog action to make the level of the emitted backtrace logs configurable. The level defaults to critical.