Announcing Envoy Gateway v1.10

Envoy Gateway v1.10 release announcement.

We are excited to announce the release of Envoy Gateway v1.10.0.

This release delivers new capabilities across traffic management, security, extensibility, observability, and infrastructure — along with key bug fixes and performance improvements. We extend our thanks to the entire Envoy Gateway community for your ongoing contributions, feedback, and collaboration. Your efforts make each release possible.

Release NotesDocsCompatibility MatrixInstall

What’s New

Envoy Gateway v1.10.0 introduces powerful enhancements, resolves critical issues, and continues to improve the platform’s reliability and performance.


🚨 Breaking Changes

  • Cookie-based session persistence now always uses Path=/ instead of the matched HTTPRoute path, following updated GEP-1619 guidance. This fixes persistence when an upstream proxy rewrites the request path. Rules on the same host must use distinct sessionName values to avoid cookie conflicts, or omit sessionName to generate unique names per rule. Existing cookies remain until they expire; clients receive a new cookie with Path=/ on their next request.
  • ClusterSettings in api/v1alpha1 has been split: ClusterSettings now holds only the CDS-only fields (loadBalancer, proxyProtocol, tcpKeepalive, healthCheck, circuitBreaker, timeout, connection, dns, http2), and a new BackendSettings type embeds ClusterSettings and adds retry. YAML/JSON field paths are unchanged, but Go code that references these types directly is affected: retry moved off ClusterSettings onto BackendSettings, and BackendCluster.BackendSettings is now typed *v1alpha1.BackendSettings instead of *v1alpha1.ClusterSettings. Code that imports api/v1alpha1 and constructs or type-references ClusterSettings directly (e.g. extension implementations) needs to update to the new types.
  • Strict Lua validation now enforces a fail-closed allowlist of filesystem paths and environment variables that Lua scripts may access during validation. By default the allowlist is empty, so all filesystem and environment variable access during validation is denied. To permit specific paths or environment variables, configure EnvoyProxy.spec.lua.strictValidation.allowedPaths and allowedEnvVars. A built-in set of sensitive paths (/etc, /proc, /sys, /certs, /var/run/secrets, /run/secrets) remains always denied and cannot be re-enabled through the allowlist. This only affects the Strict Lua validation mode; InsecureSyntax and Disabled are unchanged.
  • Wasm.Code is now a pointer (*WasmCodeSource) and is optional. Go clients that set Code: WasmCodeSource{…} must take the address. YAML that already sets code is unchanged. wasm[].name is limited to 253 characters. The unused ConfigMap Wasm code type was removed from the CRD enum.
  • EnvoyProxy KubernetesServiceSpec.loadBalancerSourceRanges now accepts at most 64 entries and validates each value as a CIDR at CRD admission using the Kubernetes cidr format; loadBalancerIP validation now uses CEL IP parsing while preserving IPv4-only behavior.
  • Lua scripts that use the getfenv, setfenv, newproxy, or module globals now fail Strict Lua validation, which is the default mode. Rewrite the script without them, or set EnvoyProxy.spec.lua.validationType to InsecureSyntax or Disabled.
  • Rate-limited 429s now include Retry-After by default. Set ClientTrafficPolicy.spec.headers.disableRetryAfterHeader: true to preserve the previous behavior.
  • The watchable_subscribe_duration_seconds histogram bucket boundaries changed from {0.001, 0.01, 0.1, 1, 5, 10} to {0.005, 0.025, 0.1, 0.25, 0.5, 1, 2, 4, 8, 15, 30, 60, 120} to improve latency resolution. The metric name and its _sum/_count series are unchanged, but _bucket series with le="0.001", le="0.01", le="5", and le="10" are no longer emitted; le="0.1" and le="1" remain available. Update any dashboard, alert, or recording rule that references the removed boundaries directly. Queries that aggregate dynamically by le, such as histogram_quantile(), do not require changes.
  • Active HTTP health checks now use the HTTP protocol of the requests to the backend instead of always using HTTP/1.1. Health checks to TLS backends offer h2 and http/1.1 through ALPN and use the protocol that’s negotiated, and health checks to backends that use HTTP/2, such as GRPCRoute backends or those with the kubernetes.io/h2c appProtocol, use HTTP/2. Set healthCheck.active.http.protocol to HTTP1 in the BackendTrafficPolicy to keep the previous behavior for a backend whose health check endpoint only serves HTTP/1.1.
  • SecurityPolicy authorization rules with operation on a TCPRoute are now rejected with a translation error.
  • HTTP is no longer supported as an OIDC issuer URL scheme.
  • OCI Wasm image pulls now require the registry to serve HTTPS. The implicit fallback to plain HTTP has been removed, so a Wasm extension backed by a plain HTTP registry will fail to load unless that registry is explicitly configured as insecure.
  • A SecurityPolicy whose authorization rules reference a JWT provider that is not defined in jwt.providers is now rejected with an Accepted: False condition, and the routes it targets return a 500 direct response. Previously the policy was accepted but the rule never matched, so a request without a valid JWT was rejected with a 401 and a request with a valid JWT fell through to authorization.defaultAction, a 403 when it is Deny. Fix the provider name, define the missing provider, or - for a policy with mergeType set - define it on the parent policy it merges into.
  • Changed the xDS shape of Lua filters generated from an EnvoyExtensionPolicy. The HTTP connection manager now holds one empty, disabled Lua filter per execution slot, named envoy.filters.http.lua/<index>, instead of one filter per policy carrying its script. Each route carries its scripts as LuaPerRoute configuration with a shared_vm_id set to the script name, so Envoy builds one set of Lua VMs per script no matter how many routes run it. A script’s VM is now shared across every listener that runs it, where previously each listener had its own. Global variables that a script uses as shared state, such as a cache or a counter, were already shared by all routes of the policy on one listener and are now also shared across listeners. This should rarely matter, but scripts that rely on per-listener globals should be aware of it and use a separate policy per listener if needed, since each policy script gets its own VM. An EnvoyPatchPolicy that targets a Lua filter by its old policy-derived name, or edits a script through the listener, needs to target the route instead.
  • The session name generated when a route rule omits sessionPersistence.sessionName now ends with a short hash of the route identity (for example httproute-default-route-1-rule-0-1a2b3c4d), so routes whose namespace and name flatten to the same string (such as a-b/c and a/b-c) no longer share a session cookie or header. Existing HTTPRoute sessions that rely on the generated name lose affinity once after upgrading; set sessionName explicitly to keep a stable name.
  • EnvoyProxy.spec.dynamicModules[].local.path now requires an absolute path at CRD admission, matching the existing rule for local Wasm module paths. A relative path such as foo.so previously passed validation and is now rejected.
  • Backend now requires endpoints when type is Endpoints (the default). Such Backends previously passed API validation even though they had no destination to route to, and are now rejected.
  • ClientTrafficPolicy now rejects tls.clientValidation.subjectAltNames, certificateHashes and spkiHashes when the client validation mode is VerifyIfGiven (or the deprecated optional: true). Envoy requires a client certificate whenever those matchers are set, so the combination silently behaved as RequireAndVerify; use RequireAndVerify when those fields are needed.
  • Health check timeout and interval (active), and interval and baseEjectionTime (passive), are now rejected at admission when set to 0s. These values were previously accepted by the API but failed IR validation during translation. The same rule now also applies to rateLimit.serviceSettings in the EnvoyGateway configuration, so an invalid value fails at startup instead of blocking xDS updates.
  • Removed the watchable_depth metric. It read the length of the subscription channel, which watchable.Map.Subscribe returns unbuffered, so it always reported 0. The bundled Grafana dashboards now build their Namespace and Runner variables from watchable_subscribe_total, and the depth panel is gone; any alert or dashboard of your own that queries watchable_depth should be removed or repointed.
  • Extension manager backendResources can no longer include the backend kinds Envoy Gateway resolves itself (core Service, multicluster.x-k8s.io/ServiceImport, gateway.envoyproxy.io/Backend), and Envoy Gateway now fails to start with such a configuration. Because extension backends are matched by group and kind, listing one of these took over every backendRef of that kind in the cluster, skipping Envoy Gateway’s own validation and endpoint resolution for them. Remove these entries from backendResources; extensions that need to adjust these backends should use the xDS translator hooks, such as the Cluster hook, instead.
  • The jwt_authn requirement generated from a JWT SecurityPolicy is now named after the SecurityPolicy that applies to the route, <namespace>/<name>, or for a merged policy after the route and parent policies it was built from, <route namespace>/<route name>+<parent namespace>/<parent name>, instead of the provider names and a hash of its content (<provider>[-or-<provider>...][-missing|-missing-or-failed]_<hash>). Routes that share a policy still share one requirement. An EnvoyPatchPolicy that targets a requirement by its hash-based name needs to use the new name. The names change once on upgrade, so requests on connections opened before it may be refused with 403 Wrong requirement_name until those connections drain.

