# Istio 1.32: what changed and how to upgrade

> What Istio 1.32 changes, which upgrade notes can break a working mesh, and a revision-based canary upgrade from 1.30 or 1.31.

Published: 2026-11-12
Author: k8wiz Team
URL: https://k8wiz.com/articles/istio-1-32-upgrade-guide

Istio 1.32.0 was released on 9 November 2026. It supports Kubernetes 1.33 to 1.37 and is built on the Envoy 1.40 release branch. The same week brought patch releases 1.31.4 and 1.30.8, so if you run either of those lines you have a fresh, CVE-patched base to upgrade from.

Most of 1.32 is additive. The release adds new outlier detection and load balancing options, a stricter mode for JWT validation and experimental per-route authorization on Gateway API ingress. A few changes do alter behavior on upgrade, and one of them can silently disable an EnvoyFilter. This guide covers those first, then the features worth turning on, then the upgrade itself.

## Check your Kubernetes version first

The minimum supported Kubernetes version is now 1.33. Clusters still on 1.32 need a Kubernetes upgrade before Istio 1.32 goes on. Check the server version, then run `istioctl x precheck` with the 1.32 binary against each cluster, which looks for other problems that would block the install.

```bash
istioctl version
kubectl version
./istio-1.32.0/bin/istioctl x precheck
```

## Changes that can break a working mesh

The 1.32 upgrade notes list three items. Two are new defaults you will barely notice. The third only matters if you opt in to a new feature, but it fails without any warning, so it deserves the most attention.

### EnvoyFilter patches on the gateway RBAC filter

When you enable `ENABLE_GATEWAY_API_HTTPROUTE_AUTH` on istiod (more on that feature below), ingress gateways emit the HTTP filter for `ALLOW` authorization policies as `istio.authorization.allow` instead of `envoy.filters.http.rbac`. The `DENY` and `AUDIT` filters keep the old name. The rename lets route-scoped configuration address the `ALLOW` filter on its own.

Any EnvoyFilter that matches `subFilter.name: envoy.filters.http.rbac` in the `GATEWAY` context stops matching the `ALLOW` filter. `MERGE`, `REPLACE` and `REMOVE` patches simply stop applying. `INSERT_BEFORE` and `INSERT_AFTER` are worse, because the filter you were inserting is dropped from the chain instead of landing somewhere else. A gateway whose only policies use `ALLOW` has no filter called `envoy.filters.http.rbac` at all.

Istio logs nothing when a patch fails to match. To see it, set `PILOT_ENVOY_FILTER_STATS=true` on istiod and watch the `pilot_envoy_filter_status` metric. If you depend on such a patch, add a second EnvoyFilter that matches `istio.authorization.allow`, or leave the HTTPRoute authorization flag off. Find the candidates before you flip the flag.

```bash
kubectl get envoyfilter -A -o yaml | grep -n "envoy.filters.http.rbac"
```

### Client-side timeouts on Kubernetes API requests

Istio components used to wait forever for a Kubernetes API response. If a load balancer or proxy dropped the return path, the controller that issued the request stalled until the process restarted. In 1.32 every request has client-side deadlines, controlled by two environment variables.

| Variable | Default | Meaning |
| --- | --- | --- |
| `ISTIO_KUBE_HEADER_TIMEOUT` | `90s` | How long a request may wait for response headers |
| `ISTIO_KUBE_REQUEST_TIMEOUT` | `15m` | Total duration of a request with no deadline of its own |

The header timeout must stay above the API server's `--request-timeout`, which is 60 seconds by default. If you run an API server with a longer request timeout, raise `ISTIO_KUBE_HEADER_TIMEOUT` to match, or you will see requests cancelled that would have succeeded. Setting either variable to `0` restores the old unbounded behavior. Each timeout logs a warning with the method and path and increments `kube_client_deadline_fired_total`, which makes a good alert.

### Unhealthy endpoints now reach the proxies

