An Ingress controller and a service mesh both put proxies in front of Kubernetes workloads, but one secures traffic entering the cluster and the other secures traffic between workloads, and treating either as a substitute for the other leaves a gap an attacker can walk through. The Ingress controller (or its successor, a Gateway API implementation) terminates client TLS at the edge, routes HTTP by host and path and is the cluster's front door. A service mesh gives every workload a cryptographic identity, encrypts pod-to-pod traffic with mutual TLS and enforces which workload may call which. This guide compares them by threat model, architecture, failure modes and controls, explains where Gateway API now spans both, covers the Ingress NGINX retirement as a decision trigger and ends with decision criteria and a worked scenario. It is for platform engineers, Kubernetes security leads and architects. Where mesh projects are named, they are listed alphabetically (Istio, Linkerd), and naming is illustrative, not a recommendation.
Key takeaways
- North-south and east-west are different problems. The edge handles untrusted clients, public certificates and request filtering; the mesh handles authenticated service identity and authorisation inside the cluster. Most production clusters with sensitive data need controls for both.
- Ingress ends TLS at the edge by design. The Kubernetes documentation states that the Ingress resource assumes TLS termination at the ingress point, with traffic to the Service and its Pods in plaintext. Encryption beyond the edge needs a mesh, re-encryption to backends or application TLS.
- A mesh gives identity, not just encryption. Workload certificates bound to Kubernetes service accounts let you write policies such as "only the orders service account may call payments", which network addresses cannot express reliably.
- Gateway API is the convergence point. The Ingress API is frozen; Gateway API covers north-south through Gateways and Routes, and its mesh work (GAMMA) attaches the same Route types to Services for east-west traffic, in the Standard Channel since v1.1.0.
- Defaults decide security. Permissive mTLS modes, unmeshed ingress controllers and missing allow policies are the common reasons a deployed mesh enforces nothing.
The short answer
If your question is "how do external clients reach my services safely", you need an edge proxy: an Ingress controller today, or a Gateway API implementation for new work. If your question is "how do I know which workload is calling my service, and stop the ones that should not", you need workload identity and east-west authorisation, which a service mesh provides without code changes. Kubernetes NetworkPolicy is the lighter alternative for east-west segmentation by label and namespace, without identity or encryption (see the Kubernetes Network Policy Guide).
The edge and the mesh then meet at one critical seam: the ingress proxy is itself a client of the mesh. If it is not meshed, its traffic to backends arrives as plaintext from an unidentified source, and strict mesh policy either blocks it or has been relaxed to let it through.
Side by side
| Dimension | Ingress controller or Gateway | Service mesh |
|---|---|---|
| Traffic direction | North-south: clients outside the cluster to services | East-west: workload to workload (and optionally egress) |
| Who connects | Untrusted or partly trusted clients, browsers, partners | Workloads with mesh-issued identities |
| TLS | Server-side TLS with public or enterprise certificates from kubernetes.io/tls Secrets; Ingress supports a single TLS port, 443 | Mutual TLS with short-lived workload certificates issued by the mesh certificate authority |
| Identity | Client identity optional (OAuth2/OIDC at the edge, client certificates, API keys) | Workload identity bound to the Kubernetes ServiceAccount |
| Routing | Host, path, headers (Gateway API), weighting | Service-to-service retries, timeouts, traffic splitting |
| Authorisation | Edge filtering, rate limits, WAF integration, external auth | Per-workload allow and deny policies on source principal, namespace, method and path |
| Protocols | Ingress: HTTP and HTTPS only; Gateway API: HTTP, gRPC and further route types | All TCP between meshed pods (Linkerd documents this as its default); L7 features for HTTP and gRPC |
| Kubernetes API | Ingress and IngressClass (frozen); GatewayClass, Gateway, HTTPRoute, GRPCRoute | Mesh custom resources; Gateway API Routes attached to Services (GAMMA) |
| Typical owner | Platform or network team, cluster operator | Platform team with security; application teams write policy |
| Main risk if absent | Ad hoc NodePort and LoadBalancer exposure, unmanaged certificates | Flat cluster network: any compromised pod can call any service |
Ingress controllers and Gateways in depth
Threat model
The edge faces the internet. Its threats are exploitation of exposed applications (MITRE ATT&CK T1190 Exploit Public-Facing Application), certificate and key theft, request smuggling and header manipulation, abuse of routing configuration, and denial of service. The controller is also a privileged workload: it watches Ingress or Route objects across namespaces and reads the TLS Secrets they reference, so a flaw in the controller or a malicious route definition can become cluster-wide compromise. This is exactly why the Kubernetes project retired Ingress NGINX (below).
Architecture in words
An Ingress object does nothing on its own: a controller selected by an IngressClass watches Ingress objects and programmes a proxy, usually exposed through a Service of type LoadBalancer. The Kubernetes documentation says the IngressClass API is always cluster-scoped, Ingress rules direct HTTP(S) traffic only, and exposing other protocols typically uses NodePort or LoadBalancer Services. Anything beyond host and path routing (rewrites, auth hooks, rate limits) was added through controller-specific annotations, which made configurations unportable and, in some controllers, allowed raw proxy configuration to be injected.
Gateway API replaces annotations with typed resources and splits ownership by role. The Kubernetes documentation describes three roles: infrastructure provider (GatewayClass), cluster operator (Gateway, listeners, policies) and application developer (Routes). A Gateway accepts Routes from its own namespace by default; routes from other namespaces need an explicit allowedRoutes setting. That split is a security feature: the team that owns certificates and listeners is not the team that writes routes, and cross-namespace attachment is opt-in.
Failure modes
- Annotation sprawl: application teams can change edge behaviour through annotations nobody reviews; snippet-style annotations that inject proxy configuration are the worst case.
- Plaintext beyond the edge: teams assume HTTPS end to end when the backend leg is plaintext inside the cluster.
- Over-privileged controller: a controller service account able to read all Secrets turns any controller compromise into credential theft.
- Bypass paths: NodePort or LoadBalancer Services exposing the same backends directly, so edge authentication and WAF rules are skipped.
- Unmaintained controllers: running a controller with no upstream security fixes.
Controls
Restrict who can create Ingress, Gateway and Route objects with RBAC; prefer Gateway API's role split; disallow snippet annotations; scope controller Secret access to the namespaces it serves where the controller supports it; block Services of type NodePort and LoadBalancer outside approved namespaces with admission policy (see the Kubernetes Admission Control Guide); automate certificates (see the cert-manager security guide); and authenticate users at the edge where applications lack it.
Service meshes in depth
Threat model
The mesh addresses what happens after one workload is compromised: lateral movement to other services, interception of in-cluster traffic (T1040 Network Sniffing, T1557 Adversary-in-the-Middle), and impersonation of services whose only "identity" is an IP address that changes with every pod restart. NIST SP 800-204A presents proxy-based service mesh components as the leading way to apply consistent security to microservices without changing service code.
Architecture in words
A control plane acts as certificate authority and configuration server; a data plane of proxies intercepts traffic. In Istio, istiod signs X.509 certificates for each workload, identity is based on the Kubernetes service account (policy principals take the form cluster.local/ns/<namespace>/sa/<account>), and the data plane runs either as Envoy sidecars or in ambient mode, where a per-node ztunnel handles mTLS, workload authentication and L4 authorisation and optional waypoint proxies add L7 policy. In Linkerd, mTLS is applied automatically to all TCP communication between meshed pods, each proxy certificate is bound to the pod's ServiceAccount and lasts 24 hours with automatic rotation, and the trust anchor created by a default install expires after 365 days and must be rotated by the operator.
These identities align with the SPIFFE model, in which a workload identity is a URI of the form spiffe://trust-domain/path presented in a verifiable document (X.509-SVID or JWT-SVID). SPIFFE trust domain names are self-registered, so the specification recommends using a domain you own or a UUID to avoid collisions, which matters when meshes in different clusters must trust each other. See the SPIFFE and SPIRE Guide.
Failure modes
- Permissive forever: Istio mesh-wide policy defaults to PERMISSIVE, which accepts both mTLS and plaintext; Linkerd accepts plaintext from non-meshed sources by default. Migration modes left in place mean an attacker in an unmeshed pod is still served.
- Encryption without authorisation: in Istio, workloads with no authorisation policy accept all requests, and deny-by-default applies only once an ALLOW policy exists. Encrypted traffic from any meshed workload is still accepted.
- JWT without a requirement: Istio RequestAuthentication rejects invalid tokens but accepts requests with no token unless an authorisation rule demands one.
- Gaps around the proxy: health checks, skip ports and non-meshed pods bypass mTLS, as the Linkerd documentation lists.
- Certificate authority neglect: an expired trust anchor or issuer certificate stops the mesh; a stolen root key lets an attacker mint any identity.
Controls
Move namespaces to STRICT mTLS (or disable plaintext acceptance) once every client is meshed; apply a default-deny posture with explicit ALLOW policies on source principals; keep DENY rules for sensitive paths (Istio evaluates CUSTOM, then DENY, then ALLOW); protect and rotate the mesh root and intermediate certificates, ideally from an external CA or HSM; treat service accounts as identities with owners, one per workload. See the Service Mesh Security Guide and the Service mesh rollout playbook.
Gateway API: one model for both directions
Gateway API matters to this comparison because it narrows the gap between edge and mesh configuration. For north-south, GatewayClass, Gateway, HTTPRoute and GRPCRoute are stable kinds in the Kubernetes documentation, with header matching and traffic weighting expressed as fields rather than annotations. For east-west, the Gateway API mesh work (GAMMA) attaches Routes directly to a Service as parentRefs instead of to a Gateway. Producer routes live in the Service's namespace and affect all clients; consumer routes live in another namespace and affect only clients in that namespace. The project states GAMMA has been in the Standard Channel since v1.1.0 and is considered GA. A practical security consequence: if routes are attached to a Service but none match a request, a GAMMA-compliant mesh rejects it.
| Concern | Ingress API | Gateway API (north-south) | Gateway API mesh (GAMMA) |
|---|---|---|---|
| Status | GA, frozen, no plans to remove | Recommended successor to Ingress | Standard Channel since v1.1.0 |
| Attachment point | IngressClass | Route to Gateway | Route to Service |
| Role separation | Single object, owned by app teams | Provider, operator and developer roles | Producer and consumer routes |
| Advanced behaviour | Controller-specific annotations | Typed fields | Typed fields |
| Cross-namespace control | Not modelled | allowedRoutes on the Gateway | Routes plus Kubernetes RBAC |
Gateway API standardises routing, not workload identity or mTLS policy: those remain mesh-specific resources. Plan for both sets of objects.
The Ingress NGINX retirement as a decision point
Kubernetes SIG Network and the Security Response Committee announced on 11 November 2025 that Ingress NGINX would receive best-effort maintenance only until March 2026, after which there are no releases, bug fixes or security updates, even for newly found vulnerabilities. Existing deployments keep running, which is the risk. The announcement points to Gateway API as the modern replacement and to alternative Ingress controllers for teams that must stay on Ingress.
For this comparison the retirement forces a design question rather than a like-for-like swap. Three paths are common: move to another Ingress controller (smallest change, stays on a frozen API); move to a Gateway API implementation at the edge (recommended direction, needs route conversion); or adopt a mesh whose gateway also serves as the north-south entry point, consolidating edge and east-west policy in one control plane (largest change, most capability). Inventory, annotation analysis and containment are covered in the Ingress NGINX Retirement guide; this page helps choose the target.
Where identity fits
A mesh turns Kubernetes service accounts into the authorisation subject for every call, so service account hygiene becomes network security. Shared service accounts collapse many workloads into one principal and make least-privilege policy impossible; the default service account in each namespace should not be the identity of any workload you write policy for. At the edge, the controller's own service account typically reads TLS Secrets and must be scoped and monitored like any privileged non-human identity. Mesh certificates are themselves non-human credentials, short-lived and automated, which is the model to aim for elsewhere. See the Kubernetes NHI Security Guide, How to find unused Kubernetes service accounts and mTLS vs Signed Tokens for Service Authentication.
Decision criteria
- Edge controller or Gateway only: few services, one team, little sensitive data in transit inside the cluster, and NetworkPolicy for segmentation. Choose Gateway API for new work.
- Add a mesh when: regulators or customers require encryption in transit inside the cluster; many teams share clusters; you need per-service authorisation by identity; or you run multi-cluster services that must authenticate each other.
- Add NetworkPolicy regardless: it is defence in depth that still works if the mesh proxy is bypassed or misconfigured.
- Prefer a lighter data plane when sidecar overhead or operational load is the objection: per-node L4 modes, such as Istio ambient mode with waypoints only where L7 policy is needed, reduce the footprint.
- Small organisations: a well-run Gateway, admission policy and NetworkPolicy usually beat a mesh nobody has time to operate.
- Large organisations: standardise one edge model and one mesh per platform, with policy as code and central certificate authority ownership.
Worked scenario: a payments platform
- Starting point: 60 services on two clusters behind Ingress NGINX with TLS at the edge; plaintext inside; no NetworkPolicy. An audit asks for encryption in transit and evidence that only the checkout service can reach the card tokenisation service.
- Edge decision: with Ingress NGINX out of maintenance, the team moves edge traffic to a Gateway API implementation, converting routes and dropping snippet annotations. The platform team owns the Gateway and certificates; application teams own HTTPRoutes in their namespaces.
- Mesh rollout: the mesh is enabled namespace by namespace in permissive mode, the Gateway's proxy is meshed so its calls carry an identity, and dashboards show the share of plaintext connections per namespace.
- Enforcement: once plaintext reaches zero in a namespace it moves to strict mTLS. The tokenisation namespace gets an ALLOW policy naming only the checkout service account; a test pod in another namespace is refused, which becomes audit evidence.
- Rollback: each step is reversible per namespace: return the namespace to permissive mode or remove the ALLOW policy, without touching the edge.
Detection and measurement
- mTLS coverage: percentage of in-cluster connections using mTLS, per namespace, from mesh telemetry; target 100 per cent in sensitive namespaces.
- Strict namespaces: share of namespaces in STRICT mode or with plaintext acceptance disabled.
- Policy coverage: share of workloads with at least one ALLOW policy (and so deny-by-default).
- Edge bypass count: NodePort and LoadBalancer Services outside approved namespaces, which should trend to zero.
- Certificate health: days to expiry of the mesh trust anchor and issuer, and edge certificates, alerted well before expiry.
- Denied calls: authorisation denials by source principal, reviewed for both attacks and broken policy. Audit log changes to Gateways, Routes and mesh policies (see the Kubernetes Audit Log Detection Guide).
Framework mapping
| Framework | Reference | Edge | Mesh |
|---|---|---|---|
| NIST CSF 2.0 | PR.DS-02 data in transit protected | Client TLS | Pod-to-pod mTLS |
| NIST CSF 2.0 | PR.AA-03 users, services and hardware authenticated | Edge user authentication | Workload identity |
| NIST CSF 2.0 | PR.IR-01 networks protected from unauthorised logical access | Single controlled entry point | East-west authorisation |
| NIST SP 800-53 Rev. 5 | SC-7 Boundary Protection | Primary | Internal boundaries |
| NIST SP 800-53 Rev. 5 | SC-8 Transmission Confidentiality and Integrity | Edge leg | Internal legs |
| NIST SP 800-53 Rev. 5 | IA-9 Service Identification and Authentication; AC-4 Information Flow Enforcement | Partial | Primary |
| NIST SP 800-53 Rev. 5 | SC-12 Cryptographic Key Establishment and Management | Edge certificates | Mesh CA and trust anchor |
| CIS Controls v8.1 | Control 12 Network Infrastructure Management; Control 13 Network Monitoring and Defense | Yes | Yes |
| MITRE ATT&CK | T1190 Exploit Public-Facing Application; T1040 Network Sniffing; T1557 Adversary-in-the-Middle | T1190 | T1040, T1557 |
Common failure patterns
- Believing the edge certificate means end-to-end encryption: Ingress terminates TLS and forwards plaintext unless something else encrypts the backend leg.
- Leaving the ingress proxy outside the mesh: strict policy then breaks external traffic, and the fix applied under pressure is usually to relax policy for everyone.
- Running the mesh in permissive mode indefinitely: dashboards show a mesh while plaintext from any pod is still accepted.
- mTLS with no authorisation policy: every meshed workload can still call every service.
- Staying on an unmaintained controller: retired Ingress NGINX deployments keep serving traffic with no future security fixes.
- Letting the trust anchor expire: a default 365-day anchor that nobody tracked takes down service-to-service traffic across the mesh.
Practitioner checklist
- Document which controls protect north-south and east-west traffic for each cluster, and where TLS terminates.
- Use Gateway API for new edge work, with platform-owned Gateways and team-owned Routes.
- Replace or contain Ingress NGINX and remove snippet-style annotations.
- Block NodePort and LoadBalancer Services outside approved namespaces with admission policy.
- Mesh the ingress proxy so its backend calls carry an identity.
- Move sensitive namespaces to strict mTLS and add default-deny with explicit ALLOW policies on service account principals.
- Give each workload its own service account and never write policy against the default one.
- Track expiry of mesh trust anchors, issuers and edge certificates, and keep root keys out of the cluster where possible.
- Keep NetworkPolicy as a second layer under the mesh.
Standards and references
- Kubernetes: Ingress
- Kubernetes: Gateway API
- Kubernetes SIG Network: Gateway API for Service Mesh
- Kubernetes: Ingress NGINX Retirement announcement
- NIST: SP 800-204A, Building Secure Microservices-based Applications Using Service-Mesh Architecture
- SPIFFE: SPIFFE ID and Trust Domain specification
- Istio: Security concepts
- Linkerd: Automatic mTLS
Related NHI Mgmt Group resources: Service Mesh Security Guide · Ingress NGINX Retirement · SPIFFE and SPIRE Guide · mTLS Guide · Kubernetes Network Policy Guide