🔒 Security Updates

  • Hardened the Lua validation sandbox by blocking the getfenv, setfenv, newproxy, and module globals.
  • Enabled AES-256-GCM encryption for OAuth2/OIDC session cookies and disabled the legacy AES-256-CBC decryption path in the Envoy proxy bootstrap, addressing the padding oracle in CVE-2026-47775. Existing OIDC sessions were encrypted with AES-256-CBC and are no longer accepted, so users with an active session are redirected to re-authenticate once after upgrading. This is set in the default bootstrap, so an EnvoyProxy using spec.bootstrap with type Replace (the default when no type is given) does not receive it; OIDC users with a replacement bootstrap must add envoy.reloadable_features.oauth2_use_gcm_encryption: true and envoy.reloadable_features.oauth2_legacy_cbc_decrypt_compat: false to a layered_runtime static layer themselves.
  • Added validation for the OIDC issuer URL configured in SecurityPolicy.
  • Fixed a nil pointer dereference in SecurityPolicy translation for TCPRoutes: a listener with no corresponding xDS IR entry (for example, a Gateway listener marked Conflicted for sharing a port with another listener) could panic the control plane during translation, silently dropping that reconcile’s IR and status publication.
  • Fixed a confused-deputy/PSA-escape issue (GHSA-w42f-28h3-998w) where a tenant-supplied KubernetesContainerSpec.SecurityContext on an EnvoyProxy replaced Envoy Gateway’s hardened default SecurityContext outright instead of merging on top of it, allowing a tenant whose EnvoyProxy is materialized in the shared controller namespace to drop restrictions (e.g. run privileged or as root) that the controller namespace’s Pod Security Admission would otherwise enforce.
  • Fixed a control-plane availability issue in EnvoyExtensionPolicy Wasm OCI permission handling.
  • Fixed OCI Wasm image pulls silently downgrading to plain HTTP when the registry rejected the HTTPS request, which allowed an on-path attacker to serve arbitrary Wasm code to the Envoy proxies. Plain HTTP is now used only for registries that are explicitly configured as insecure.
  • Prevent user-defined proxies from conflicting with or using controller-owned resources in ControllerNamespace mode. The following are now rejected: resource names colliding with envoy-gateway or envoy-gateway-config; service account names matching the controller’s own SA or the certgen SA (<fullname>-certgen, where <fullname> is the Helm release fullname, defaulting to envoy-gateway); volumes mounting the envoy-gateway Secret or ConfigMap; and environment variables referencing those resources via secretKeyRef, configMapKeyRef, or envFrom.
  • Added an EnvoyProxyPatch runtime flag to EnvoyGateway. EnvoyProxy Kubernetes resource patch fields can grant arbitrary access to resources applied by Envoy Gateway’s more privileged ServiceAccount when EnvoyProxy is namespace-scoped and tenant-authored. The flag is enabled by default to preserve pre-existing behavior; multi-tenant clusters where tenants can author their own EnvoyProxy resources should disable it through runtimeFlags.disabled.
  • Fixed a panic in the xDS server’s Kubernetes JWT authentication when the TokenReview response contains an Extra field without the pod name key (authentication.kubernetes.io/pod-name). Such tokens (e.g. service account tokens not bound to a pod) are now rejected with an Unauthenticated error instead of crashing the control plane.