`PILOT_AUTO_SEND_UNHEALTHY_ENDPOINTS` is on by default. A bug meant it skipped unready pods selected by a ServiceEntry and unhealthy health-checked WorkloadEntry resources. In 1.32 those endpoints appear in EDS with an unhealthy status instead of being left out. They carry an unhealthy status, so this mostly changes what you see. If you read endpoint lists with `istioctl proxy-config endpoint`, expect more entries than before.

## Smaller changes worth knowing

Several fixes change behavior you might have built on. The `source_app` and `destination_app` metric labels go back to using the pod's `app` label when a pod also has `app.kubernetes.io/name`. Since 1.29.1 they had used `app.kubernetes.io/name`, which was a regression. If your dashboards or alerts were written against the regressed values, some series will change name after the upgrade.

AuthorizationPolicy now matches HTTP methods case-insensitively, and the same goes for conditions on `request.headers[:authority]` and `request.headers[host]`. Both closed bypasses where a client changed the case of a value to slip past a rule. A policy that relied on case to tell requests apart (unlikely, but possible) now treats them the same.

`istioctl` now verifies istiod's certificate when it connects to xDS with a service account token, for example in `istioctl proxy-status` with `--xds-address`. Scripts that point it at an address whose name doesn't match the certificate will fail. Fix the address or `--authority`, or pass `--insecure` if you accept the risk. The istiod debug endpoints also stop matching `proxyID` as a substring, so short forms like `proxyID=istio-ingressgateway` must become the full `<pod>.<namespace>`.

Finally, the charts no longer set a `GOMAXPROCS` environment variable. Go 1.25 already sets it from the container's CPU limit, so this changes nothing unless you were patching that variable yourself.

## New features worth turning on

### Failure-rate outlier detection

Classic outlier detection in Istio counts consecutive errors. A pod that fails every other request never reaches five in a row, so it never gets ejected even at a 50 percent error rate. The new `failurePercentageThreshold` field ejects a host when its failure rate over the analysis `interval` reaches the threshold, whether the errors are consecutive or not. A second new field, `outlierDetectionHttpErrorCodes`, changes which status codes count as errors.

```yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: payments
  namespace: shop
spec:
  host: payments.shop.svc.cluster.local
  trafficPolicy:
    outlierDetection:
      interval: 10s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
      failurePercentageThreshold: 30
```

There is a catch the release notes don't mention. Istio sets the threshold and turns enforcement on, but it leaves Envoy's other failure percentage settings at their defaults. Envoy only runs this check when the cluster has at least 5 hosts and a host has received at least 50 requests in the interval. A service with three replicas, or a quiet one, will never eject anything by failure rate. For those, keep using `consecutive5xxErrors` or `consecutiveGatewayErrors`.

Note also that `outlierDetectionHttpErrorCodes` only redefines what counts as an error for the consecutive counters. Ejection is still driven by `consecutive5xxErrors` and `consecutiveGatewayErrors`.

### Load balancing on backend utilization

The new `backendUtilization` load balancer weighs endpoints by the utilization they report in ORCA load reports, using Envoy's `client_side_weighted_round_robin` policy. Endpoints reporting higher load get proportionally fewer requests. It only helps if your servers emit ORCA load reports. Endpoints that report nothing get equal weight, so turning it on for a service that sends no reports just gives you round robin.

```yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: ratings
  namespace: shop
spec:
  host: ratings.shop.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      backendUtilization:
        weightStabilizationPeriod: 10s
        weightExpirationPeriod: 3m
        weightUpdatePeriod: 1s
```

### Required JWTs

Until now, a RequestAuthentication with several `jwtRules` validated each token it found but let a request through with only one of them. The new `presence` field on a rule changes that. With `presence: REQUIRED`, a request missing that token gets a `401`, even when another rule is satisfied. The default stays `OPTIONAL`, so existing policies behave as before.

