Skip to content
Published on

Kong Ingress Controller Guide — API Gateway-Style Ingress Through Its Plugin Ecosystem

Authors

Introduction

Once you put a service on Kubernetes, a question follows almost immediately: how do you receive external traffic and route it to the right place? The most standard answer is the Ingress resource, and the implementation that has been used most widely is ingress-nginx. But as you actually operate real services, you reach a point where merely routing traffic by path is no longer enough.

Requirements like these start to pile up. A specific API needs a rate limit of 100 requests per minute. Partner calls need API key authentication. Requests going to a legacy backend need headers transformed and added. Every request must be traced with OpenTelemetry and exposed as Prometheus metrics. At this point, what you actually need is not a simple reverse proxy but an API Gateway.

Kong Ingress Controller (KIC from here on) targets exactly this gap. KIC wears the appearance of an Ingress controller, but behind it sits Kong Gateway, a full-stack API Gateway. In other words, you declare things in a Kubernetes-native way (Ingress resources, Gateway API, custom resources), and those declarations get translated into Kong Gateway's powerful plugin pipeline.

This article treats KIC not as a simple install guide but from the perspective of why it is designed this way and what you need to know operationally. We will cover the architecture and operating modes, the core CRDs, the representative plugins, Helm-based deployment, declarative config, and the relationship with Gateway API along with troubleshooting, all in a code-centric way. The reference versions are the Kong Gateway 3.x line, the KIC 3.x line, and the Kubernetes v1.32 line as of the first half of 2026.

The Ingress Landscape in 2026 — Why Look at Kong Now

Let us settle the big picture first. As of 2026, the Kubernetes ingress ecosystem is passing through an important inflection point.

First, the Ingress API is effectively frozen. The Kubernetes networking.k8s.io/v1 Ingress is stable, but no new features are being added. The spec only includes standard capabilities like TLS termination and host/path-based routing, while everything more advanced (header routing, traffic splitting, auth, rate limiting) has been pushed into the non-standard territory of each controller's annotations.

Second, Gateway API has taken over the standard's seat as its successor. Gateway API is the next-generation standard designed around role separation (infrastructure operator versus application developer), expressive routing, and portable specification, and as of 2026 its core resources have reached GA and entered a stage usable in production.

Third, ingress-nginx, the most widely used controller, has clearly shifted toward a maintenance-mode posture. It is operated with a focus on responding to security issues rather than developing new features, and as vulnerabilities related to injecting arbitrary configuration through annotations have been repeatedly reported, organizations have begun seriously evaluating alternatives.

Where these three trends meet, Kong emerges as an attractive option. Kong supports both Ingress and Gateway API, replaces the annotation hell of ingress-nginx with structured CRD-based configuration, and above all provides API Gateway-grade features as plugins.

Itemingress-nginxKong Ingress ControllerPure Gateway API implementations
Underlying data planeNGINXKong Gateway (NGINX + Lua/OpenResty)Varies by implementation
Standard interfaceIngressIngress + Gateway APIGateway API
Advanced feature configAnnotations/ConfigMapCRDs (KongPlugin etc.) + annotationsPolicy CRDs/filters
Auth and rate limitingLimitedRich pluginsImplementation-dependent
ObservabilityBasic metricsPrometheus/OTel pluginsImplementation-dependent
Project statusMaintenance-focusedActiveActive

As the table shows, Kong's differentiator is that you can use API Gateway features in a Kubernetes-native, declarative manner. So let us take apart its structure piece by piece.

Kong Gateway and KIC — Understanding the Two Layers

The first thing to make clear is that Kong Gateway and Kong Ingress Controller are separate components. Many newcomers confuse the two, but if you understand their roles separately, every concept that follows becomes easy.

Kong Gateway is the data plane that actually handles traffic. It is built on top of NGINX and OpenResty (LuaJIT), matches incoming requests to routes and services, and runs the plugin pipeline in between. This is the engine responsible for authentication, rate limiting, transformation, and logging.

Kong Ingress Controller plays the control plane role. It watches the Kubernetes API server, and when resources such as Ingress, Gateway API objects, or Kong CRDs are created or changed, it translates them into configuration that Kong Gateway understands and injects it. In other words, KIC is a translator and synchronization loop that converts Kubernetes declarations into Kong configuration.

+-----------------------------------------------------------------------+
|                          Kubernetes cluster                           |
|                                                                       |
|   [control plane]                      [data plane]                   |
|   Kong Ingress Controller   ──inject config──▶ Kong Gateway (proxy)    |
|        |                                          ^                    |
|        | watch                                    | handles traffic   |
|        v                                          |                    |
|   kube-apiserver                                  |                    |
|   |- Ingress / HTTPRoute                          |                    |
|   |- KongPlugin / KongConsumer                    |                    |
|   |- Service / Endpoints  <───────lookup endpoints┘                    |
|                                                                       |
+-----------------------------------------------------------------------+
        ^                                          |
        | kubectl apply                            v
   operator/developer                       external client request

Thanks to this separation, operators can choose one of two deployment patterns. One bundles the controller and gateway as sidecars in the same pod for simple operation. The other separates the control plane and data plane into distinct deployments so the data plane can scale horizontally on its own. When traffic grows large, the latter is the common choice.

DB-less Mode vs DB Mode — The Most Important Choice

When adopting KIC, the first and most important thing to decide is the data plane's operating mode. Kong Gateway runs in one of two modes depending on how it stores configuration.

In DB-less mode, Kong keeps no external database and operates from a declarative configuration loaded entirely in memory. KIC reads Kubernetes resources, generates a declarative configuration (JSON/YAML), and applies it wholesale into memory through Kong's Admin API. This is the recommended standard pattern in Kubernetes environments.

In DB mode, Kong uses PostgreSQL as a backend and persists configuration there. You can directly CRUD individual entities through the Admin API, which is flexible, but you take on the burden of operating an additional stateful store on top of Kubernetes and managing migration jobs.

ComparisonDB-less modeDB mode (PostgreSQL)
Config storeMemory (declarative)PostgreSQL
Single source of truthKubernetes resourcesDatabase
Operational complexityLowHigh (DB ops and migrations)
Horizontal scalingEasy, data plane is statelessPossible DB bottleneck
Admin API writesDisabled (read-only in nature)Enabled
GitOps fitVery highModerate
Recommended forGeneral Kubernetes useWhen some enterprise features are needed

The core principle in one line: if you run KIC on Kubernetes, make DB-less mode your default unless you have a specific reason not to. The Kubernetes resources themselves become the single source of truth, which meshes naturally with GitOps, and not operating a state store reduces your failure surface.

The following example shows the shape of the declarative configuration that KIC produces in DB-less mode. Operators rarely write it by hand, but knowing its shape helps you understand what Kong is actually looking at when you troubleshoot.

_format_version: "3.0"
services:
  - name: example-service.default.80
    host: example-service.default.svc
    port: 80
    protocol: http
    routes:
      - name: example-route
        paths:
          - /api
        strip_path: true
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          policy: local
consumers:
  - username: partner-a
    keyauth_credentials:
      - key: secret-api-key-value

Core CRDs — KongPlugin, KongConsumer, KongIngress

KIC's real power comes from custom resources. Everything that standard Ingress cannot express, Kong expresses cleanly with CRDs. Let us look at the three you will use most often.

KongPlugin and KongClusterPlugin

It is no exaggeration to say KongPlugin is the very reason to use KIC. You declare a single plugin, attach it via annotation to a target such as an Ingress, Service, HTTPRoute, or Consumer, and the plugin is applied to that traffic path.

The following example declares a rate-limiting plugin.

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: rl-by-minute
  namespace: default
config:
  minute: 100
  policy: local
plugin: rate-limiting

To apply this declared plugin to a specific Ingress, add an annotation on the Ingress side. You can apply multiple plugins at once by listing them with commas.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: default
  annotations:
    konghq.com/plugins: rl-by-minute,key-auth-plugin
    konghq.com/strip-path: "true"
spec:
  ingressClassName: kong
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: example-service
                port:
                  number: 80

KongPlugin is namespace-scoped, and when you want to apply the same role cluster-wide, you use KongClusterPlugin. It suits security headers or global logging plugins that you attach to every workload in common.

KongConsumer and Authentication

KongConsumer represents the calling party in the Kong world. You register partners, internal services, or specific clients as Consumers and attach authentication credentials (API keys, JWTs, and so on) to them. This lets you identify who made a call and apply different rate limits or permissions per Consumer.

apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
  name: partner-a
  namespace: default
  annotations:
    kubernetes.io/ingress.class: kong
username: partner-a
credentials:
  - partner-a-apikey

Credentials are usually managed separately as secrets. The following example defines a key-auth API key as a secret.

apiVersion: v1
kind: Secret
metadata:
  name: partner-a-apikey
  namespace: default
  labels:
    konghq.com/credential: key-auth
type: Opaque
stringData:
  key: super-secret-partner-a-key

KongIngress and the Direction of Newer CRDs

KongIngress was the traditional CRD for tuning the detailed behavior of routes and services (protocol, timeouts, retries, path handling, and so on). However, as of 2026 this role is gradually splitting into KongUpstreamPolicy plus Service annotations and Gateway API policies. For a project starting fresh, rather than cramming all configuration into a single KongIngress, the recommended approach is to express upstream behavior with KongUpstreamPolicy and plugins with KongPlugin.

The following example defines upstream health checks and a load-balancing algorithm with KongUpstreamPolicy.

apiVersion: configuration.konghq.com/v1beta1
kind: KongUpstreamPolicy
metadata:
  name: example-upstream-policy
  namespace: default
spec:
  algorithm: least-connections
  healthchecks:
    active:
      type: http
      httpPath: /healthz
      healthy:
        interval: 5
        successes: 1
      unhealthy:
        interval: 5
        httpFailures: 3

To apply this policy, you link it to the target Service with an annotation.

apiVersion: v1
kind: Service
metadata:
  name: example-service
  namespace: default
  annotations:
    konghq.com/upstream-policy: example-upstream-policy
spec:
  selector:
    app: example
  ports:
    - port: 80
      targetPort: 8080

Touring the Core Plugin Ecosystem

Kong's true value lies in its plugin ecosystem. There are dozens of official plugins and community plugins, and it is convenient to understand them in five broad categories.

CategoryRepresentative pluginsWhat they do
Authenticationkey-auth, jwt, oauth2, basic-auth, ldap-auth, openid-connectIdentify and authenticate the caller
Traffic controlrate-limiting, request-termination, proxy-cache, request-size-limitingFlow control and protection
Transformationrequest-transformer, response-transformer, correlation-idManipulate headers, body, and query
Observabilityprometheus, opentelemetry, http-log, file-log, datadogMetrics, traces, and logs
Securitycors, ip-restriction, bot-detection, aclAccess control and defense

Let us look at each category through a representative example.

Authentication — key-auth and OIDC

The simplest authentication is key-auth. As we saw, you attach an API key to a Consumer and apply the key-auth plugin to a route, and it verifies the key passed via header or query to identify the Consumer.

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: key-auth-plugin
  namespace: default
config:
  key_names:
    - apikey
  key_in_header: true
  key_in_query: false
plugin: key-auth

In enterprise environments, it is common to use the openid-connect plugin to integrate with Keycloak or an in-house IdP and apply OIDC-based authentication. In this case the gateway handles token verification, so backend services are freed from authentication logic.

Traffic Control — rate-limiting

