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.
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.