Introduction
When a service reports an outage, the first operational question is: Is the failure inside the Envoy sidecar (including the mTLS handshake) or inside the application container? Answering this quickly reduces mean‑time‑to‑identify (MTTI) and prevents wasted effort on the wrong tier. Telemetry that is generated as close to the failure point as possible—Envoy’s access logs, counters, and TLS‑freshness signals—provides the evidence needed to prove or disprove an Envoy‑ or mTLS‑path origin without relying on application‑level instrumentation that may be missing during an outage.
Access‑Log Fields for Telemetry
HTTP Request and Response Fields
Envoy’s access log can be formatted to emit request‑level details that directly map to the request lifecycle:
Field (Envoy % specifier) | Meaning | Why it helps isolate Envoy vs. app |
|---|---|---|
%START_TIME% | Request receipt timestamp (nanosecond precision) | Anchor for correlation windows |
%DURATION% | Total latency (request received → response sent) | High latency may indicate Envoy buffering or TLS handshake delay |
%REQ(:method)% | HTTP method | Confirms request reached Envoy |
%REQ(:path)% | Request path | Enables routing‑level filtering |
%RESPONSE_CODE% | HTTP status code returned by Envoy (or upstream) | 5xx from Envoy indicates Envoy‑generated error |
%UPSTREAM_HOST% | Selected upstream cluster endpoint | Shows whether Envoy selected a host |
%UPSTREAM_TRANSPORT_FAILURE_REASON% | Failure reason if upstream connection failed (e.g., connection_failed, no_healthy_upstream) | Direct evidence of Envoy‑side network failure |
%DOWNSTREAM_LOCAL_ADDRESS% | Listener address Envoy received the connection on | Useful for listener‑level debugging |
%DOWNSTREAM_REMOTE_ADDRESS% | Client address | Helps correlate with client‑side logs |
%REQUEST_HEADERS_BYTES% / %RESPONSE_HEADERS_BYTES% | Header sizes | Detects abnormal header injection or compression issues |
TLS Handshake and Certificate Fields
When mTLS is enabled, Envoy can add TLS‑specific fields to the same access‑log line:
Field (Envoy % specifier) | Meaning | Failure indication |
|---|---|---|
%DOWNSTREAM_TLS_SESSION_ID% | TLS session identifier | Empty or rapidly changing may indicate handshake failures |
%DOWNSTREAM_TLS_CIPHER% | Negotiated cipher suite | TLS_NULL_WITH_NULL_NULL or missing indicates handshake abort |
%DOWNSTREAM_TLS_SNI% | Server Name Indication presented by client | Mismatch with configured SNI can trigger reject |
%DOWNSTREAM_LOCAL_CERTIFICATE% | PEM‑encoded leaf cert Envoy presented | Absent → Envoy unable to load cert |
%DOWNSTREAM_PEER_CERTIFICATE% | PEM‑encoded peer cert presented by client | Absent → client did not send cert (mTLS failure) |
%DOWNSTREAM_PEER_CERT_SUBJECT% / %DOWNSTREAM_PEER_CERT_ISSUER% | Subject/Issuer of peer cert | Enables validation against expected SPIFFE ID |
%DOWNSTREAM_PEER_CERT_SERIAL% | Serial number of peer cert | Use for revocation checks |
%DOWNSTREAM_PEER_CERT_NOT_BEFORE% / %DOWNSTREAM_PEER_CERT_NOT_AFTER% | Validity period of peer cert | Direct freshness signal |
%DOWNSTREAM_TLS_ERROR% | TLS error string (if any) | Non‑empty → handshake abort reason (e.g., certificate_verify_failed) |
Example Access‑Log Configuration for Envoy
A minimal, production‑ready configuration that captures the fields above while keeping cardinality manageable (no per‑request headers or raw bodies). The log is written to /var/log/envoy/access.log and streamed via gRPC to a collector (e.g., Fluent Bit) for real‑time querying.
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: service_backend }
http_filters:
- name: envoy.filters.http.router
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /var/log/envoy/access.log
format: |
[%START_TIME%] "%REQ(:method)% %REQ(:path)% %PROTOCOL%" \
%RESPONSE_CODE% %RESPONSE_FLAGS% %DURATION% \
%UPSTREAM_HOST% %UPSTREAM_TRANSPORT_FAILURE_REASON% \
%DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% \
%DOWNSTREAM_TLS_SESSION_ID% %DOWNSTREAM_TLS_CIPHER% %DOWNSTREAM_TLS_SNI% \
%DOWNSTREAM_LOCAL_CERTIFICATE% %DOWNSTREAM_PEER_CERTIFICATE% \
%DOWNSTREAM_PEER_CERT_SUBJECT% %DOWNSTREAM_PEER_CERT_ISSUER% \
%DOWNSTREAM_PEER_CERT_SERIAL% %DOWNSTREAM_PEER_CERT_NOT_BEFORE% %DOWNSTREAM_PEER_CERT_NOT_AFTER% \
%DOWNSTREAM_TLS_ERROR%
- name: envoy.access_loggers.http_grpc
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.GenericAccessLog
transport_api_version: V3
common_config:
name: envoy_grpc_access_log
grpc_service:
envoy_grpc:
cluster_name: accesslog_cluster
Note – Envoy does not emit a metric for certificate expiration directly; the expiration must be derived from
%DOWNSTREAM_PEER_CERT_NOT_AFTER%(or from SDS). If the access‑log format omits the TLS fields, the mTLS path becomes invisible.
Counter Families for Outage Detection
Request and Response Counters
Envoy exports a hierarchical set of counters under the stats sink (Prometheus, StatsD, etc.). The most relevant families for distinguishing Envoy vs. app failures are:
| Counter family (Envoy metric) | Meaning | Interpretation for outage source |
|---|---|---|
upstream_rq_total{cluster="<name>"} | Total requests sent to upstream | Baseline traffic volume |
upstream_rq_2xx{cluster="<name>"} | 2xx responses from upstream | Healthy app path |
upstream_rq_5xx{cluster="<name>"} | 5xx responses from upstream | App‑generated errors (if Envoy successfully forwarded) |
upstream_rq_timeout{cluster="<name>"} | Requests timed out waiting for upstream | Could be Envoy timeout or app unresponsiveness; needs correlation with logs |
upstream_rq_reset{cluster="<name>"} | Requests reset by Envoy (e.g., due to circuit breaker) | Envoy‑side back‑pressure or overload |
listener_http_downstream_rq_total{listener="<name>"} | Total downstream requests received | Confirms Envoy received traffic |
listener_http_downstream_rq_5xx{listener="<name>"} | 5xx responses generated by Envoy (e.g., due to filter rejection) | Direct Envoy failure |
server_listener_downstream_cx_total{listener="<name>"} | Total downstream connections | Connection‑level load |
server_listener_downstream_cx_rx_bytes_total{listener="<name>"} | Bytes received on downstream side | Detects zero‑byte connections (possible TLS handshake stall) |
tls_handshake_total{listener="<name>"} | TLS handshakes attempted | Baseline for mTLS load |
tls_handshake_failed{listener="<name>"} | TLS handshakes that failed | Key signal: rise indicates mTLS path problem |
tls_handshake_success{listener="<name>"} | Successful TLS handshakes | Complement to failed |
ssl_connection_error_total{listener="<name>"} | SSL errors (alerts, protocol violations) | Granular TLS error classification |
Error and Failure Counters
Beyond the request families, Envoy provides error counters that surface internal processing problems:
| Counter | Meaning | Relevance |
|---|---|---|
http_conn_manager_total{code="503"} | 503 responses from HTTP connection manager (often due to no healthy upstream) | Envoy‑generated 503 → Envoy or upstream health |
http_conn_manager_total{code="502"} | 502 responses (bad gateway) | Envoy received invalid response from upstream |
cluster_upstream_cx_connect_fail_total{cluster="<name>"} | Failed TCP connections to upstream hosts | Network or Envoy‑side connection pool exhaustion |
cluster_upstream_cx_rx_bytes_total{cluster="<name>"} | Bytes received from upstream | Zero bytes while requests are sent → blackhole |
listener_udp_listener_datagram_rx_errors_total{listener="<name>"} | UDP datagram receive errors | Not relevant for HTTP/mTLS but useful for sidecar UDP telemetry |
Example Counter Configuration for Envoy
Envoy exposes these counters automatically when a stats_sink is configured. A minimal Prometheus scrape setup using the StatsD sink (replace with Prometheus exporter as needed):
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
stats_sinks:
- name: envoy.metrics
typed_config:
"@type": type.googleapis.com/envoy.extensions.statsd.v3.StatsdSink
tcp_cluster_name: statsd_cluster
prefix: envoy
If Prometheus is used via the envoy-prometheus exporter, the same counters appear under the envoy namespace.
Note – Envoy does not expose a counter for “certificate expired” or “certificate about to expire”. Those must be derived from the access‑log TLS fields or from an external secret‑management health check.
Certificate Freshness Signals
Certificate Expiration and Renewal Monitoring
The mTLS path is healthy only if both sides present a valid, non‑expired certificate. Envoy provides the peer certificate’s notAfter timestamp via the access‑log field %DOWNSTREAM_PEER_CERT_NOT_AFTER%. To turn this into a usable signal:
- Parse the ISO‑8601 timestamp from each access‑log line (or from a periodic SDS dump).
- Compute
time_to_expiry = notAfter - now. - Emit a gauge (e.g.,
mtls_peer_cert_time_to_expiry_seconds) to a monitoring system.
If the gauge drops below a threshold (e.g., 86 400 s for 1 day), an alert fires indicating an impending cert expiry.
Certificate Validation and Verification Signals
Beyond expiration, validation failures are captured by:
%DOWNSTREAM_TLS_ERROR%– contains strings likecertificate_verify_failed,unsupported_certificate,certificate_required.tls_handshake_failedcounter – increments when the TLS handshake aborts for any reason, including cert validation.ssl_connection_error_total{reason="certificate_unknown"}– fine‑grained error reason if the listener is configured withsslstats.
Example Certificate Freshness Signal Configuration
Assuming a sidecar Fluent Bit agent tails Envoy’s access log and emits metrics to Prometheus via the prometheus_exporter output:
[INPUT]
Name tail
Path /var/log/envoy/access.log
Parser envoy_access_log
Tag envoy.access
[PARSER]
Name envoy_access_log
Format regex
Regex ^\[(?<start_time>[^\]]+)\] "(?<method>[^ ]+) (?<path>[^ ]+) (?<proto>[^ ]+)" (?<status>\d+) (?<flags>\S+) (?<duration>\d+) (?<upstream_host>\S+) (?<upstream_fail>\S+) (?<downstream_local>\S+) (?<downstream_remote>\S+) (?<tls_session_id>\S+) (?<tls_cipher>\S+) (?<tls_sni>\S+) (?<local_cert>\S+) (?<peer_cert>\S+) (?<peer_subject>\S+) (?<peer_issuer>\S+) (?<peer_serial>\S+) (?<not_before>\S+) (?<not_after>\S+) (?<tls_error>\S+)$
Time_Key start_time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
[OUTPUT]
Name prometheus_exporter
Match envoy.access
Listen 0.0.0.0
Port 2020
Metrics mtls_peer_cert_time_to_expiry_seconds
# Add additional metric definitions as needed
The parser extracts the not_after field; a downstream Fluent Bit filter (or a Prometheus exporter script) can compute the time‑to‑expiry gauge and expose it for alerting.
By combining these access‑log fields, counter families, and certificate‑freshness signals within a tight correlation window (e.g., 5‑second buckets), operators can quickly determine whether an outage originates in Envoy or the mTLS path, independent of application logs or traces that may be missing during a failure.