Rate limiting is the most frequently used plugin. You can set limits per minute, hour, or day, and you can choose local, cluster, or Redis as the limit calculation policy. To share limits across multiple data plane pods, the Redis policy is accurate. The local policy is fast, but remember that limits are calculated separately per pod.

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: rl-redis
  namespace: default
config:
  minute: 100
  hour: 2000
  policy: redis
  redis:
    host: redis-master.default.svc
    port: 6379
plugin: rate-limiting

Transformation — request-transformer

When communicating with a legacy backend, you often need to add, remove, or change request headers. request-transformer handles this work at the gateway.

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: add-legacy-headers
  namespace: default
config:
  add:
    headers:
      - "X-Gateway: kong"
      - "X-Forwarded-Prefix: /api"
  remove:
    headers:
      - "X-Internal-Token"
plugin: request-transformer

Observability — prometheus and opentelemetry

The heart of operations is visibility. Apply the prometheus plugin globally and it exposes metrics such as request counts, latency, status codes, and upstream health in Prometheus format. The opentelemetry plugin exports distributed traces over OTLP, letting you trace the flow of requests passing through the gateway.

apiVersion: configuration.konghq.com/v1
kind: KongClusterPlugin
metadata:
  name: prometheus
  annotations:
    kubernetes.io/ingress.class: kong
  labels:
    global: "true"
config:
  status_code_metrics: true
  latency_metrics: true
  bandwidth_metrics: true
plugin: prometheus

A KongClusterPlugin labeled global: "true" is automatically applied to all routes. Observability plugins pair especially well with this global application style.

The Full Path a Request Travels

If we summarize in a single picture how the components we have seen so far fit together on an actual request, it looks like this.

external client
   |  GET https://api.example.com/api/orders  (with apikey header)
   v
[load balancer / Service type=LoadBalancer]
   |
   v
[Kong Gateway data plane pod]
   |
   |- 1. route match: host=api.example.com, path=/api  --> example-service
   |
   |- 2. plugin pipeline (request phase)
   |     |- cors            (handle preflight)
   |     |- key-auth        (verify apikey -> identify Consumer=partner-a)
   |     |- acl             (check partner-a is in the allowed group)
   |     |- rate-limiting   (check partner-a per-minute limit)
   |     |- request-transformer (massage headers)
   |
   |- 3. upstream selection (KongUpstreamPolicy: least-connections + healthcheck)
   |
   v
[example-service -> pod endpoints]
   |
   v  response
[Kong Gateway data plane]
   |
   |- 4. plugin pipeline (response phase)
   |     |- response-transformer
   |     |- prometheus / opentelemetry (record metrics and traces)
   |
   v
return response to external client

The important point in this flow is that plugins run in order according to phase and priority. For example, authentication (key-auth) must run before rate limiting (rate-limiting) so the gateway knows whose limit it is. Kong assigns each plugin a default priority that guarantees this order, and operators can adjust priorities if needed.

Simultaneous Support for Ingress and Gateway API

As noted earlier, the key theme of 2026 is Gateway API. A major advantage of KIC is that it supports both traditional Ingress and the new standard Gateway API. This lets you keep existing Ingress assets while gradually migrating to Gateway API.

In Gateway API, roles are separated. The infrastructure operator defines listeners and infrastructure with GatewayClass and Gateway, while the application developer declares routing rules with HTTPRoute. KIC handles all of these resources.

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: kong
spec:
  controllerName: konghq.com/kic-gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: kong-gateway
  namespace: default
spec:
  gatewayClassName: kong
  listeners:
    - name: http
      protocol: HTTP
      port: 80

HTTPRoute can express matching by header, method, and query parameter as a standard, not just by host and path. This was an area Ingress never managed to standardize.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: orders-route
  namespace: default
spec:
  parentRefs:
    - name: kong-gateway
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api/orders
          headers:
            - name: X-Api-Version
              value: v2
      backendRefs:
        - name: orders-service
          port: 80

