Skip to content

TLS handshake failures when updating from 1.9.0 to 1.9.1 (related to #9519) #9918

Description

@lboynton

Description:
During an upgrade from Envoy Gateway 1.9.0 to 1.9.1, we noticed a spike in TLS handshake errors. Root cause is the same class of bug as #9519 — pod readiness/liveness never checks whether SDS has actually delivered certificates — but via a different trigger: a brand-new pod's very first SDS fetch on cold start, racing a mass simultaneous pod restart, rather than #9519's stale delta-ADS re-subscription on a long-running proxy. Filing separately since the trigger and recovery profile are meaningfully different, but linking as clearly related.

Once the upgrade rollout had finished the TLS handshake errors went away.

Failure mechanism:

  1. Trigger — routine version upgrade. We applied the Envoy Gateway 1.9.0 → 1.9.1 update to our cluster. This rolled all proxy pods to a new ReplicaSet.
  2. Mass simultaneous cold start. All new pods started at once, each needing to fetch ~53 distinct SDS secrets (TLS certs for our configured Gateways/HTTPRoutes) from the envoy-gateway control plane simultaneously — i.e. ~477 concurrent initial SDS fetches hitting the control plane at once.
  3. Readiness passes independent of SDS state. Per internal/xds/translator/listener_ready.go and internal/xds/filters/wellknown.go, the readiness listener uses the stock health_check HTTP filter with PassThroughMode: false — it answers purely from the Envoy process's own liveness flag, with no dependency on LDS/SDS convergence. So every pod passed Kubernetes readiness almost immediately, well before its 53 SDS secrets had loaded, and was added to the Service endpoints.
  4. Real traffic hits listeners with SDS still outstanding. With the mass fetch contending against the control plane, a meaningful number of those initial fetches took long enough to hit the (correctly, per 1.9.1's fix) reverted 15s initial_fetch_timeout for SDS. Per GrpcSubscriptionImpl::onConfigUpdateFailed (Envoy core), a timed-out initial fetch still unblocks the init manager — so the listener becomes "active" without ever having received a valid cert for the affected filter chain. Live TLS handshakes against that pod failed until the pod's retry succeeded.
  5. Self-healing, unlike Proxies permanently lose TLS after a listener/cluster update that follows an Envoy Gateway restart — new SDS subscriptions are never fetched over delta ADS (15s initial_fetch_timeout activates listeners without certs) #9519. Each pod's own retry-after-timeout eventually succeeded once control-plane contention eased.

Repro steps:

  1. Install Envoy Gateway v1.9.1+ with a Gateway/HTTPRoute configuration referencing a non-trivial number of TLS secrets — we saw this reliably with ~50 per proxy pod; a handful may not create enough control-plane fetch contention to lose the race, so scale up if it doesn't reproduce at low secret counts.
  2. Send steady TLS traffic to the Gateway's listener(s) so there's something to observe failing.
  3. Trigger a rollout of all proxy pods at once so many pods request their initial SDS secrets from the control plane at the same moment.
  4. While the rollout is in progress, in parallel:
  5. Tail the new pods' logs for gRPC config: initial fetch timed out for type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret.
  6. Poll each new pod's readiness endpoint (the port/path configured via bootstrap.EnvoyReadinessPort/EnvoyReadinessPath, exposed at the Kubernetes readiness probe) — it will report healthy immediately, before and during the SDS timeout warnings.
  7. Send real TLS requests directly at each new pod's Service IP (bypassing any external LB retry/failover) as it becomes "Ready," to observe handshake failures independent of upstream retry logic masking the issue.
  8. Confirm recovery: once a pod's SDS retry succeeds, handshakes against it succeed again, without a pod restart — this distinguishes it from Proxies permanently lose TLS after a listener/cluster update that follows an Envoy Gateway restart — new SDS subscriptions are never fetched over delta ADS (15s initial_fetch_timeout activates listeners without certs) #9519, where recovery required a manual restart.
  9. To isolate this from Proxies permanently lose TLS after a listener/cluster update that follows an Envoy Gateway restart — new SDS subscriptions are never fetched over delta ADS (15s initial_fetch_timeout activates listeners without certs) #9519's trigger, do not restart the envoy-gateway control-plane pod or otherwise disturb an already-running proxy's stream — this repro is specifically about a fresh pod's very first fetch, not a re-subscription on a warm one.

Environment:
Envoy Gateway 1.9.1

Logs:
Proxy log (representative — reconstructed from Envoy's source-level format at source/extensions/config_subscription/grpc/grpc_subscription_impl.cc:130 plus our aggregated log-count evidence; we have counts per pod via our log platform, not a saved verbatim capture of a single raw line):

[2026-08-28 14:08:2X.XXX][1][warning][config] [source/extensions/config_subscription/grpc/grpc_subscription_impl.cc:130] gRPC config: initial fetch timed out for type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret

This line repeated exactly 53 times in immediate succession per new pod — once per configured TLS secret — within the first couple of seconds after each pod's readiness probe had already started reporting healthy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions