OMNI52
Istio Quick Reference OMNI52™ GmbH
New in Istio 1.31GCP-hosted artifacts go dark in December 2026 · TCPRoute CRDs below Gateway API v1.6.0 are ignored · Unhealthy endpoints are now sent to EnvoyAll changes →

Istio service mesh
on a single sheet.

A dense reference for senior platform engineers and SREs. Traffic management, security, observability, performance, multi-cluster and the failure modes you actually hit in production. No beginner slides.

Preview (3 pages, A4 landscape)

Cheatsheet page 1: architecture, traffic management, security
Cheatsheet page 2: observability, performance, multi-cluster, diagnostics
Cheatsheet page 3: Gateway API, release & support, anti-patterns, contact & license

Download the PDF

Direct download, no email required. CC BY-SA 4.0, copying, printing and redistributing is explicitly allowed as long as the attribution stays visible.

Istio Quick Reference (PDF, ~100 KB)

What’s inside

Traffic

Gateway, VirtualService, DestinationRule, ServiceEntry, Sidecar scoping, with outlier detection and connection-pool defaults.

Security

PeerAuthentication, AuthorizationPolicy with JWT claims, RequestAuthentication, SPIFFE identity, migration pattern for mTLS STRICT.

Observability

Telemetry API, the metrics that matter from the Envoy stack, tracing header propagation, OTel logging with filters.

Performance

Sidecar sizing from the Istio benchmark, Pilot tuning for push storms, ambient mode (GA since 1.24) as the sidecar-less option.

Multi-cluster

Primary-remote, multi-primary, external control plane. Trust-domain setup and endpoint discovery in one block.

Diagnostics

istioctl tools in the order you reach for them during an incident. Typical UF/UC/NR/UO response flags with root cause.

Full-text cheatsheet

The same content as the PDF, to read along, search and copy the YAML snippets directly. As of: Istio 1.31 (v1.31 · 2026.10).

Architecture

Datapath & components

N-S: Client → ingress GW → sidecar(dst) → app. E-W: App → sidecar(src) → sidecar(dst) → app, two Envoy hops (ambient: ztunnel → ztunnel, +waypoint for L7). mTLS handshake only on connect.

istiod: Pilot/Citadel/Galley combined, xDS push + CA. Data plane: Envoy sidecar (mutating webhook) or ambient ztunnel (Rust, L4) + optional waypoint (Envoy, L7). Gateway: dedicated Envoys, Deployment + LB service.

Envoy version = Istio version + 8. Istio 1.31 → Envoy 1.39.

Install profiles

default (prod), minimal (istiod only), ambient (sidecar-less), remote (MC workload cluster), demo (noisy). Canary via --revision=1-31-1.

istioctl x precheck
istioctl install --set profile=default \
  --set hub=registry.example.com/istio  # mirror

Traffic Management

Gateway

Binds ports/hosts/TLS at the edge proxy. servers[].port + tls.mode: SIMPLE, MUTUAL, PASSTHROUGH, ISTIO_MUTUAL. credentialName points to a Secret in the same namespace as the gateway pod.

apiVersion: networking.istio.io/v1
kind: Gateway
metadata: {name: web-gw, namespace: istio-system}
spec:
  selector: {istio: ingressgateway}
  servers:
  - port: {number: 443, name: https, protocol: HTTPS}
    hosts: ["www.istio-quickref.de"]
    tls:
      mode: SIMPLE
      credentialName: web-cert

VirtualService

Routing rules. Binds hosts to gateways (or mesh for east-west). Match order is significant, the first matching rule wins. Header names must be lowercase; exact/prefix matches on header values and URI are case-sensitive (URI: ignoreUriCase: true, regex: (?i)).

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata: {name: reviews}
spec:
  hosts: [reviews]
  http:
  - match:
    - headers: {end-user: {exact: jason}}
    route:
    - destination: {host: reviews, subset: v2}
  - route:
    - destination: {host: reviews, subset: v1}
      weight: 90
    - destination: {host: reviews, subset: v3}
      weight: 10
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream
    timeout: 10s