Plugins apply identically in a Gateway API environment. You attach a KongPlugin annotation to an HTTPRoute, or connect it through the Gateway API standard's policy attachment mechanism. In other words, you move the standard interface to Gateway API while still enjoying Kong's rich plugin ecosystem unchanged.

The practical recommended path is this. For a new cluster, start with Gateway API from the beginning. For an existing Ingress-based cluster, unify on KIC, run Ingress and Gateway API side by side, and migrate gradually with HTTPRoute. That two standards can coexist under the same controller is a major strength of KIC.

Deploying with Helm

Now let us move to actual deployment. The standard way to deploy KIC is the Helm chart, and a single chart can install both the controller and the gateway. First add the Helm repository.

helm repo add kong https://charts.konghq.com
helm repo update
kubectl create namespace kong

The following is an example values file for deploying in DB-less mode. It enables the controller, exposes the gateway as a LoadBalancer, and specifies the proxy and Admin API configuration.

ingressController:
  enabled: true
  installCRDs: false
  ingressClass: kong

env:
  database: "off"
  router_flavor: expressions

proxy:
  enabled: true
  type: LoadBalancer
  http:
    enabled: true
    servicePort: 80
  tls:
    enabled: true
    servicePort: 443

admin:
  enabled: true
  type: ClusterIP
  http:
    enabled: true

resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: "2"
    memory: 1Gi

replicaCount: 2

It is safer to install CRDs separately and first. If you let the chart install CRDs, conflicts can occur during upgrades, so managing CRDs explicitly is operationally cleaner.

kubectl apply -f https://github.com/Kong/kubernetes-ingress-controller/releases/latest/download/all-in-one-dbless.yaml

Once the values file is ready, install it.

helm install kong kong/ingress \
  --namespace kong \
  --values values-dbless.yaml

After installation completes, check the gateway service's external IP and the controller's status.

kubectl get pods -n kong
kubectl get svc -n kong
kubectl get ingressclass

If you see kong in IngressClass and an EXTERNAL-IP assigned to the gateway service, it is ready to receive traffic. From there, apply the Ingress, HTTPRoute, and KongPlugin resources we saw earlier to configure routing and policies.

Declarative Config and GitOps

The biggest appeal of DB-less mode is that all configuration is expressed as Kubernetes resources and lands directly in Git. This meshes naturally with GitOps. If you synchronize manifests with Argo CD or Flux, the change history and rollback of gateway configuration are managed entirely within the Git workflow.

If your organization uses Kong outside Kubernetes as well, the tool decK is worth knowing. decK is a CLI that manages Kong's declarative configuration as code, shows the diff between current and desired state, and synchronizes it. It is useful for dumping and validating the configuration KIC produces, or for comparing configuration across environments.

deck gateway dump --kong-addr http://localhost:8001 -o kong-state.yaml
deck gateway diff kong-state.yaml --kong-addr http://localhost:8001

That said, you need caution when using KIC and decK against the same gateway at once. In DB-less mode the single source of truth must be the Kubernetes resources, so limit decK to export and validation, and keep the discipline of changing configuration only through Kubernetes resources. That is the way to avoid conflicts.

Operations and Tuning

To run KIC reliably in production, you need to take care of a few core points.

First, horizontal scaling of the data plane. The gateway in DB-less mode is nearly stateless, so you can grow throughput just by increasing replicas. However, the local policy of rate-limiting calculates limits separately per pod, so if you need an accurate global limit, you must switch to the Redis policy.

Second, health checks and probes. Kong provides a status endpoint, so connect it to readiness and liveness probes to quickly isolate unhealthy pods. Also configure upstream health checks (active/passive) with KongUpstreamPolicy so traffic does not go to dead backend pods.

Third, resource and worker tuning. Values such as the number of NGINX worker processes, connection limits, and timeouts must be adjusted to your traffic characteristics. In particular, growing the upstream keepalive connection pool appropriately reduces the cost of opening a new connection to the backend on every request.