✨ New Features

API & Traffic Management

  • Added a failTrafficOnPanic field to the zone-aware preferLocal load balancer settings, exposing Envoy’s fail_traffic_on_panic. When a cluster enters panic mode because the share of healthy endpoints dropped below panicThreshold, Envoy load balances across every endpoint regardless of health; setting this to true makes it reject the traffic instead, applying backpressure to clients and giving the backend room to recover. It sits under preferLocal because Envoy only defines the setting inside zone_aware_lb_config. When unset it defaults to false, preserving the existing behavior.
  • Added support for tuning Gzip, Brotli and Zstd compressor settings in the compressor field of BackendTrafficPolicy.
  • Added a mode field to requestBuffer in BackendTrafficPolicy. The default BufferAndLimit keeps the existing behavior of buffering the whole request before forwarding it upstream. The new LimitOnly mode only raises the request body buffer limit for the route, so a filter later in the chain can buffer a larger body without the gateway fully buffering the request; unlike BufferAndLimit, it is compatible with protocol upgrades and streaming. Requests whose buffered body exceeds the limit are rejected with HTTP 413 in both modes.
  • Added session persistence support to GRPCRoute. The sessionPersistence field on a GRPCRoute rule was previously accepted but ignored; it is now applied the same way as on HTTPRoute. Cookie-based (the default) and header-based persistence are supported, a session name is generated per rule when sessionName is omitted, and absoluteTimeout sets the cookie TTL when cookieConfig.lifetimeType is Permanent. A rule with an unknown persistence type is rejected with reason UnsupportedValue.
  • Added a forwardProtoConfig field to proxyProtocol in ClientTrafficPolicy, allowing the x-forwarded-proto header to be inferred from the PROXY protocol destination port when PROXY protocol is enabled. This is useful when a Layer 4 load balancer (such as AWS NLB) terminates TLS and forwards traffic to Envoy over PROXY protocol.
  • Added a mutations field to ClientTrafficPolicy’s headers.earlyRequestHeaders and headers.lateResponseHeaders filters, an ordered list of header operations applied in exactly the order given. Each entry is one of write (a header plus an action of Append, Overwrite, AddIfAbsent or OverwriteIfExists, and an optional keepEmptyValue), remove, or removeOnMatch. This expresses sequences the existing flat set/add/addIfAbsent/remove/removeOnMatch fields cannot, such as overwriting a header and then appending to it, or removing a header and then re-adding it.
  • Added a maxRequestHeaderLimit field under ClientTrafficPolicy’s headers to configure the maximum request header size (Envoy’s max_request_headers_kb) without an EnvoyPatchPolicy. The value is a Quantity rounded up to the nearest KiB, must be at least 1Ki, and cannot exceed 8192Ki.
  • Added a disableConnectionReuse field to the active health check configuration in BackendTrafficPolicy, exposing Envoy’s HealthCheck.reuse_connection. Setting it to true makes Envoy open a fresh connection for each health check probe, which is required to interoperate with backends such as haproxy-style agent checks that do not expect a reused connection. When unset it defaults to false, preserving the existing behavior of reusing the connection across probes.
  • Added ClientTrafficPolicy.spec.headers.disableRetryAfterHeader to control whether a Retry-After header is emitted on rate-limited 429 responses, for both the global and local rate limit filters. The header is emitted by default; set this field to true to opt out.
  • Added healthCheck.active.http.protocol to BackendTrafficPolicy, to select the HTTP protocol used to send active health check requests to a backend whose health check endpoint doesn’t serve the protocol of its application endpoint.
  • Added support for BackendTrafficPolicy to target a Service, ServiceImport, or Backend directly, applying cluster-scoped settings to the backend’s merged BackendCluster (requires the MergeBackends EnvoyProxy feature).
  • Added ClientTrafficPolicy.spec.http3.advertisedPort to configure the port in HTTP/3 alt-svc response headers when an external load balancer exposes a different port than the Gateway listener.
  • Added gRPC-JSON transcoding to HTTPRouteFilter with the grpcJSONTranscoder field, so REST/JSON clients can call gRPC backends. The proto descriptor set is read from a ConfigMap and validated when the route is translated.
  • Added support for matching on request headers in BackendTrafficPolicy.responseOverride.
  • Added Backend type: KubernetesService to route to an in-cluster Service through its EndpointSlices, so Backend tls and appProtocols apply on top of the same endpoint handling as a Service backendRef.
  • Added http3 to BackendTrafficPolicy, enabling HTTP/3 to the backend. The default Auto mode uses HTTP/3 only for backends that advertise it through an alt-svc response header and falls back to TCP when QUIC is unavailable, while Always sends every request over HTTP/3. The backend must be configured with TLS through a BackendTLSPolicy or the Backend’s spec.tls; where HTTP/3 cannot be used for a route, it is left off and the policy reports a Warning condition.
  • Added support for rewriting the Host header on mirrored requests in HTTPRouteFilter using spec.requestMirror.hostname with Literal and Backend modes.
  • Added BackendTrafficPolicy.spec.http1 (BackendHTTP1Settings) for configuring HTTP/1 settings on upstream backend connections, including enableTrailers and preserveHeaderCase. When both BackendTrafficPolicy.spec.http1 and ClientTrafficPolicy.spec.http1 are set, BackendTrafficPolicy takes precedence for backend traffic. Setting http1: {} (empty) forces explicit HTTP/1 on the upstream cluster. When targeting a GRPCRoute, TCPRoute, TLSRoute, or UDPRoute directly via targetRef, targetRefs, or targetSelectors, the policy remains accepted but receives a Warning condition since those route kinds do not use HTTP/1 and the field has no effect. When attached to a broader target (Gateway, ListenerSet, or a specific listener via sectionName), http1 is silently ignored for any GRPCRoute, TCPRoute, TLSRoute, or UDPRoute destinations beneath that target and still applies to HTTPRoute destinations. For gRPC-only external service backends (ExtProc, gRPC ExtAuth, ALS, OpenTelemetry access log/metrics/tracing), http1 on their own BackendSettings is rejected at admission by existing validations on those types.
  • Added the backendPriority field to HTTPRouteFilter, which sets the Envoy locality priority of the HTTPRoute backendRef it is referenced from, enabling multi-tier, per-route priority failover between a rule’s backendRefs. Rules using it are excluded from MergeBackends cluster deduplication.
  • Added maxConnectionDurationJitter to the connection limit settings in ClientTrafficPolicy, so connections opened at the same time do not all reach their max duration and reconnect together.