```yaml
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: api-tokens
  namespace: shop
spec:
  selector:
    matchLabels:
      app: api
  jwtRules:
  - issuer: "https://id.example.com"
    jwksUri: "https://id.example.com/.well-known/jwks.json"
    presence: REQUIRED
  - issuer: "https://scopes.example.com"
    jwksUri: "https://scopes.example.com/.well-known/jwks.json"
    fromHeaders:
    - name: x-scope-token
    presence: REQUIRED
```

When several rules are required, claims from all the validated tokens are available to AuthorizationPolicy through `request.auth.claims`.

### Per-route authorization on ingress (experimental)

With `ENABLE_GATEWAY_API_HTTPROUTE_AUTH=true` on istiod, an AuthorizationPolicy can target an `HTTPRoute` through `targetRefs`. Only `ALLOW` and `DENY` actions work, the policy must sit in the route's namespace, and only ingress gateways enforce it. Waypoints and sidecars ignore these policies.

Read the semantics carefully. A route-scoped `ALLOW` is evaluated together with the gateway's own `ALLOW` policies, so it widens access on that route rather than narrowing it. Route-scoped `DENY` policies add to the gateway's denials and can't override them. Remember the EnvoyFilter rename above before you enable it.

### Inbound retries you can tune

Sidecars have always retried inbound requests that were reset before reaching the application, with two attempts on `reset-before-request`. That was hardcoded. The new `defaultInboundHttpRetryPolicy` mesh config field lets you tune `attempts`, `retryOn` and `backoff`, and setting it to `{}` turns inbound retries off mesh-wide. It doesn't touch `defaultHttpRetryPolicy`, which governs outbound calls.

## Upgrade with a canary revision

The Istio docs recommend revision-based canary upgrades, and they allow a jump of two minor versions, so 1.30 to 1.32 works directly. In-place upgrades must step through each minor version. The steps below use `istioctl` and revision tags. Revision names can't contain dots, so 1.32.0 becomes `1-32-0`.

### Install the new control plane

Install 1.32 next to the existing control plane with its own revision. Pass the same overlay file you used for the current install so mesh config carries over.

```bash
./istio-1.32.0/bin/istioctl install --revision=1-32-0 -f my-overlay.yaml --skip-confirmation
kubectl get pods -n istio-system -l app=istiod -L istio.io/rev
```

Both istiod deployments now run side by side. Nothing has moved yet, since existing sidecars stay connected to the old control plane until their pods restart.

### Move one namespace first

If your namespaces use revision tags such as `prod-stable`, point a canary tag at the new revision and relabel one low-risk namespace to use it. If they still use `istio-injection=enabled`, remove that label, because it takes precedence over `istio.io/rev`.

```bash
istioctl tag set prod-canary --revision 1-32-0
kubectl label namespace checkout istio-injection- istio.io/rev=prod-canary --overwrite
kubectl rollout restart deployment -n checkout
istioctl proxy-status | grep "\.checkout "
```

Check that the restarted pods report version 1.32.0 and that error rates for that namespace look normal for a while before going further.

### Move the rest

Once the canary namespace looks healthy, move the stable tag. Every namespace labeled `prod-stable` now injects from 1.32 on its next restart, without any relabeling.

```bash
istioctl tag set prod-stable --revision 1-32-0 --overwrite
kubectl rollout restart deployment -n shop
kubectl rollout restart deployment -n payments
```

Restart or upgrade the gateways too. A gateway can be pinned to a revision of its own, so check which control plane each one reports in `istioctl proxy-status`.

### Remove the old control plane

When `istioctl proxy-status` shows nothing connected to the old revision, set the `default` tag to the new revision and uninstall the old one by its revision name (`1-31-4` in this example). If the old control plane was installed without a revision, uninstall it with its original install options instead.

```bash
istioctl tag set default --revision 1-32-0 --overwrite
istioctl uninstall --revision 1-31-4 -y
```

If something goes wrong before this step, rolling back is cheap. Point the tags back at the old revision, restart the affected namespaces and uninstall the canary. That safety net is the main reason to prefer revisions over an in-place upgrade, even for a release as quiet as this one.