DestinationRule

Subsets (versions) + traffic policy (LB, connection pool, outlier detection, TLS). Takes effect only after VirtualService routing. host: FQDN or short name in the DR’s namespace. 1.31+: meshConfig.defaultTrafficPolicy sets a mesh-wide connectionPool/outlierDetection baseline; a DR block overrides it per block.

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: {name: reviews}
spec:
  host: reviews
  trafficPolicy:
    connectionPool:
      tcp: {maxConnections: 100}
      http:
        http1MaxPendingRequests: 64
        http2MaxRequests: 1000
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 60s
    loadBalancer:
      consistentHash:
        httpHeaderName: x-user-id
  subsets:
  - name: v1
    labels: {version: v1}
  - name: v2
    labels: {version: v2}

ServiceEntry

Pull external hosts into the mesh registry (DB, SaaS, REST APIs). MESH_EXTERNAL + DNS resolution is the clean default combo. With outboundTrafficPolicy: REGISTRY_ONLY, egress without a ServiceEntry lands in BlackHoleCluster. 1.31: mode ALLOW_ANY_DYNAMIC_DNS (MeshConfig only, not settable per Sidecar resource; sidecar proxies only) resolves unknown plaintext-HTTP hosts via Envoy DFP; TLS/TCP stays PassthroughCluster.

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata: {name: stripe-api}
spec:
  hosts: ["api.stripe.com"]
  ports:
  - {number: 443, name: https, protocol: HTTPS}
  resolution: DNS
  location: MESH_EXTERNAL

Sidecar

