Kubernetes Ingress and API Gateway Security: TLS, Auth, and Rate Limiting

Secure your Kubernetes ingress and API gateway with this complete guide covering TLS, authentication, rate limiting, WAF, and the Gateway API migration. Ten critical mistakes and how to fix them.

Your Kubernetes ingress is the front door to every service in your cluster — and most teams leave it unlocked, unmonitored, and wide open to attackers. A misconfigured Ingress controller or an API gateway with default settings can expose internal services to the internet, bypass authentication, and leak sensitive data through improperly terminated TLS.

In the 2026 Cloud Native Security Survey by the CNCF, 68% of respondents reported at least one security incident involving an exposed ingress or API gateway endpoint. The most common root causes were default TLS certificates, missing authentication on admin endpoints, and unrestricted network access from the Ingress controller to all cluster services. This guide walks through the ten most critical mistakes teams make when securing Kubernetes Ingress and API gateways — and exactly how to fix each one.

Why Kubernetes Ingress Security Matters

An Ingress controller or API gateway is the single entry point for external traffic into your Kubernetes cluster. Unlike a pod-to-pod connection within the same namespace, Ingress traffic crosses the cluster boundary and enters from the public internet — or at least from an external network you don't fully control. Every request that reaches your Ingress controller has the potential to exploit a misconfiguration and reach a service that was never meant to be public.

The Kubernetes Ingress API has evolved significantly. The originalnetworking.k8s.io/v1 Ingress resource is being supplemented — and in many environments replaced — by the Gateway API(gateway.networking.k8s.io), which offers role-oriented configuration, stronger multi-tenancy separation, and first-class support for TLS, HTTP header manipulation, and traffic splitting. Understanding both APIs is essential because most production clusters today run a mix of Ingress and Gateway resources during the migration period.

At the same time, third-party API gateways like Kong, Ambassador (now Edge Stack), Traefik, and Gloo add their own CRDs, authentication plugins, and rate-limiting middleware. Each layer you add expands the attack surface unless it is deliberately hardened.

Below are the ten most common — and most dangerous — misconfigurations we see in production Kubernetes clusters, with concrete fixes and verification commands for each.

Mistake 1: Default TLS Certificate in Production

Every major Ingress controller ships with a self-signed or auto-generated TLS certificate for development convenience. NGINX Ingress uses a default certificate stored in theingress-nginx namespace; Traefik generates one at startup; Kong creates a default SNI certificate. Using any of these in production means every client that connects to your services receives a browser warning — or worse, silently accepts a certificate that an attacker could also generate if they know the controller version.