Security & Authentication

  • Added OIDC token cookie chunking: Envoy now splits an access, ID or refresh token too large for one browser cookie across several cookies, instead of the browser dropping it and the login looping back to the OIDC provider. Tokens that fit in one cookie are written as before. Envoy Proxy older than v1.40 can’t read the split cookies, so while proxies of an earlier release serve the same hostnames, for example during the upgrade, disable the new OIDCTokenCookieChunking runtime flag, which is enabled by default, through runtimeFlags.disabled.
  • Added spec.oidc.postLogoutRedirect to SecurityPolicy to configure the post_logout_redirect_uri parameter EG sends to the OIDC Provider during RP-Initiated Logout, either by setting an explicit uri or by setting disabled to omit the parameter entirely.
  • Added spec.oidc.codeVerifierTTL to SecurityPolicy to configure how long the PKCE code verifier cookie generated during the OAuth2 authorization flow remains valid. Alongside csrfTokenTTL, this bounds how long the cookies of an abandoned authorization flow live in the browser. Defaults to 10 minutes.
  • Added support for customizing all Envoy OAuth2 cookie names through SecurityPolicy OIDC cookieNames, and a cookieNames.suffix field that pins the suffix Envoy Gateway appends to every OAuth2 cookie name.
  • Added UDPRoute as a valid SecurityPolicy target, so client IP allowlists and denylists can now be applied to UDP services such as DNS, syslog, NTP, telemetry collectors and game servers. As with TCPRoute, only authorization rules using principal.clientCIDRs apply; the client IP is the source address of the datagram, and a denied source is dropped without a reply rather than receiving an error.
  • Added an optional claimPath field to SecurityPolicy’s JWT claimToHeaders, letting claims whose names contain dots — such as URI-namespaced OIDC claims like https://example.com/claims/tenant_name — be extracted into a header via explicit path segments instead of the dot-split claim field.
  • Added SecurityPolicy.spec.cors.allowOriginRegexes to allow CORS origins using regular expressions alongside existing allowOrigins entries.
  • Added Issuer to certDetailsToAdd in ClientTrafficPolicy, so the issuer of the client certificate can be forwarded in the XFCC header.
  • Added packAsBytes to SecurityPolicy extAuth.bodyToExtAuth to send the request body to gRPC external authorization services as raw bytes. maxRequestBytes is now optional and defaults to 32768 bytes (32KiB).

Extensibility

  • Added local Wasm modules registered on EnvoyProxy.spec.wasmModules (source type Local with a path today; shape matches DynamicModule for future sources). When EnvoyExtensionPolicy.wasm[].code is omitted, wasm[].name is looked up in that list so Envoy can load the file without the control-plane fetch and cache path. HTTP and Image stay on wasm[].code. The unused ConfigMap enum value (never implemented) was removed.
  • Added named backend dependencies to EnvoyExtensionPolicy for Lua, Wasm and dynamic module HTTP callouts, with optional backend connection settings and alias bindings exposed through static route metadata. Disabled by default and enabled by setting extensionApis.enableExtensionBackends to true in the EnvoyGateway configuration.
  • Added the per-entry vmSharingScope option to EnvoyExtensionPolicy. Setting it to Namespace allows compatible Wasm extensions in the same policy namespace to reuse a VM on each Envoy worker. It defaults to Policy, preserving a separate VM ID for each policy’s Wasm entry. The same entry may reuse its VM across routes; different entries do not share a VM.
  • ExtensionServerPolicy can now target a GatewayClass (via targetRefs or targetSelectors) when mergeGateways is enabled, attaching the policy to every listener of the merged Gateways. Targeting a GatewayClass without mergeGateways now reports an Accepted=False status instead of being silently ignored. sectionName is not supported on a GatewayClass target, since Gateway API only defines it for kinds such as Gateway, and such a target is rejected as Invalid; target a Gateway with sectionName to select a listener.
  • Added support for listener TLS certificates via extension server using the Secret hook. A Gateway or ListenerSet listener’s tls.certificateRefs can reference a kind registered in the Extension Manager’s secretResources, and the extension server resolves it to the SDS secret config Envoy uses.
  • Added a timeout field to the Extension Manager’s service. It bounds each extension hook call, including the time spent waiting for the extension server to become reachable and any retries. Previously an unreachable extension server blocked xDS translation indefinitely, because hook calls had no deadline and failOpen only applies once a call returns an error. With timeout set, the call fails with DEADLINE_EXCEEDED and failOpen takes effect. Leaving timeout unset keeps the existing behavior.
  • ExtensionServerPolicy can now target a ListenerSet (via targetRefs or targetSelectors), or a single ListenerSet listener via sectionName, attaching the policy to that ListenerSet’s listeners and reporting status against the ListenerSet. Previously such policies were silently ignored.
  • Extension server policies targeting a Gateway (without sectionName) are now forwarded to PostVirtualHostModifyHook via the hook context’s extension_resources field. Each virtual host receives only the policies attached to its own listener (or containing Gateway).

Infrastructure & Helm

  • Added deployment.pod.initContainers and deployment.pod.extraContainers to the gateway-helm chart, passed through to the control-plane Deployment so additional init/sidecar containers (including native sidecars with restartPolicy: Always) can run alongside the Envoy Gateway controller.
  • Added a serviceSettings field under EnvoyGateway.spec.rateLimit to configure the Envoy cluster that connects the proxies to the global rate limit service, such as circuit breakers, timeouts and health checks. It reuses the ClusterSettings type, but its fields are supported on a best-effort basis: known-unsupported fields are rejected (see the API reference), and a field that doesn’t apply to the rate limit service cluster may have no effect. The CRD schema defaults, OpenAPI constraints and CEL rules of ClusterSettings are applied when the config is loaded, so an invalid value is rejected at startup.
  • Added PreferDualStack to EnvoyProxy’s ipFamily. It sets the proxy Service to the PreferDualStack IP family policy and leaves ipFamilies unset, so the same EnvoyProxy applies to both dual-stack and single-stack clusters. DualStack keeps requiring both families.
  • Added cosign keyless signing of the docker.io/envoyproxy/gateway release image. Verify it with cosign verify and the identity of the release workflow, as shown in the “Verify Image Signatures” install docs.