Fourth, TLS and certificate management. Integrating with cert-manager to automate certificate issuance and renewal is standard. Connect a certificate secret to the TLS section of an Ingress or to a Gateway listener, and configure cert-manager to renew automatically via Let's Encrypt or similar.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-example-com-tls
  namespace: default
spec:
  secretName: api-example-com-tls
  dnsNames:
    - api.example.com
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer

Fifth, the security boundary. The Admin API must never be exposed externally. Keep it as ClusterIP or restrict access with network policies, and in DB-less mode operate the Admin API close to read-only for safety. To avoid the kind of arbitrary configuration injection vulnerabilities that repeatedly plagued ingress-nginx, manage plugin configuration only from trusted sources (Git, reviewed manifests).

Common Pitfalls and Troubleshooting

Here are problems you frequently hit in operations, along with their causes.

First, a missing IngressClass. If you do not specify ingressClassName: kong on an Ingress, KIC ignores that Ingress. If routing does not work at all and the logs are also quiet, suspect this first. In Gateway API, check that the GatewayClass controllerName exactly matches KIC's controller name.

Second, plugin annotation typos. If the name written in the konghq.com/plugins annotation differs from the actual KongPlugin resource name, or the namespace is off, the plugin is not applied. When a plugin seems not to attach, compare the KongPlugin resource's name and namespace against the annotation string.

Third, strip_path confusion. If you set konghq.com/strip-path to true, the matched path prefix is removed before forwarding to the backend. If the path the backend receives differs from what you expected, check this setting. Whether a request arriving at /api reaches the backend as / or as /api is decided here.

Fourth, config propagation delay. KIC detects changes and synchronizes them to the gateway, but on bulk changes or large clusters there can be a slight delay. When a change appears not to take effect, check the controller logs for synchronization success and the last sync timestamp.

Fifth, declarative config validation failure. If the declarative configuration KIC generates has an error (for example, an invalid plugin config schema), the gateway rejects the new configuration and keeps the last good one. So it can look like you applied a bad change and nothing happened. The key here is to find the validation error message in the controller logs.

A useful set of commands for diagnosis is as follows.

kubectl logs -n kong deploy/kong-controller -c ingress-controller --tail=200
kubectl describe ingress api-ingress -n default
kubectl get kongplugin -A
kubectl get kongconsumer -A
kubectl port-forward -n kong svc/kong-admin 8001:8001

After port-forwarding to the Admin API with the last command and directly querying the routes, services, and plugin state the gateway actually holds, you can most quickly close the gap between Kubernetes declarations and what is actually applied.

Sixth, admission webhook rejection. KIC can use a validating webhook to block invalid plugin configuration or duplicate credentials in advance. If kubectl apply is rejected, reading the webhook message verbatim is the fastest fix. The webhook is a safety net that stops bad configuration before it reaches the gateway, so it is better to interpret and fix the message than to disable it.

Conclusion

KIC is not merely an Ingress controller but a bridge connecting Kubernetes-native declarations to a full-stack API Gateway. In the 2026 landscape where the Ingress API is frozen and Gateway API has taken the standard's seat, KIC supports both standards while offering its plugin ecosystem as a powerful differentiator.

If you are weighing adoption, here is the order we recommend. First, make DB-less mode your default to lower operational complexity, and express all configuration as Kubernetes resources to ride GitOps. Cleanly separate core features such as auth, rate limiting, and observability into plugins from the start to keep backends light. Express new routing with Gateway API's HTTPRoute, but run existing Ingress assets side by side under the same controller and migrate gradually.

Above all, what matters is understanding the shift from an era of cramming everything into annotations to an era of expressing policy with structured CRDs and the standard Gateway API. Kong is a tool that lets you walk that transition relatively smoothly, and for any organization that needs an API Gateway, it is well worth serious consideration.

References