Back to blog
December 2025 5 min read

Kong Gateway: Routes, Rate Limits, and Locking the Front Door

A hands-on guide to Kong API Gateway — OSS quickstart with Docker, declarative routing via the Admin API, deploying on K3s with Helm, hiding version headers, IP blocking, and request rate limiting.

Kong API Gateway Kubernetes K3s Helm DevOps Security

Every service behind an API gateway needs the same handful of things solved once: routing, rate limiting, IP restrictions, and not leaking what software you're running. Kong handles all of it declaratively — as YAML in Kubernetes, or as plain curl calls to its Admin API everywhere else.


Quick Setup: Kong OSS with Docker

The official quickstart script spins up a complete Kong instance (database, Admin API, proxy) in one shot:

curl -Ls https://get.konghq.com/quickstart | bash -s -- -i kong -t latest

Once running, Kong exposes two key ports: the Admin API (localhost:8001) for configuration, and the Proxy (localhost:8000) that actually routes traffic.


Routing Setup (Upstream → Service → Route)

Kong's model has three layers: an Upstream (the actual backend target, with optional health checks and load-balancing weight), a Service (a named pointer to that upstream, tied to a host/protocol/port), and a Route (the public-facing path that maps to a service).

Step 1 — Create an Upstream

Set the upstream name and host header to match your target domain (e.g. land.ba-systems.com for both). Health checks are optional — enable them if you want Kong to automatically stop routing to unhealthy targets.

Step 2 — Set the Upstream Target

Point the upstream at the actual backend IP and port:

192.168.10.253:31501

Assign a weight if you're load-balancing across multiple targets — it defaults to 100 if omitted.

Step 3 — Create a Service

Via UI: choose protocol, host, port, and path — set host to land.ba-systems.com, path to / (or blank) for root, and name it something identifiable like land-api.

Via Admin API:

curl -i -X POST http://localhost:8001/services \
  --data protocol=http \
  --data host=land.ba-systems.com \
  --data name=land-api

Step 4 — Create a Route for the Service

Via UI: name it (land-route), attach it to the service created above, and give it the path you want exposed (e.g. /test).

Via Admin API:

curl -i -X POST http://localhost:8001/routes \
  --data "paths[]=/test" \
  --data service.id=<service-id>

Step 5 — Route a Specific Backend Endpoint

For a nested API path like /api/v1/get-divisions, create a dedicated service and route pair:

# Service
curl -i -X POST http://localhost:8001/services \
  --data protocol=http \
  --data host=land.ba-systems.com \
  --data name=land-api-backend

# Route
curl -i -X POST http://localhost:8001/routes \
  --data "paths[]=/test/api/v1" \
  --data service.id=<service-id>

Deploying Kong Gateway on K3s

For a Kubernetes-native setup, Kong ships an official Helm chart via its Ingress Controller.

Install Helm

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Install Kong Ingress Controller

helm repo add kong https://charts.konghq.com
helm repo update
helm install kong kong/ingress -n kong --create-namespace

Expose It Without a Cloud Load Balancer

If you're on bare-metal or a VM (no cloud LB provisioning), switch the proxy service to NodePort:

kubectl edit svc kong-gateway-proxy -n kong

Or patch it directly without opening an editor:

kubectl patch svc kong-gateway-proxy -n kong -p '{"spec":{"type":"NodePort"}}'

Hardening: Hide the Kong Version Header

Leaking the gateway version in response headers is a common information disclosure finding in security scans. Fix it by disabling Kong's default headers:

kubectl edit deployment kong-gateway -n kong

Add under the container's env: block:

- name: KONG_HEADERS
  value: "off"

If editing the deployment directly isn't convenient (e.g. scripted pipelines), patch it instead:

kubectl patch deployment kong-gateway \
  -n kong \
  --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/env/-","value":{"name":"KONG_HEADERS","value":"off"}}]'

Blocking a Specific IP

Use the built-in ip-restriction plugin to blacklist an address at the ingress level.

block-specific-ip-kongplugin.yaml

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: block-specific-ip
config:
  blacklist:
  - 39.124.212.150
plugin: ip-restriction

kong-ingress-a2i-web.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-for-a2i-web
  annotations:
    konghq.com/protocols: "http,https"
    konghq.com/strip-path: "false"
    kubernetes.io/ingress.class: kong
    konghq.com/plugins: block-specific-ip  # <-- Plugin attached here
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: test-erp-web
            port:
              number: 80
        path: /
        pathType: Prefix

Enabling Request Rate Limiting

Layer on the rate-limiting plugin to cap requests per client:

rate-limit-kongplugin.yaml

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

Attach both plugins to the same ingress by listing them comma-separated in the annotation:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-for-a2i-web
  annotations:
    konghq.com/protocols: "http,https"
    konghq.com/strip-path: "false"
    kubernetes.io/ingress.class: kong
    konghq.com/plugins: block-specific-ip,rate-limit-plugin
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: test-erp-web
            port:
              number: 80
        path: /
        pathType: Prefix

policy: local keeps the rate-limit counter in-memory per Kong node — fine for a single replica, but worth switching to redis if you're running Kong with multiple replicas, otherwise each pod tracks its own independent limit.

Verify Attached Plugins

kubectl get kongplugin

Increasing Request Timeouts

Long-running backend calls (report generation, large uploads, batch jobs) will hit Kong's default timeouts unless you override them per-service via annotations:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-qpa
  namespace: default
  annotations:
    konghq.com/connect-timeout: "360000"
    konghq.com/read-timeout: "360000"
    konghq.com/write-timeout: "360000"
spec:
  selector:
    app: tomcat-qpa
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  type: ClusterIP

Values are in milliseconds — 360000 here equals 1 hour. Set these directly on the backend Service, not the Ingress.


Summary

| Concern | Mechanism | |---|---| | Routing | Upstream → Service → Route (UI or Admin API) | | K8s deployment | Helm chart (kong/ingress) | | Version disclosure | KONG_HEADERS: "off" env var | | IP blocking | ip-restriction KongPlugin | | Rate limiting | rate-limiting KongPlugin (policy: local or redis) | | Long-running requests | konghq.com/*-timeout annotations on the Service |

Golden rule: plugins attach to ingresses via the konghq.com/plugins annotation as a comma-separated list — always check kubectl get kongplugin after applying, since a typo in the plugin name fails silently rather than erroring the ingress.