Observability

  • Added support for tuning the Gzip, Brotli and Zstd compressor settings of the Prometheus metrics endpoint compression in EnvoyProxy.
  • Added an optional prefix field to the OpenTelemetry metrics sink (EnvoyProxy.spec.telemetry.metrics.sinks[].openTelemetry.prefix) that is prepended to emitted stat names, so the full stat name becomes <prefix>.<stat_name>.
  • Added enableCircuitBreakerRemainingStats to EnvoyProxy telemetry metrics, enabling Envoy’s circuit breaker track_remaining field so that the remaining_* gauges report how much headroom is left before a circuit breaker opens.
  • Envoy Gateway’s own traces now carry the Envoy Gateway version in the service.version resource attribute and the git commit in envoy_gateway.git_commit_id, so traces can be compared across versions.
  • Added extension hook call counts and durations as OTel span attributes on the XdsTranslator.processHTTPListenerXdsTranslation span (extension.hook.route_call_count, extension.hook.route_duration_ms, extension.hook.vhost_call_count, extension.hook.vhost_duration_ms) and the XdsTranslator.notifyExtensionServerAboutListeners span (extension.hook.listener_call_count, extension.hook.listener_duration_ms), enabling latency and call-count visibility for extension server hooks in distributed traces. When multiple extension servers are registered, call counts reflect the sum of all servers actually invoked and durations cover the total time across all of them.

Status & Validation

  • Added a BackendPriorityIneffective condition to HTTPRoute and GRPCRoute status when a rule’s backends have different priorities, set by a backendPriority HTTPRouteFilter or a fallback Backend, but are split into separate clusters, so priority failover between them has no effect.

