envoyproxy / envoyproxy/gateway

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)

Open
#9,519 2 comments 0 reactions 0 assignees View on GitHub
kind/bug
Dominant language
Go
Stars
3k
Forks
864
Avg merge
2d 2h
Merged PRs (30d)
140

Description

*Description*:

We had an outage where running Envoy proxies (uptime: weeks) silently lost their downstream TLS certificates and stopped serving all HTTPS traffic, with **no self-healing** — recovery required manually restarting the proxy pods. We traced it end-to-end through the Envoy Gateway xDS-server logs and the proxy logs, and believe there are two Envoy Gateway–actionable gaps plus one upstream Envoy protocol gap involved.

**Failure mechanism (two-step):**

1. **Arming — control-plane restart.** The single envoy-gateway pod was rescheduled (node consolidation). The proxies reconnected to the new instance and re-established their delta ADS stream (`DeltaAggregatedResources`), declaring their cached resources via `initial_resource_versions`. The new control plane agreed the secrets were current and (correctly, per delta semantics) sent nothing. From this point, the stream's bookkeeping on both ends considers those secrets delivered — **but nothing on this stream has ever actually carried them**.

2. **Firing — a later listener/cluster-touching update.** ~90 minutes later, an unrelated node drain churned backend endpoints. Envoy Gateway regenerated and re-pushed Listener/Cluster resources (byte-different, semantically equivalent — the behaviour described in #8889). Envoy replaced the affected filter chains / clusters, which instantiated **new SDS providers** for the *same, unchanged* secret names. Because the shared delta subscription already lists those names as subscribed+delivered, **Envoy never sends a re-subscribe request on the wire** — the control-plane log confirms zero Secret-type requests from these proxies after the reconnect, while Listener/Cluster/Route requests cycled normally through ~25 snapshot versions. The new providers starve, the SDS `initial_fetch_timeout` (default **15s** — see below) fires, and the updated listener **activates without certificates**. Every TLS handshake fails from then on. Readiness/liveness (admin `/ready`) stay green, so nothing recovers automatically.

**Evidence (from the EG xDS-server log; timestamps UTC):**

Delta watch activity for one affected proxy (internal gateway, 2 replicas — both identical):

| time | type | snapshot version |
|---|---|---|
| 20:25:17 | Secret watch opened (after reconnect) | v2 — **never re-opened again** |
| 21:54:54 – 21:57:44 | Listener / Cluster / RouteConfiguration / CLA watches cycling | v8 → v33 |
| 22:10:57 | Secret watches from the **replacement** pod (manual restart) | v35 — 5 responses, recovery |

Proxy log — the timeouts fire exactly 15.0s after each of the two listener/cluster-touching snapshot versions (v13 @ 21:54:55, v31 @ 21:56:59), in pairs (two cert secrets):

```
[2026-07-16 21:55:10.672][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
[2026-07-16 21:55:10.672][1][warning][config] ... (same, second secret)
[2026-07-16 21:57:14.300][1][warning][config] ... initial fetch timed out for type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret
[2026-07-16 21:57:14.300][1][warning][config] ... (same, second secret)
```

After 21:57 the HTTPS access logs go completely silent (TLS handshakes fail before HTTP) until the proxy pods were manually restarted at 22:10. Established connections kept working briefly, which made the onset gradual and confusing. A second Gateway (different listeners) on the same control plane went through the identical evening *armed* but unharmed — its listener bytes happened to stay stable, so no new SDS providers were created. This matches the mechanism: the failure needs (armed stream) AND (byte-changed listener/cluster referencing a secret) AND (secret itself unchanged since reconnect).

**Why we're filing this against Envoy Gateway** (the root protocol gap is Envoy's — envoyproxy/envoy#13009, envoyproxy/envoy#36951, and the spec note in envoyproxy/envoy#22418 — but EG has three levers):

1. **SDS config sources generated by EG still default to `initial_fetch_timeout: 15s`.** #8069 addressed this for the bootstrap-level LDS/CDS config sources (`0s` = wait indefinitely), but the `sds_config` blocks embedded in generated listeners/clusters aren't covered. That 15s timeout is what converts "stale config, still serving" into "listener active without certs = hard outage": with `0s` the new filter chain would keep warming and the old one would keep serving. Setting `initial_fetch_timeout: 0` on generated SDS config sources (matching the #8069 rationale) would remove the outage mode of this bug. (#9373 appears to be another user hitting exactly this — same versions, same warning, and their config dump shows `0s` on lds/cds but the SDS warning still firing.)
2. **Byte-stable xDS output** (#8889) would shrink the firing surface dramatically — semantically-equivalent listener re-pushes on unrelated endpoint churn are what created the new SDS providers here.
3. **Tracking/escalating the Envoy-side fix**: a newly created SDS provider for an already-subscribed delta resource must either be fed from the local subscription cache or trigger an on-the-wire re-subscribe (per the xDS spec, servers must honor re-subscription even for resources they believe delivered).

*Repro steps*:

1. Install EG, one Gateway with an HTTPS listener terminating TLS from a cert secret; send steady traffic.
2. Restart / reschedule the envoy-gateway pod while the proxies keep running (they reconnect; secrets must NOT change afterwards — no cert renewal).
3. Cause a config update that byte-changes the listener or its clusters. In our case plain endpoint churn from a node drain was sufficient (cf. #8889); touching an HTTPRoute/policy that alters the listener also works.
4. Observe `initial fetch timed out for ...tls.v3.Secret` in the proxy 15s later; from that point new TLS handshakes fail (`ssl.connection_error` rises), while admin `/ready` stays healthy indefinitely. Restarting the proxy pod recovers (fresh stream, full initial fetch).

*Environment*:

- Envoy Gateway v1.8.1 (helm chart), Envoy `distroless-v1.38.1`
- Kubernetes v1.33 on AWS (Cluster API), NLB in front of the proxy Service
- 2 GatewayClasses / 2 Gateways (internal + external), merged-gateways off
- Delta ADS (`DeltaAggregatedResources`), default bootstrap otherwise

*Logs*:

Key excerpts inline above. We have the full control-plane log (including the complete per-version delta-watch timeline) and proxy logs preserved and can attach or share them on request.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the generated listener and cluster SDS config sources and the DeltaAggregatedResources subscription path; compare their initial_fetch_timeout handling with the bootstrap LDS/CDS changes described in #8069. Reproduce the restart, unchanged-secret, and byte-changing listener or cluster sequence, then verify that TLS remains available without the 15-second SDS timeout or manual proxy restart.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, go, helm, kubernetes
Domain
api, backend, infrastructure
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.