Limits what a workload sees of the mesh registry, critical for memory footprint and push latency in large clusters. Default: every sidecar gets config for all services. 1.31+: ~ns/* subtracts from the import set (*/* + ~legacy/*).

apiVersion: networking.istio.io/v1
kind: Sidecar
metadata: {name: default, namespace: prod}
spec:
  egress:
  - hosts:
    - "./*"             # own namespace only
    - "istio-system/*"
    - "shared/*"

Security

PeerAuthentication

mTLS mode between sidecars. STRICT, PERMISSIVE, DISABLE. Scope: mesh (in istio-system), namespace, or workload via selector. Migrating to STRICT: first mesh-wide PERMISSIVE, then switch namespace by namespace.

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: {name: default, namespace: prod}
spec:
  mtls: {mode: STRICT}
---
# Port exception (spec only, own name): NLB check
spec:
  selector: {matchLabels: {app: legacy}}
  mtls: {mode: STRICT}
  portLevelMtls:
    8080: {mode: PERMISSIVE}

AuthorizationPolicy

L7 access control. Order: CUSTOM (ext authz) → DENY → ALLOW (default action); AUDIT only logs. No ALLOW policy on a workload = allow all. spec: {} (ALLOW, no rules) denies everything; rules: [{}] matches every request. Rules OR, fields in a rule AND. 1.31+: source.trustDomains.

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: {name: reviews-allow, namespace: prod}
spec:
  selector: {matchLabels: {app: reviews}}
  action: ALLOW
  rules:
  - from:
    - source:
        principals:
        - cluster.local/ns/prod/sa/productpage
    to:
    - operation:
        methods: [GET]
        paths: ["/reviews/*"]
    when:
    - key: request.auth.claims[groups]
      values: ["reader", "admin"]

RequestAuthentication (JWT)

Validates the JWT and fills request.auth.* for the AuthorizationPolicy. Important: without an additional DENY policy with notRequestPrincipals=["*"], unauthenticated requests can still get through.

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata: {name: jwt, namespace: prod}
spec:
  selector: {matchLabels: {app: api}}
  jwtRules:
  - issuer: "https://auth.example.com"
    jwksUri: "https://auth.example.com/jwks"
    audiences: ["api.example.com"]
    forwardOriginalToken: true

Identity

SPIFFE URI spiffe://<trust>/ns/<ns>/sa/<sa>. Trust domain defaults to cluster.local; all clusters of one mesh share it (or list each other in trustDomainAliases), only the cluster name is unique. Workload certs: 24 h TTL (SECRET_TTL), istio-agent renews at half-life (SECRET_GRACE_PERIOD_RATIO=0.5), issued by the istiod CA.

Observability

Telemetry API

Metrics/logs/traces per workload or namespace. Replaces EnvoyFilter telemetry mods and the old values.telemetry.v2.* settings.

apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata: {name: trace-prod, namespace: prod}
spec:
  tracing:
  - providers: [{name: tempo}]
    randomSamplingPercentage: 5.0
  metrics:
  - providers: [{name: prometheus}]
    overrides:
    - match: {metric: REQUEST_COUNT}
      tagOverrides:
        request_protocol: {operation: REMOVE}
  accessLogging:
  - providers: [{name: otel}]
    filter:
      expression: "response.code >= 400"

Metrics & tracing

RED counter: istio_requests_total, latency: istio_request_duration_milliseconds_bucket. The reporter label = source/destination, in dashboards always aggregate on destination (otherwise double counting).

Envoy creates spans and trace headers but cannot link an inbound request to the app’s outbound calls. The app must copy incoming trace headers (x-request-id, x-b3-*, traceparent) onto its outgoing requests.

Performance & Tuning

Sidecar sizing

Default requests 100m / 128Mi, limits 2 CPU / 1Gi: not prod-grade at high RPS.

Istio benchmark (1.24, 1000 RPS, 1 KB, 2 workers): sidecar ≈ 0.20 vCPU + 60 MB, waypoint ≈ 0.25 vCPU + 60 MB, ztunnel ≈ 0.06 vCPU + 12 MB.

Memory scales with the config the proxy holds: Sidecar/exportTo trim it.

Pilot tuning

PILOT_PUSH_THROTTLE: default 0 = auto, min(15 + 5 × GOMAXPROCS, 100). PILOT_DEBOUNCE_AFTER: default 100 ms. PILOT_DEBOUNCE_MAX: default 10 s. On push storms (many pod restarts) raise debounce; for faster convergence raise throttle together with istiod CPU.

Ambient mode (GA since 1.24)

No sidecar injection. ztunnel: node DaemonSet, L4 mTLS (HBONE). Waypoint: optional Envoy for L7, per namespace, service or pod (istio.io/use-waypoint). Opt-in: label istio.io/dataplane-mode=ambient (namespace or pod). 1.31: weighted waypoint canaries (istio.io/use-waypoint-canary + ...-canary-weight).

Saves RAM at high pod counts, costs complexity when debugging.

Jobs & CronJobs (sidecar lifecycle)

App before proxy ready → connection refused. App done, sidecar still running → the job hangs.

Native sidecar (K8s 1.33+ GA): proxy as an initContainer (restartPolicy: Always), proper lifecycle. Default since Istio 1.27: ENABLE_NATIVE_SIDECARS=auto, native only when every node runs kubelet ≥ 1.33 (mixed clusters during a node upgrade get the classic sidecar).

Per pod (1.24+, beats the mesh setting): sidecar.istio.io/nativeSidecar: "false" forces the classic sidecar, "true" forces native.

Pre-native: holdApplicationUntilProxyStarts against the start race, trap with POST :15020/quitquitquit on EXIT against shutdown hangs (fires on crash/signal too, not only on success).

# Pre-native fallback (without native sidecars):
metadata:
  annotations:
    proxy.istio.io/config: |   # start race
      { "holdApplicationUntilProxyStarts": true }
spec:
  containers:
  - name: worker
    command: ["/bin/sh","-c"]
    args:
    - |
      trap 'curl -fsS -XPOST localhost:15020/quitquitquit||true' EXIT
      ./run-task

Graceful drain: EXIT_ON_ZERO_ACTIVE_CONNECTIONS

Classic sidecar: on SIGTERM the sidecar only drains for terminationDrainDuration (default 5 s), then hard-exits, long-lived connections (gRPC streams, WebSockets, DB pools) are cut mid-flight (503/reset on rollout/scale-down).

Fix: EXIT_ON_ZERO_ACTIVE_CONNECTIONS=true (via proxyMetadata or proxy.istio.io/config): pilot-agent polls active Envoy connections and stops the proxy as soon as they reach 0 instead of on a fixed timer.

Caveat: if a connection hangs (client never closes), the proxy blocks until terminationGracePeriodSeconds → SIGKILL. Raise the grace period accordingly. Mesh-wide via meshConfig.defaultConfig.proxyMetadata.

Native sidecar (default since 1.27): kubelet stops the proxy only after the app has exited, so the race mostly applies to the classic mode.

Multi-Cluster

Topologies & setup

Primary-remote: one istiod, several workload clusters. Multi-primary: istiod per cluster, shared root CA. External control plane: istiod on the outside.

Required: common root CA, shared trustDomain (or trustDomainAliases), unique cluster name, endpoint discovery via istioctl create-remote-secret. Multi-network only: label topology.istio.io/network on istio-system.

istioctl create-remote-secret \
  --context=cluster-b \
  --name=cluster-b \
  | kubectl apply --context=cluster-a -f -

Diagnostics

First istioctl tool during an incident

istioctl proxy-status shows the sync state of every sidecar. SYNCED = Envoy ACKed the last push, NOT SENT = istiod has nothing to send (often normal, e.g. no routes), STALE = sent but not ACKed → network istiod↔proxy or an Istio bug: check the istiod logs.

istioctl proxy-status
istioctl proxy-config routes <pod>.<ns> -o json
istioctl proxy-config clusters <pod>.<ns>
istioctl proxy-config listeners <pod>.<ns>
istioctl proxy-config endpoints <pod>.<ns>
istioctl proxy-config secrets <pod>.<ns>
# Static lint: VirtualService/DR conflicts etc.
istioctl analyze -n prod
# Bug report (not anonymised; secrets only w/ --full-secrets)
istioctl bug-report

Live log of a sidecar

istioctl proxy-config log <pod> --level debug sets the log level live without a pod restart. Component-specific, e.g. --level rbac:debug,jwt:debug. After diagnosis, set it back to warning.

Common failure modes

UF (503): upstream connection failure: wrong targetPort, or mTLS conflict (DR tls.mode DISABLE vs. STRICT server).
UC (503): upstream terminated the connection: keep-alive/idle-timeout race, or pod-IP call without a route at the server sidecar.
NR: no route configured: VirtualService host/match order or DR subset (404 when no path matches).
UO (503): circuit breaker, connectionPool limits of the DR.

OUTPUT_CERTS scraping: read: connection reset by peer

App-originated mTLS (Prometheus/Alloy with proxyMetadata.OUTPUT_CERTS) gets an RST although the target is PERMISSIVE.

Cause: the client sidecar wraps the app mTLS in a second ISTIO_MUTUAL tunnel, typically via a DR host: "*.local" + exportTo: ["*"] (STRICT migration helper) that also matches direct pod-IP calls. 15006 strips only the outer layer, the app port receives TLS bytes. PERMISSIVE only decides inbound.

Fix: excludeOutboundPorts (or includeOutboundIPRanges: "") on the scraper pod, or a more specific DR with tls.mode: DISABLE for the target. 1.31+ (opt-in): ENVOY_SECURE_METRICS_PORT / ENVOY_SECURE_MERGED_METRICS_PORT add mTLS scrape listeners.

# client wraps? sni=outbound_* + alpn istio* = proof
istioctl pc cluster <pod>.<ns> --fqdn <target> -o json \
  | jq '.[].transportSocket.typedConfig
        |{sni, alpn: .commonTlsContext.alpnProtocols}'

Gateway API (v1, kubernetes-sigs)

Status in 1.31

Istio 1.31 builds on Gateway API v1.6.0 (Gateway, HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, ListenerSet), an equal alternative to Gateway/VirtualService. CRDs are not bundled: 1.31 expects v1.6.0 (TCPRoute minimum v1.6.0, TLSRoute v1.5.0); CRDs below their minimum are silently not processed, istioctl analyze reports IST0176. New projects: Gateway API; networking.istio.io stays, both coexist.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: web, namespace: istio-system}
spec:
  gatewayClassName: istio
  listeners:
  - name: https
    hostname: www.istio-quickref.de
    port: 443
    protocol: HTTPS
    allowedRoutes: {namespaces: {from: All}} # prod: Selector
    tls:
      certificateRefs:
      - {name: web-cert}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: site, namespace: web}
spec:
  parentRefs: [{name: web, namespace: istio-system}]
  hostnames: ["www.istio-quickref.de"]
  rules:
  - matches: [{path: {type: PathPrefix, value: /}}]
    backendRefs: [{name: nginx, port: 80}]

Release & Support

Cadence & Support Window

Roughly one minor per quarter. A minor is supported until six weeks after the N+2 release, then no more security or critical-bug back-ports.

1.31: released 31 Aug 2026, latest patch 1.31.1 (21 Sep 2026), K8s 1.32–1.37.
1.30: latest patch 1.30.5 (21 Sep 2026), supported until six weeks after 1.32.
1.29: EOL on 12 Oct 2026 (latest 1.29.8).
1.28: EOL since 28 Jun 2026.

Artifacts from 1.31: images only on docker.io/istio, charts on blob.istio.io and ghcr.io/istio/release/charts; gcr.io/istio-release and registry.istio.io go dark in Dec 2026 (scream tests 13 Oct, 17 Nov). 1.31.1+ signed with istio-key-v2.pub. 1.30 defaults to registry.istio.io/release: set hub=docker.io/istio (or a mirror) even without upgrading.

In-place upgrades one minor at a time; canary (revision) upgrades in sidecar mode may skip one (N→N+2); ambient (ztunnel/CNI tolerate only N+1) goes step by step. More than two minors behind means unpatched CVEs.

Anti-Patterns

What you should not do

Default sidecar resources in prod: 1Gi limit OOMs in large meshes without Sidecar scoping, 100m CPU request under-reserves at high RPS.
Mesh VirtualService without a Sidecar resource: every sidecar gets every rule, push storm.
STRICT without migration: non-injected workloads break (jobs, external health checks).
EnvoyFilter as the default tool: reach for the Telemetry API, AuthorizationPolicy, TrafficExtension (1.30+, v1alpha1; Wasm + Lua) or WasmPlugin first. EnvoyFilter breaks between minors.
Multi-cluster without a shared root CA: mTLS fails.

License & redistribution

CC BY-SA 4.0. You may copy, redistribute, print and quote this cheatsheet in your own materials. Condition: the attribution „Istio Quick Reference, OMNI52 GmbH, istio-quickref.de“ stays visible, and derivative works are licensed under the same terms (share-alike).

Not allowed: using the logo or trademarks, or creating the impression that the content originates from you or that OMNI52 GmbH sponsors the reuse.

Full license text: creativecommons.org/licenses/by-sa/4.0/.

Also from OMNI52:
istio-cheatsheet.de, Istio in depth (German)
service-mesh-cheatsheet.de, Istio + Linkerd + Cilium compared
cilium-cheatsheet.de, eBPF networking
kubernetes-cheatsheet.de, Kubernetes Core

Istio is a trademark of The Linux Foundation. OMNI52™ is a trademark of OMNI52 GmbH (filed, not yet registered). This website is operated by OMNI52 GmbH and is not affiliated with, endorsed by, or sponsored by The Linux Foundation, the CNCF, or the Istio project. “Istio” is used in a nominative/descriptive sense to indicate the technology this reference describes.