🐞 Bug Fixes

  • Fixed minContentLength being ignored for the Prometheus metrics endpoint compression in EnvoyProxy.
  • Fixed gateway-wide ClientTrafficPolicy port-overlap validation for Gateways whose names share the same prefix.
  • Fixed OIDC flow-state cookies accumulating in the browser and overflowing the request header size limit. Envoy mints a nonce (CSRF) and a PKCE code verifier cookie for every authorization flow it starts, but only deletes the pair belonging to the flow that completes the callback, so flows that are abandoned - parallel requests from a logged out browser, a user navigating away from the provider’s login page - leave their cookies behind until they expire. Envoy Gateway now scopes both cookies to the OIDC redirect path, the only path where Envoy needs their value, so any orphans are no longer sent on every request. Note this bounds the damage rather than eliminating it: orphans are still sent to the callback endpoint itself until they expire, and logout can no longer purge them early because the browser no longer sends them to the signout path, so also consider lowering csrfTokenTTL. The PKCE code verifier cookie is also now named CodeVerifier-<suffix>, carrying the same per-policy suffix as the other OAuth2 cookies instead of Envoy’s shared default, so SecurityPolicies on the same cookie domain no longer delete each other’s in-flight flow cookies on logout. On upgrade, a browser already holding flow cookies keeps them at the old path=/, since the new deletion headers are scoped to the redirect path. They expire on the lifetime they were originally issued with - 10 minutes by default, and unaffected by any csrfTokenTTL you configure during the upgrade. The old code verifier cookie name is also no longer read, so a login that was in progress across the rollout may need to be retried once.
  • Fixed HTTPRoute (and other xRoute) acceptance not being re-evaluated when a namespace’s labels changed to newly match, or stop matching, a Gateway listener’s allowedRoutes.namespaces.from: Selector, requiring a controller restart to pick up the change. Envoy Gateway now watches Namespace label updates and re-reconciles affected Gateways automatically.
  • Fixed the File and ALS access log sinks silently falling back to the default JSON fields when telemetry.accessLog.settings[].format sets text without type, which the API accepts.
  • Added CRD validation (minimum: 200, maximum: 599) to BackendTrafficPolicy’s responseOverride[].response.statusCode field, rejecting out-of-range values such as -1 at admission time instead of letting them reach the generated Envoy config.
  • Fixed HTTPRoutes attached to a listener that won a hostname conflict being rejected with NoMatchingListenerHostname and omitted from xDS by excluding conflict-losing listeners from route hostname filtering.
  • Fixed active health checks failing against backends that speak HTTP/2: health checks are now sent over the protocol of the backend, and health checks to a backend that negotiates its protocol through ALPN negotiate it the same way, so that every endpoint is checked over the protocol that requests to it use. Negotiating health checks require Envoy 1.40 or later.
  • Fixed the controller crash-looping when an extension manager’s backendResources references a CRD that is not installed in the cluster. The CRD existence is now checked once at controller startup and cached in a field on the reconciler, following the same pattern already used for ServiceImport, Backend, and other optional CRDs. When the CRD is absent, both the watch and the reconcile list path are skipped; all other errors from the list call are returned so the reconcile is retried instead of publishing an incomplete resource snapshot.
  • The global rate limit cluster is now built from the in-cluster envoy-ratelimit Service/EndpointSlices using EDS instead of resolving a static DNS hostname, so requests keep hitting rate limit service replicas correctly as they scale up or down. When the Service or its endpoints can’t be discovered, Envoy Gateway falls back to the previous STRICT_DNS behavior.
  • Fixed a BackendTrafficPolicy setting a ConsistentHash load balancer having no effect when it targets a UDPRoute. The cluster was configured with Maglev, but no hash policy was set on the UDP proxy listener filter, so Envoy had no hash key to compute and fell back to picking an upstream host at random for every UDP session. The source IP hash policy is now configured on the UDP proxy. Only the SourceIP consistent hash type applies to UDP, since headers, cookies and query parameters do not exist in a UDP datagram.
  • Removed the --cpuset-threads flag from the Envoy proxy startup arguments, which was causing Envoy to log a warning when the container’s CPU limits did not align with a CPU set.
  • Fixed controller panic when translating backend credential injection with an invalid header.
  • Fixed the Wasm image permission cache key omitting the CA certificate, which allowed a permission check result to be reused across different TLS trust configurations.
  • Fixed rateLimitDeployment.pod.priorityClassName being ignored when rendering the rate limit Deployment, so the configured PriorityClass is now applied to the rate limit pod the same way it is for the Envoy Proxy deployment.
  • Fixed egctl x translate ignoring the binaryData of ConfigMaps.
  • Fixed a Gateway, xRoute, policy, Backend or EnvoyProxy being left with no status when it is deleted and recreated with the same name and an unchanged spec within a single reconcile, which previously left the recreated object stuck at the default status its CRD defines until the controller was restarted.
  • Fixed responseOverride in BackendTrafficPolicy clearing %RESPONSE_CODE_DETAILS%. The reason the request actually failed (for example csrf_origin_mismatch) is now preserved in access logs instead of being logged as an empty string.
  • Fixed a route-level SecurityPolicy with mergeType set being rejected on admission when its authorization rules reference a JWT provider defined by the parent SecurityPolicy it merges into (if authorization.rules.principal.jwt is used, jwt must be defined). The CRD validation ran against the policy before the merge, so the JWT providers had to be duplicated on every route. The reference is now resolved against the merged policy.
  • Fixed EnvoyGateway.spec.envoyProxy accepting an invalid EnvoyProxySpec template. This default/merge-base spec is now validated the same way a standalone EnvoyProxy resource is, so misconfigurations such as an invalid provider are rejected at config-load time instead of surfacing later as a translation failure.
  • Fixed extension-declared backend resources (extensionManager.backendResources) not triggering reconciliation when only their labels or annotations changed, requiring a spurious spec change or controller restart to pick up the update. The watch predicates for these resources (and for other extension-managed GVKs) now share a common generation/label/annotation-changed predicate, so annotation or label updates on an extension backend now regenerate xDS as expected.
  • Fixed local rate limiting for Distinct client selectors to retain up to 10,000 per-value token buckets per wildcard descriptor by applying the cache limit to each route’s filter configuration instead of relying on Envoy’s default of 20.
  • Fixed HTTPRoute status collapsing when a route had two parentRefs to the same Gateway differing only by port (with no sectionName). IsParentRefEqual short-circuited before comparing Port, so both refs resolved to the same RouteParentStatus and one listener’s verdict overwrote the other’s — a route accepted on one listener could report Accepted: False borrowed from another. Listener selection and the data plane were unaffected; only the reported status was wrong.
  • Fixed a duplicate filter chain matcher error when a TLS listener with no attached routes shares a port with an HTTPS listener, which caused Envoy to reject every subsequent xDS update to that listener.
  • Fixed HTTPS and TLS listeners from different merged Gateways sharing a port and a hostname being accepted, which produced two Envoy filter chains with the same SNI match and caused Envoy to reject the listener.
  • Fixed mergeBackends applying a whole-Gateway BackendTrafficPolicy’s entire cluster-scoped feature set to merged UDP and TCP clusters regardless of the backend’s protocol. Without mergeBackends those settings can never reach such a cluster - ir.UDPRoute, for instance, only carries load balancing and DNS - so enabling cluster deduplication could put healthCheck, circuitBreaker, tcpKeepalive, proxyProtocol, http2, useClientProtocol and HttpProtocolOptions on the cluster udp_proxy routes to, most damagingly running TCP/HTTP health checks against a UDP-only port. A merged cluster now receives only what the same policy would produce for that protocol without mergeBackends.
  • Fixed the xDS translator emitting equivalent-but-byte-different resources on every reconcile: filter typed_config was marshaled non-deterministically, so proto map fields (such as an access log’s json_format) re-ordered their keys each translation and caused repeated no-op xDS pushes. Typed configs are now marshaled deterministically.
  • Fixed ClientTrafficPolicy rejecting BoringSSL equal-preference cipher groups such as [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] as an unsupported cipher suite. Each member of a group is now validated on its own.
  • Fixed MergeBackends being unnecessarily disabled for HTTP and gRPC rules using gateway-level ConsistentHash load balancing.
  • Adding, editing or removing a Lua EnvoyExtensionPolicy no longer drains the listener. The scripts travel with the routes, so a policy change is a route configuration update rather than a listener rebuild. The listener carries placeholder Lua filter slots allocated in blocks of 10, sized to the longest Lua chain on any of its routes. It is still rebuilt when a Gateway gets its first Lua policy or loses its last one, or when that longest chain crosses a multiple of 10, for example growing from 10 scripts to 11.
  • Fixed active gRPC health checks sending an invalid :authority header. Envoy Gateway only set the service name on the gRPC health checker, so Envoy fell back to the name of the cluster, which is generated per route rule and contains slashes (grpcroute/my-ns/my-route/rule/0). Slashes are not valid in an authority, and gRPC servers built on golang.org/x/net v0.59.0 or later reset the stream with PROTOCOL_ERROR, leaving every endpoint marked failed_active_hc and clients receiving no healthy upstream from a healthy backend. The authority now defaults to the effective route hostname, mirroring the HTTP health checker, and BackendTrafficPolicy gained healthCheck.active.grpc.hostname to set it explicitly. Upgrading alone does not fix routes with a wildcard hostname or no hostname: neither is a valid authority, so unless the backend endpoint defines a hostname in a Backend resource, the authority still falls back to the cluster name, and healthCheck.active.grpc.hostname must be set explicitly.
  • Fixed the shutdown manager exiting after the minimum drain period while UDP proxy sessions were still active. The drain now also waits for udp.*.downstream_sess_active to reach the exit threshold. UDP sessions only close on their idle timeout (60s by default), so a proxy that has handled UDP traffic recently may wait until the drain timeout before exiting.
  • Fixed the shutdown-manager failing to create /tmp/shutdown-ready (breaking graceful shutdown and delaying proxy pod termination by the full grace period) when the Envoy container’s securityContext sets readOnlyRootFilesystem: true, by mounting a writable emptyDir at /tmp on the shutdown-manager.
  • Fixed a panic during shutdown caused by closing the shared infraIR/pResources channels and resources maps before all runner goroutines still consuming or writing to them had exited.
  • Fixed obsolete resource snapshots being retained by the backing array of coalesced subscription updates.
  • Fixed HTTPS listeners referencing non-existent RouteConfigurations when they share a container port with an HTTP listener that is translated first (for example HTTPS on port 443 and HTTP on port 10443). The HTTPS virtual hosts were added to the HTTP listener’s RouteConfiguration, leaving the HTTPS filter chains without routes.
  • Fixed an HTTPRoute or GRPCRoute rule with sessionPersistence.sessionName: "" producing an empty cookie or header name that Envoy rejects, which blocked the xDS update for the whole listener; such a rule is now rejected with reason UnsupportedValue.
  • Fixed route status conditions being reported in a non-deterministic order when a route parent had errors of more than one condition type, which caused unnecessary route status updates.
  • A BackendTrafficPolicy targeting a Service, ServiceImport, or Backend now reports Accepted=False with reason NotMerged on each Gateway that references the backend but has no merged cluster for it, instead of silently doing nothing.
  • Fixed the listener-level api_key_auth filter being built from the first route that uses API key auth. Rotating that route’s keys no longer forces a listener update that drains long-lived connections on every route of the listener, and a route whose credential Secret has no usable keys now rejects requests instead of accepting the first route’s keys.
  • Fixed Service and ServiceImport backends whose EndpointSlices carry both kubernetes.io/service-name and multicluster.kubernetes.io/service-name (as Cilium ClusterMesh creates them): ServiceImport backends no longer report EndpointsNotFound, and Service backends now pick up endpoint changes.
  • Fixed a BackendTrafficPolicy’s cluster-scoped settings (circuit breaker, load balancer, timeout, etc.) failing to apply to merged backend clusters whenever an unrelated route-scoped field (such as a broken rateLimit) failed to translate. This affected both whole-Gateway BackendTrafficPolicies and backend-targeted BackendTrafficPolicy merging.
  • Fixed healthCheck.passive.maxEjectionPercent: 0 being dropped from the generated Envoy cluster; the explicitly configured 0% limit is now preserved instead of falling back to Envoy’s 10% default.
  • Fixed an HTTPRoute not being reconciled when an HTTPRouteFilter that it references only from backendRef filters is created or updated, if the rule has no rule-level filters.
  • Fixed the Kubernetes provider’s cache scope being random on each start when watch.type is Namespaces, which could leave the controller unable to become Ready or unable to see its own namespace’s Secrets and ConfigMaps in Gateway Namespace Mode; the Helm chart now also grants EndpointSlice read in the controller namespace for this mode.
  • Fixed a BackendTrafficPolicy with mergeType being recorded twice in a route’s policy metadata when two of the route’s parentRefs reach the same listener.
  • Fixed a route-level SecurityPolicy, BackendTrafficPolicy or EnvoyExtensionPolicy with mergeType set reporting its status on two ancestors for the same parent when the route’s parentRef has no sectionName. The merge results (Merged, or Accepted: False when the merged policy failed to translate) went on one ancestor per listener, while Accepted: True went on the Gateway or ListenerSet itself, so a policy whose merge failed reported both Accepted: False and Accepted: True. The merge results of all listeners are now reported on the parentRef’s ancestor, the same one used without mergeType.
  • Fixed route status.parents accumulating duplicate entries until the 32-entry limit when another controller writes status for the same parentRef, by matching parent status entries on both parentRef and controllerName.
  • Fixed the Helm chart not granting Secret and EndpointSlice read in the controller namespace when watch.namespaces is set in the default deploy mode, which kept the controller from becoming Ready unless the controller namespace was listed in watch.namespaces.
  • Fixed the default Envoy proxy version reported by envoy-gateway version missing the image digest.
  • Fixed GatewayNamespace mode surfacing a misleading “illegal change in a custom label” error when a pre-existing, unmanaged Deployment or DaemonSet shares the Gateway’s name; it now reports a clear ownership-conflict error, detected via an uncached API read so label-filtered resource kinds (e.g. DaemonSet) are caught too.
  • Fixed SecurityPolicy OIDC returning HTTP 500 on every protected route when the issuer’s /.well-known/openid-configuration could not be fetched during a reconciliation, by retaining the last successfully discovered configuration per issuer across translations and falling back to it when discovery fails.
  • Fixed a stale status.parents entry lingering on a Route after one of its spec.parentRefs was removed, while still keeping status entries for parents not touched in a given reconciliation batch.
  • Fixed backendTrafficPolicy.spec.compressor[].minContentLength (and the deprecated spec.compression[]) silently wrapping when set above 4 GiB: the quantity was cast straight to a uint32, so a value such as 4Gi truncated to 0 and Envoy compressed every response instead of only large ones. Such out-of-range values are now rejected with a policy error rather than truncated.
  • Fixed route translation panicking when a backendRef to an extension backend resource whose kind collides with a built-in one (for example custom.example.io/Service) was dispatched to the built-in Service resolver. Extension backend resources are now always handed to the extension, and the Service/ServiceImport resolvers report BackendNotFound for a missing object.
  • Fixed extensionRef filters being dispatched by kind only, so an extension resource with kind HTTPRouteFilter in a different group was resolved as the Envoy Gateway HTTPRouteFilter of the same name instead of being passed to the extension server. Both the group and the kind are now checked when loading, indexing and translating HTTPRouteFilter references.
  • Fixed requests on existing connections being refused with 403 Wrong requirement_name after a JWT SecurityPolicy edit. Since v1.9.0 the jwt_authn requirement was named after a hash of its content, so an edit of the JWT config (an audience, the JWKS, claimToHeaders, optional) renamed it in both the listener’s requirement_map and every route’s requirement_name, and Envoy applies the two as separate updates. The requirement is now named after the SecurityPolicy that applies to the route, so such an edit changes only the listener’s requirement content. Connections that are still draining verify against the previous config until they close, as they did before v1.9.0.
  • An invalid telemetry setting (clusterStatName, an access log format or sink, tracing, a metrics sink or Prometheus compression) or an invalid filterOrder no longer rejects the whole EnvoyProxy. The invalid field is ignored, the rest of the EnvoyProxy is applied, and the EnvoyProxy status reports a Warning condition with reason InvalidField (or Warnings when the EnvoyProxy is also warned about for another reason, such as a deprecated field). Previously the Gateway was not accepted and its proxy infrastructure was reset to defaults, which could recreate the Envoy Deployment under a different name and drop all traffic. Invalid provider, bootstrap, or mergeGateways/mergeBackends settings still reject the EnvoyProxy.
  • Fixed status updates being dropped when the API server returned a timeout, throttling or another transient error. The status writer now retries these errors with backoff, in addition to conflicts.