The fix:Always provision a TLS certificate from a trusted Certificate Authority (Let's Encrypt, a commercial CA, or an internal PKI for private services). Usecert-manager for automated certificate issuance and renewal:

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-prod
  namespace: ingress-nginx
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 80

Verify that the certificate is valid:openssl s_client -connect app.example.com:443 -servername app.example.com | openssl x509 -noout -dates

Mistake 2: Missing Authentication on the Ingress Controller's Own Endpoints

The NGINX Ingress controller exposes a/metrics endpoint, a /healthz endpoint, and a /nginx-statuspage on port 10254 by default. Traefik exposes a dashboard on port 9000. Kong has an Admin API on port 8001. These internal endpoints are intended for cluster-internal monitoring — but if your Ingress controller's pod network policy is too permissive, or if you've inadvertently exposed the dashboard through a Service of type LoadBalancer, an attacker on the internet can enumerate every upstream service, inspect TLS certificates, and read real-time traffic metrics.

The fix:Restrict access to these endpoints using Kubernetes Network Policies. For the NGINX Ingress controller:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-ingress-nginx-metrics
  namespace: ingress-nginx
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/component: controller
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: monitoring
      podSelector:
        matchLabels:
          app.kubernetes.io/name: prometheus
    ports:
    - protocol: TCP
      port: 10254

Also disable the default server that responds to requests with no matching hostname:

# In the NGINX Ingress ConfigMap:
data:
  enable-annotation-validation: "true"
  default-backend-service: "my-safe-default"  # or disable:
  --default-ssl-certificate: ""

Mistake 3: No Rate Limiting on Public Endpoints

Without rate limiting, an attacker can brute-force login endpoints, enumerate valid usernames, or exhaust cluster resources with a simple script. The Kubernetes Ingress resource does not natively support rate limiting — you must configure it at the Ingress controller or API gateway layer. NGINX Ingress provides annotations for this; Kong and Ambassador include rate-limiting plugins.

The fix:Use NGINX Ingress annotations to enforce per-IP or per-connection limits:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-gateway
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "10"
    nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
    nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/8, 172.16.0.0/12"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /login
        pathType: Prefix
        backend:
          service:
            name: auth-service
            port:
              number: 8080

For Kong Gateway, use the rate-limiting plugin in declarative config:

plugins:
- name: rate-limiting
  service: auth-service
  config:
    minute: 20
    hour: 500
    policy: local

Mistake 4: Allowing Host Header Injection and Routing Bypass

When an Ingress controller processes an incoming request, it selects the correct backend based on theHost header. If the controller's default backend serves a catch-all response, an attacker can send a request with a Host header that doesn't match any rule and potentially reach internal services or get a detailed error page that reveals stack traces and internal IP addresses.

The fix:Always set a safe default backend and reject unknown hosts at the firewall level. For NGINX Ingress:

# In the NGINX Ingress ConfigMap
data:
  default-backend-service: ingress-nginx/404-default
  enable-underscores-in-headers: "false"
  log-format-escape-json: "true"
  use-proxy-protocol: "false"

# Alternatively, use the --default-backend-service flag
# Deploy a simple 404 responder:
apiVersion: v1
kind: Service
metadata:
  name: 404-default
  namespace: ingress-nginx
spec:
  selector:
    app: 404-default
  ports:
  - port: 80
    targetPort: 8080

Use cert-manager and Let's Encrypt to enforce TLS on every host, which prevents cleartext host-header manipulation between the client and the controller.

Mistake 5: Overly Permissive Ingress Controller RBAC

The Ingress controller needs to read Ingress and Service resources across namespaces to function. Many default Helm charts grant cluster-wide permissions far beyond what is necessary. If the controller pod is compromised — through a CVE in NGINX, a supply-chain attack on the controller image, or a misconfigured admission webhook — an attacker inherits the controller's ServiceAccount permissions and can read Secrets, create new Ingress rules, or even escalate to cluster-admin.

The fix:Audit the Ingress controller's RBAC bindings and restrict them to the minimum required:

# Minimal ClusterRole for NGINX Ingress Controller
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: ingress-nginx-minimal
rules:
- apiGroups: ["networking.k8s.io", "extensions"]
  resources: ["ingresses"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["services", "endpoints", "secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
  resources: ["endpointslices"]
  verbs: ["get", "list", "watch"]
---
# No "secrets" create/update/delete unless you use
# the controller for TLS certificate management

Verify with:kubectl describe clusterrolebinding ingress-nginx | grep -A5 Subjects and check the rules on the bound ClusterRole.

Mistake 6: Disabling TLS Verification Between Ingress and Backend Services

By default, the NGINX Ingress controller communicates with backend services over plain HTTP on the service port. Even if the external connection uses HTTPS, the hop between the Ingress controller and your application pod is unencrypted. An attacker who gains access to the cluster network — through a compromised sidecar, a misconfigured network policy, or a pod in the same node — can sniff traffic between the Ingress controller and your backend services.

The fix:Configure mTLS between the Ingress controller and backend services where possible, or at minimum use TLS for the internal hop:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-backend
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    nginx.ingress.kubernetes.io/ssl-passthrough: "false"
    nginx.ingress.kubernetes.io/secure-backends: "true"
    nginx.ingress.kubernetes.io/ssl-verify: "true"
    nginx.ingress.kubernetes.io/proxy-ssl-secret: "default/backend-ca-secret"
    nginx.ingress.kubernetes.io/proxy-ssl-verify: "on"
spec:
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app
            port:
              number: 443

For service mesh environments (Istio, Linkerd), enableStrict mTLSmode on the mesh, which encrypts all inter-pod traffic automatically:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default-strict
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

Mistake 7: Exposing Internal Services Through Wildcard or Catch-All Ingress Rules

A single Ingress rule with a wildcard host (*.example.com) or a catch-all path (/) that matches every request can inadvertently expose internal dashboards, CI/CD tools, or monitoring interfaces. We have seen production incidents where a Grafana dashboard, a Jenkins controller, or a RabbitMQ management UI was accessible from the internet because the team relied on "security through obscurity" (no DNS record, no public knowledge of the subdomain) instead of actual access controls.

The fix:Instead of a single catch-all Ingress, create separate Ingress resources for each subdomain or path prefix, and use network policies to limit egress from the Ingress controller to only the required services:

# Bad: single catch-all
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# spec:
#   rules:
#   - host: *.example.com  # DANGEROUS
#     http:
#       paths:
#       - path: /
#         backend:
#           service:
#             name: any-service
#             port:
#               number: 80

# Good: explicit per-service Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    nginx.ingress.kubernetes.io/whitelist-source-range: "0.0.0.0/0"  # public
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: internal-tools
  annotations:
    nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8, 192.168.0.0/16"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - grafana.internal.example.com
    secretName: internal-tls
  rules:
  - host: grafana.internal.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: grafana
            port:
              number: 3000

Mistake 8: Not Using Web Application Firewall (WAF) Rules

A vanilla Ingress controller forwards every HTTP request verbatim to the backend service. SQL injection payloads, XSS attempts, path traversal sequences, and large request bodies all pass through unmodified. While the backend application should validate input, defense in depth dictates that the edge layer should also filter malicious traffic before it reaches your pods.

The fix:Enable ModSecurity with the OWASP Core Rule Set (CRS) on the NGINX Ingress controller, or use a dedicated WAF like Cloudflare, AWS WAF, or Signal Sciences:

# Enable ModSecurity in the NGINX Ingress ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  enable-modsecurity: "true"
  enable-owasp-modsecurity-crs: "true"
  modsecurity-snippet: |
    SecRuleEngine On
    SecRequestBodyAccess On
    SecResponseBodyAccess On
    SecResponseBodyMimeType text/plain text/html text/xml
    SecDefaultAction "phase:1,log,auditlog,pass"
    SecAuditEngine RelevantOnly
    SecAuditLog /var/log/modsec_audit.log

# Verify with:
# curl -X POST https://app.example.com/search -d "q=1' OR '1'='1"
# Should return 403 Forbidden

For the Gateway API, use policy attachment to apply WAF rules at the gateway level. Thev1alpha2 HTTPRouteresource supports filter rules that can redirect, rewrite, or reject requests based on headers, path, or query parameters.

Mistake 9: Ignoring the Gateway API Migration Path

The original Ingress API (networking.k8s.io/v1) has several limitations: it cannot express cross-namespace routing, header-based routing, or weighted traffic splitting without controller-specific annotations. The Gateway API(gateway.networking.k8s.io) was designed as the successor with role-oriented resources (Gateway, GatewayClass, HTTPRoute, TLSRoute) that separate infrastructure concerns from application routing. Clusters still using only the Ingress API are missing built-in support for advanced security patterns like per-route authentication, request mirroring for security analysis, and fine-grained TLS configuration.

The fix:Start migrating to the Gateway API. Install the Gateway API CRDs and begin with a simple HTTPRoute for a non-critical service:

# Install Gateway API CRDs
# kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
spec:
  gatewayClassName: nginx
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      certificateRefs:
      - name: app-tls
    allowedRoutes:
      namespaces:
        from: Selector
        selector:
          matchLabels:
            security-tier: "trusted"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app-route
spec:
  parentRefs:
  - name: production-gateway
  hostnames:
  - app.example.com
  rules:
  - matches:
    - path:
        value: /api
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: X-Frame-Options
          value: DENY
    backendRefs:
    - name: api-service
      port: 8080
  - matches:
    - path:
        value: /login
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: Strict-Transport-Security
          value: max-age=31536000; includeSubDomains
    backendRefs:
    - name: auth-service
      port: 8080

Mistake 10: Disabling Access Logs and Monitoring on the Ingress Controller

Without access logs, you cannot detect ongoing attacks — credential stuffing, SQL injection probes, path traversal attempts — against your services. Many production clusters run with default logging settings that either produce too little detail (no request body, no response status) or log everything to stdout without structured formats, making analysis in SIEM tools like Splunk, Elastic, or Loki impractical.

The fix:Enable structured JSON logging on the Ingress controller and ship logs to a centralized monitoring system:

# NGINX Ingress ConfigMap for structured logging
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  log-format-escape-json: "true"
  log-format-upstream: '{"time": "$time_iso8601", "remote_addr": "$remote_addr",
    "host": "$host", "method": "$request_method", "path": "$uri",
    "status": $status, "body_bytes_sent": $body_bytes_sent,
    "referer": "$http_referer", "user_agent": "$http_user_agent",
    "request_length": $request_length, "upstream_addr": "$upstream_addr",
    "upstream_response_time": "$upstream_response_time",
    "ssl_protocol": "$ssl_protocol", "ssl_cipher": "$ssl_cipher"}'

Set up alerts for HTTP 4xx and 5xx spikes, unusual request patterns (same path repeated >100 times from one IP), and requests with suspicious User-Agent strings.

Monitoring hint: deploy Prometheus with the NGINX Ingress metrics exporter and create Grafana dashboards for request rate, error rate, latency (P99), and TLS certificate expiry.

Complete Kubernetes Ingress Security Checklist

  • ⬜ All Ingress and Gateway resources use TLS from a trusted CA (not self-signed)
  • ⬜ TLS certificates are managed by cert-manager with auto-renewal
  • ⬜ Ingress controller's admin/metrics endpoints are firewalled with NetworkPolicies
  • ⬜ Rate limiting is enabled on all public-facing endpoints
  • ⬜ Default backend returns 404 for unmatched hosts and paths
  • ⬜ Ingress controller ServiceAccount has least-privilege RBAC (no cluster-admin)
  • ⬜ mTLS or HTTPS is enforced between the Ingress controller and backend services
  • ⬜ Internal dashboards and tools are NOT accessible through the same Ingress that serves public traffic
  • ⬜ WAF rules (ModSecurity + OWASP CRS) are enabled on the edge
  • ⬜ Structured access logs are shipped to a SIEM or centralized log system
  • ⬜ Alerts are configured for 4xx/5xx surges and anomalous traffic patterns
  • ⬜ Ingress and Gateway API resources are audited weekly for unused routes
  • ⬜ Host header validation prevents routing bypass attacks
  • ⬜ Migration plan exists from Ingress v1 to the Gateway API

Related ShieldOps Reads

Strengthen your Kubernetes security posture with these related articles:

Frequently Asked Questions

What is the difference between Ingress and the Gateway API?

The Ingress API (networking.k8s.io/v1) is a single flat resource that combines infrastructure and routing configuration. The Gateway API (gateway.networking.k8s.io) separates concerns into GatewayClass (infrastructure provider), Gateway (instance), and HTTPRoute/TLSRoute (application routing), enabling better multi-team isolation, cross-namespace routing, and richer security policies.

Should I use NGINX Ingress, Traefik, or Kong for production?

All three are production-grade. NGINX Ingress is the most widely deployed, with the largest ecosystem of annotations and community support. Traefik excels in dynamic environments with automatic service discovery and Let's Encrypt integration. Kong offers a plugin ecosystem for authentication, rate limiting, and logging at the gateway layer. Choose based on your team's familiarity and whether you need advanced plugin capabilities.

How do I enforce HTTPS-only access on my Ingress?

Usecert-manager with Let's Encrypt to provision TLS certificates automatically, then set nginx.ingress.kubernetes.io/ssl-redirect: "true" on each Ingress resource. For a cluster-wide redirect, configure the default backend to return a 308 redirect status code for HTTP requests. The Gateway API's HTTPRoute supports redirect filters natively.

Can I use a single Ingress controller for both public and private services?

Yes — but configure two separate Ingress resources: one for public services withwhitelist-source-range: "0.0.0.0/0" and one for private services with a restricted CIDR range (e.g., "10.0.0.0/8"). For stronger isolation, deploy separate Ingress controller instances — one for internet-facing traffic and one for internal traffic — each with its own NetworkPolicy and RBAC.

How do I detect an Ingress misconfiguration before it reaches production?

Usekubectl ingress-nginx lint or a policy engine like OPA Gatekeeper or Kyverno to validate Ingress resources against your organization's security policies. For example, enforce that every Ingress has a tls block, a cert-manager.io/cluster-issuer annotation, and a rate-limiting annotation. Use admission webhooks to reject resources that don't meet these requirements.

What is the impact of a compromised Ingress controller?

A compromised Ingress controller can read TLS certificates (potentially decrypting traffic across all services), redirect traffic to attacker-controlled backends, modify responses to inject malicious content, and, if RBAC is over-permissive, access Secrets and create or modify Ingress resources across namespaces. This is why least-privilege RBAC and strict NetworkPolicies around the controller are critical controls.

Should I use mTLS for all ingress-to-backend communication?

Yes, whenever possible. If your backend services support HTTPS, configure the Ingress controller to usebackend-protocol: HTTPS with certificate verification. In a service mesh (Istio, Linkerd, Cilium), enable strict mTLS mode to encrypt all inter-pod traffic automatically without modifying application code.

Conclusion

Kubernetes Ingress and API gateway security is not optional — it is the first line of defense for every external-facing service in your cluster. The ten mistakes covered in this guide represent the most common misconfigurations we see in production environments, from default TLS certificates and missing authentication to unmonitored access logs and over-privileged RBAC.

The path to a hardened ingress layer is clear: enforce TLS with cert-manager, restrict the controller's network and RBAC permissions, enable rate limiting and WAF rules, and monitor every request through structured logging and alerting. As the Kubernetes ecosystem evolves toward the Gateway API, each of these controls becomes simpler to express and enforce at the platform level rather than per-application.

Start your security audit today: runkubectl get ingress --all-namespaces and check every resource against the checklist above. Use ShieldOpsto automate policy enforcement, vulnerability scanning, and compliance reporting for your Kubernetes clusters — so you catch these mistakes before attackers do.

Ready to apply these concepts?

Scan your Kubernetes manifests for RBAC gaps, policy issues, and misconfigurations.

Scan Your Kubernetes Cluster

Your take

Rate this article or leave a comment

Have more questions? Check our

FAQ
🤖