If you run Istio, Linkerd or Consul in front of your microservices, the mesh decides how traffic moves between pods. It does not decide which endpoints the internet can see. This article covers how service mesh traffic relates to externally visible endpoints, which mesh ports turn up most often in an external scan, and how to check that the attack surface you think you have is the one that’s actually there. The main payoff is catching exposures that internal dashboards miss, such as a load balancer someone added for convenience during a debugging session.
What a Service Mesh Changes About Your Attack Surface
A mesh adds a proxy next to every workload. That’s Envoy for Istio and Consul, and linkerd2-proxy for Linkerd. Each proxy listens on its own set of ports, and the control plane adds more. In a typical Istio cluster, a sidecar listens on 15001 and 15006 for redirected traffic, 15020 and 15090 for metrics, 15021 for health checks and 15000 for the Envoy admin interface. Istiod serves xDS configuration on 15010 (plaintext) and 15012 (TLS), and its webhook runs on 15017.
None of those ports should be reachable from the internet. Inside the cluster, the mesh handles mTLS, retries and authorization policies. At the edge, the usual rules still apply. Any Kubernetes Service of type LoadBalancer or NodePort, any pod with hostNetwork: true and any gateway listener can show up on a public IP. The mesh doesn’t block any of them.
Linkerd’s list is shorter but similar: 4143 for inbound proxy traffic, 4191 for proxy admin and metrics, and 8086 for the destination controller. Consul adds 8500 for its HTTP API and UI, 8502 for gRPC, 8301-8302 for Serf gossip, and by default 21000-21255 for the Envoy sidecars it manages.
Myth: mTLS Means Exposed Mesh Ports Don’t Matter
A common belief is that mesh traffic is encrypted and authenticated, so a reachable mesh port is harmless. That’s wrong for three reasons.
First, Istio’s default PeerAuthentication mode is PERMISSIVE, which accepts both mTLS and plaintext. Clusters that were migrated gradually often never switch to STRICT. Second, many mesh ports aren’t data-plane ports at all. The Envoy admin interface on 15000 can dump the full proxy configuration, including cluster names, upstream addresses and route tables. It can also change log levels and shut the proxy down. Istio binds it to localhost for this reason, but custom builds and port-forward workarounds sometimes expose it anyway.
Third, observability add-ons ship without authentication. Kiali on 20001, Jaeger on 16686, Grafana on 3000 and Prometheus on 9090 together show your whole service topology. An attacker who finds an exposed Kiali doesn’t need to run reconnaissance, because Kiali already shows the topology.
A Familiar Scenario: The Multi-Cluster Gateway
Here’s a pattern that comes up often. A platform team running Istio on EKS adds a second cluster in another region and follows the multi-primary guide. That means deploying an east-west gateway, which exposes port 15443 for SNI-based cross-cluster traffic. The sample manifest creates a Service of type LoadBalancer, and AWS gives it an internet-facing NLB because nobody added the internal-scheme annotation.
Six months later, an external scan finds 15443 open on a public IP that isn’t in any asset inventory. The port accepts TLS connections, and depending on how auto-passthrough is configured, it can route traffic to internal services that were never meant to be reachable from outside the VPC. Nothing in the mesh dashboards looked wrong, because from the mesh’s point of view everything was working as intended.
Older Istio installation profiles have the same issue on the ingress gateway. Earlier default profiles published 31400 and 15443 next to 80, 443 and the 15021 status port. Clusters upgraded in place often still have those Service definitions.
How to Audit Service Mesh Endpoints from the Outside
An experienced platform security engineer works from both directions: first what Kubernetes says is exposed, then what the internet can actually reach. A practical sequence:
1. List every Service of type LoadBalancer or NodePort across all namespaces with kubectl get svc -A, and flag any that belong to istio-system, linkerd, consul or observability namespaces.
2. Check cloud load balancer schemes. On AWS, look for service.beta.kubernetes.io/aws-load-balancer-scheme: internal. On Azure, look for azure-load-balancer-internal: “true”. On GCP, look for the internal load balancer type annotation.
3. Search for pods running with hostNetwork: true or hostPort, since these bind directly to node interfaces and skip Service-level controls.
4. Run an external scan against every public IP and node address, covering the full NodePort range 30000-32767 and the mesh ranges 15000-15090 and 21000-21255.
5. Compare the scan results with what the gateway manifests say should be open. Anything you can’t explain gets an owner and a deadline.
Step 4 matters most. Kubernetes describes the intended state, but security groups, cloud firewall rules and node-level iptables all decide what actually gets through. The guide on validating firewall rules with external port scans covers the method in more detail. Use service detection here, not just a port list. A banner showing Envoy or a TLS certificate with a SPIFFE URI in its SAN tells you right away that the port belongs to the mesh.
Common Mistakes in Mesh Edge Exposure
The most common mistake is exposing an observability tool to fix a short-term access problem. Someone changes Kiali or Grafana to type LoadBalancer because kubectl port-forward kept dropping during an incident. The incident ends, and the change stays in place for good.
The second mistake is trusting gateway resources instead of Services. Teams review their Istio Gateway and VirtualService objects carefully but never look at the Kubernetes Service in front of the gateway pod. The Gateway resource might only define 443, while the Service still forwards 15021, 15443 and an old TCP port to the internet.
The third is treating NodePorts as internal. In clusters where worker nodes have public IPs (common on cheaper managed offerings and self-managed setups on Hetzner or OVH), every NodePort is reachable from the internet unless a firewall blocks it. A scan that only covers well-known ports will miss all of them.
When the Standard Advice Doesn’t Apply
Bare-metal clusters using MetalLB hand out LoadBalancer IPs from a configured pool. If that pool is routed RFC 1918 space behind a corporate firewall, an internet scan won’t see the IPs, but an attacker inside the network will. In that case, run the same audit from a host inside the network as well.
Istio ambient mode, generally available since Istio 1.24, removes per-pod sidecars and moves traffic through ztunnel on port 15008 (HBONE). That gives you fewer ports to track per pod, but the node-level ztunnel becomes the component to watch. Teams of fewer than ten engineers running five or six services should ask whether they need a mesh at all. Every component you add brings more ports, and those ports need someone responsible for them. The wider topic is covered in port security in microservices architectures.
FAQ
Which Istio ports should be visible from the internet?
Normally only 80 and 443 on the ingress gateway. Port 15443 is needed only for multi-cluster setups and should be restricted to the other clusters’ IP ranges. The 15021 health port should be reachable only by the load balancer’s health checker.
Does Linkerd expose fewer external ports than Istio?
By default, Linkerd doesn’t ship an ingress gateway, so its control plane ports normally stay inside the cluster. The risk comes from whichever ingress controller you pair it with and from the dashboard extension, if someone exposes it.
How often should mesh edge endpoints be scanned?
Scan after every mesh upgrade, gateway change or new cluster rollout, and continuously in between. Helm chart upgrades can bring back Service ports you previously removed. The Kubernetes port security guide covers cluster-level checks.
Summary
A service mesh secures traffic between your services, not the boundary between your cluster and the internet. Gateway Services, east-west gateways, observability tools and NodePorts all end up on public IPs through ordinary configuration, and the mesh won’t warn you. Check your manifests against what an external scan finds, give every mesh port you expose an owner, and treat any reachable admin or telemetry port as an exposure to fix rather than background noise.