🚀 Performance Improvements

  • Improved reconcile performance by listing the extension manager’s resources and backendResources once per GatewayClass instead of once per Gateway.
  • Added an opt-in debounce for the provider reconcile, configurable via the top-level debounce field in the EnvoyGateway config. Every watch enqueues the same GatewayClass request, so the workqueue already collapses events that arrive while a reconcile is in flight, but it has no quiet period: when reconciles are fast relative to the event rate, each event costs a full rebuild of the resource tree. Setting the field turns debouncing on: enqueue requests are held until no new request has arrived for debounce.after (default 100ms), or until debounce.max (default 10s) if requests keep arriving, so a burst of resource changes costs a single reconcile. Because status is computed during reconcile, setting this also delays status updates by up to debounce.max. Debouncing is off when the field is unset.
  • Reduced control-plane memory on gateways where many backends validate against the same CA by storing each upstream CA bundle once per gateway and referencing it by a digest of its content, instead of carrying a copy on every route destination.
  • Reduced xDS translation work for listeners with many routes by computing GeoIP header removals once per listener.
  • Reduced repeated JSON decoding and encoding when JSONPath patches match multiple locations.
  • Reduced snapshot-copy allocations when HTTP routes share destination settings across matches or listeners, while preserving isolation between snapshots.
  • Reduced the cost of checking cross-namespace references during reconciliation by looking up only the ReferenceGrants in the target namespace that allow references from the source namespace to the target kind, instead of listing every ReferenceGrant for the target kind across the cluster.
  • Reduced translation time for gateways with many BackendTLSPolicies by indexing the policies once per translation by the backend (and section) they target, instead of scanning every policy for each route backendRef.
  • Reduced translation time for routes that use extensionRef filters or custom backends by indexing extension resources once per translation, instead of scanning every extension resource for each filter and backendRef.
  • Improved xDS translation performance for large configurations by indexing cluster names during resource construction.
  • Policy CRDs are no longer deep-copied from the informer cache on every reconcile.
  • Reduced BackendTrafficPolicy translation work for large HTTPRoute and GRPCRoute target sets by indexing repeated route lookups within each translation.
  • Reduced BackendTrafficPolicy translation work by reusing consecutive child/parent policy merges within one translation pass.

🛑 Deprecations

  • The ClientTrafficPolicy http1 fields enableTrailers, preserveHeaderCase, and http10 currently configure both client and backend HTTP/1 protocol settings. In a future release these fields will be restricted to client connections only. The ApplyClientTrafficPolicyHTTP1SettingsToClientsOnly runtime flag can be used to opt in to the new behavior today. Migration guidance per field: - enableTrailers: use BackendTrafficPolicy.http1.enableTrailers for backend settings. - preserveHeaderCase: use BackendTrafficPolicy.http1.preserveHeaderCase for backend settings. - http10: this field currently forces explicit HTTP/1 on backend connections when enableTrailers and preserveHeaderCase are absent. In a future release it will no longer affect backend connections. Use BackendTrafficPolicy.http1: {} to force explicit HTTP/1 on a backend cluster. Note: accept_http_10 and default_host_for_http_10 are downstream-only fields in Envoy and have never had any effect on upstream clusters.
  • The following deprecated fields are now scheduled for removal in v1.11, which is stated in the API reference and in the DeprecatedField warning condition set on the resource’s status; migrate to their replacements before upgrading: targetRef on all policies (use targetRefs/targetSelectors), backendRef on backend clusters such as ext auth, ext proc and OIDC (use backendRefs), host/port on EnvoyProxy tracing, OpenTelemetry metrics and OpenTelemetry access log sinks (use backendRefs), host/port on the EnvoyGateway extension manager service (use fqdn/ip/unix), ClientTrafficPolicy enableProxyProtocol (use proxyProtocol) and headers.preserveXRequestID (use headers.requestID: PreserveOrGenerate), BackendTrafficPolicy compression (use compressor), and the consistent hash header field and Header type (use headers and Headers). Deprecation warnings are now also reported for backendRef on ext proc, JWT remote JWKS and OIDC providers, for host/backendRef on EnvoyProxy telemetry sinks, for the consistent hash header field and Header type, and, in the Envoy Gateway startup log, for host/port on extension manager and remote infrastructure provider services.

📝 Other Changes

  • Added per-phase tracing spans to the Gateway API and xDS translators, each recording the size of the input it processed, so that a slow translation can be attributed to a specific phase — listener processing, HTTP and gRPC route processing, the main policy types, EnvoyPatchPolicy JSON patches, extension server hooks, and xDS resource validation — instead of showing up as one opaque multi-second span.

📝 Upgrade Notes

  • We encourage all users to upgrade to v1.10.0 to take advantage of the new features, security improvements, and performance gains. For full details, see the Release Notes and updated Documentation.
  • Envoy Gateway v1.10 ships with Envoy Proxy v1.40 and keeps the bundled Gateway API CRDs at v1.6.1.
  • Review the Breaking Changes above before upgrading, especially if you use EnvoyPatchPolicy or an extension server that matches generated xDS names.
  • For upgrade instructions, see Helm or YAML.