⚡ Opening Hook
During a Black Friday sale, the e‑commerce platform nileshblog.tech saw traffic soar to 120 k RPS. Within seconds, its customer‑facing services returned 502 errors, and the checkout pipeline ground to a halt. The root cause? A single, overloaded API gateway that tried to juggle authentication, rate limiting, and traffic routing without the right architecture. The fallout cost minutes of lost revenue and a bruised brand reputation—issues that any microservices team can relate to when the edge layer isn’t built for security and scale.
TL;DR – 5 Takeaways
- Kong provides a cloud‑native, plugin‑centric gateway that can handle >30 k RPS with sub‑5 ms overhead when tuned correctly.
- Choose DB‑backed Kong for dynamic config changes; switch to DB‑less for immutable, high‑performance deployments.
- Secure the Admin API with mTLS, IP allow‑lists, and rotate secrets every 30 days.
- Implement advanced, multi‑dimensional rate limiting via a custom Lua/Go plugin to respect tier, endpoint, and time‑of‑day constraints.
- Design a HA datastore layer (Postgres or Cassandra) with automated failover and disaster‑recovery to avoid a single point of failure.
Before You Start, You Need:
- A Kubernetes 1.27 cluster (or Docker Compose for local testing).
- Kong 3.5.0 Docker image or Helm chart.
- PostgreSQL 14 or Cassandra 4.0 for the datastore (or plan for DB‑less mode).
- Basic knowledge of Lua 5.4, Go 1.22, JWT, and OAuth 2.0.
kubectl,helm, andterraformv1.6 installed locally.
1. Why an API Gateway Is Non‑Negotiable for Microservices – API Management & Edge Security
The Growing Pains of Direct Client‑to‑Service Communication
When each microservice publishes its own public endpoint, clients must maintain dozens of URLs, handle divergent auth schemes, and retry across flaky services. This fragmentation inflates latency and multiplies the attack surface.
Core Responsibilities of a Modern API Gateway
A well‑engineered gateway consolidates authentication, rate limiting, observability, traffic shaping, and TLS termination into a single, highly available layer. By moving these concerns out of the business logic, services stay focused on delivering value.
💡 Pro Tip: Treat the gateway as the “north‑south” security perimeter; everything else lives inside the mesh.
2. Introducing Kong: The Open‑Source Cloud‑Native API Gateway – Reverse Proxy & High Availability
Architectural Philosophy: Extensibility and Performance
Kong builds on NGINX‘s event‑driven core while exposing a Lua JIT runtime for plugins. This combination yields nanosecond‑level request dispatch and the ability to write custom logic without recompiling the binary.
Kong vs. Alternatives – Load Balancing, Service Mesh, and Cost
| Feature | Kong 3.5 | NGINX Ingress | AWS API Gateway | Zuul 2 |
|---|---|---|---|---|
| Plugin ecosystem | >300 community plugins | Limited Lua modules | Managed authorizers only | Java‑based filters |
| Cloud‑agnostic | ✅ | ✅ | ❌ (AWS‑only) | ✅ |
| DB‑backed vs. DB‑less | Both | DB‑less only | Managed | DB‑less only |
| Latency overhead (avg) | 3‑5 ms | 2‑4 ms | 7‑12 ms | 5‑9 ms |
Kong’s hybrid model (stateless proxy + optional datastore) gives you flexibility that pure Ingress controllers lack while keeping the operational cost lower than fully managed services.
⚠️ Warning: If you rely on heavy request‑body transformations, NGINX may outperform Kong because its core handles such work natively.
3. Designing for Security from the Ground Up – Authentication, Bot Detection, and Secrets Management
Authentication & Authorization: JWT, OAuth 2.0, and ACL Implementation
# Deploy Kong with the official Helm chart, pinning version 3.5.0
helm repo add kong https://charts.konghq.com
helm repo update
helm install kong kong/kong \
--set image.tag=3.5.0 \
--set env.database=postgres \
--set admin.tls.enabled=true \
--set admin.tls.cert=secret:admin-tls-cert \
--set admin.tls.key=secret:admin-tls-key
- Enable the JWT plugin on the service that handles user profiles.
- Add OAuth 2.0 plugin on the token endpoint.
- Bind ACL groups (
premium,free) to each consumer record.
-- plugins/jwt-auth/handler.lua (Kong 3.5.0)
local BasePlugin = require "kong.plugins.base_plugin"
local jwt = require "resty.jwt"
local JwtAuthHandler = BasePlugin:extend()
JwtAuthHandler.PRIORITY = 1000
JwtAuthHandler.VERSION = "1.0.0"
function JwtAuthHandler:new()
JwtAuthHandler.super.new(self, "jwt-auth")
end
function JwtAuthHandler:access(conf)
JwtAuthHandler.super.access(self)
local token = ngx.req.get_headers()["Authorization"]
if not token then
return kong.response.exit(401, { message = "Missing token" })
end
local _, err = jwt:verify(conf.key_set, token, {
-- enforce HS256 algorithm, check nbf/exp
allowed_algos = { "HS256" },
verify_claims = true,
})
if err then
return kong.response.exit(401, { message = "Invalid token: " .. err })
end
end
return JwtAuthHandler
The plugin validates the JWT, enforces expiration, and aborts early if the token is missing—preventing downstream services from seeing unauthenticated traffic.
Rate Limiting, Bot Detection, and DDoS Mitigation Strategies
Kong’s built‑in rate‑limiting plugin works per consumer, IP, or service. To achieve tiered limits (user tier + endpoint + time‑of‑day), combine the plugin with a custom Lua script:
-- plugins/advanced-rate-limit/handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local redis = require "resty.redis"
local AdvancedRateLimit = BasePlugin:extend()
AdvancedRateLimit.PRIORITY = 900
AdvancedRateLimit.VERSION = "1.2.0"
function AdvancedRateLimit:new()
AdvancedRateLimit.super.new(self, "advanced-rate-limit")
end
function AdvancedRateLimit:access(conf)
AdvancedRateLimit.super.access(self)
local consumer = kong.client.get_consumer()
local path = ngx.var.request_uri
local hour = os.date("%H")
local key = string.format("rl:%s:%s:%s", consumer.id, path, hour)
local red = redis:new()
red:set_timeout(100) -- 100 ms timeout
local ok, err = red:connect("redis-svc", 6379)
if not ok then
kong.log.err("Redis connection failed: ", err)
return kong.response.exit(500, { message = "Rate limit service unavailable" })
end
local current, err = red:get(key)
if current == ngx.null then current = 0 end
if tonumber(current) >= conf.limit then
return kong.response.exit(429, { message = "Too many requests" })
end
red:incr(key)
red:expire(key, 3600) -- reset each hour
end
return AdvancedRateLimit
Key points:
- Store counters in a fast Redis cache (Redis 7.0+).
- The key encodes consumer ID, path, and hour, enabling fine‑grained limits.
- Errors from Redis are logged and turned into a 500 response, protecting the gateway from hidden failures.
For bot detection, enable Kong’s Bot Detection plugin, configure it with a known‑bad‑user‑agent list, and couple it with a WAF like ModSecurity for deeper payload inspection.
Secure Configuration: Managing Secrets and TLS Certificates
- Store TLS certs in Kubernetes Secrets and reference them via the
admin.tls.certandadmin.tls.keyfields. - Enable mTLS between Kong and upstream services by uploading client certs and setting
upstream_sslon each Service object. - Rotate secrets automatically with a
CronJobthat pulls new certs from Vault (v1.14) and patches the Kong secret:
apiVersion: batch/v1
kind: CronJob
metadata:
name: kong-secret-rotator
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: rotator
image: hashicorp/vault:1.14
env:
- name: VAULT_ADDR
value: "https://vault.example.com"
command: ["sh", "-c"]
args:
- |
NEW_CERT=$(vault read -field=certificate secret/kong/cert)
kubectl patch secret kong-admin-tls -p "{\"data\":{\"tls.crt\":\"$(echo -n $NEW_CERT | base64)\"}}"
restartPolicy: OnFailure
4. Architectural Blueprint for Scalability and High Availability – Load Balancing and Caching
Stateless vs. Hybrid Deployment Models
| Model | Config Updates | Performance | Complexity |
|---|---|---|---|
| Stateless (DB‑less) | Immutable, via declarative YAML | Minimal latency (≈2 ms) | Low – no datastore |
| Hybrid (DB‑backed) | Dynamic via Admin API | Slightly higher (≈4 ms) | Medium – need HA DB |
| Full HA (Hybrid + Replication) | Runtime updates + failover | Comparable to stateless if DB tuned | High – requires DB cluster |
Start with DB‑less for PoC; graduate to hybrid when you need live config changes (e.g., feature flag rollouts).
Datastore Choice: PostgreSQL vs. Cassandra for Your Scale
- PostgreSQL 14 offers strong ACID guarantees, excellent for ACLs and rate‑limit tables. Use streaming replication with a synchronous standby for zero‑data‑loss.
- Cassandra 4.0 shines in write‑heavy scenarios and geo‑distributed clusters. Its eventual consistency model suits large, read‑only config caches.
Database Failover & Disaster Recovery
# Example PostgreSQL Patroni cluster (v2.1)
apiVersion: v1
kind: Service
metadata:
name: postgres-primary
spec:
selector:
app: patroni
role: master
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: patroni
spec:
serviceName: "postgres"
replicas: 3
selector:
matchLabels:
app: patroni
template:
metadata:
labels:
app: patroni
spec:
containers:
- name: patroni
image: patroni:2.1
env:
- name: PATRONI_SCOPE
value: postgres
- name: PATRONI_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: PATRONI_RESTAPI_LISTEN
value: "0.0.0.0:8008"
- name: PATRONI_POSTGRESQL_DATA_DIR
value: /data/postgres
volumeMounts:
- name: pgdata
mountPath: /data
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: pgdata-pvc
Patroni automatically promotes a replica on primary failure within ~2 seconds. Pair it with pgBackRest (v1.7) for incremental backups stored in an S3 bucket. Schedule nightly backups and a weekly full snapshot.
For Cassandra, use K8ssandra (v1.16) which provisions a multi‑region cluster with automatic repair and reaper for anti‑entropy.
Horizontal Scaling: Load Balancing Kong Gateways with Kong Admin API
Deploy Kong as a DaemonSet (one pod per node) for low latency local routing, or as a Deployment behind a LoadBalancer for centralized control.
apiVersion: apps/v1
kind: Deployment
metadata:
name: kong
spec:
replicas: 4
selector:
matchLabels:
app: kong
template:
metadata:
labels:
app: kong
spec:
containers:
- name: kong
image: kong:3.5.0-alpine
env:
- name: KONG_DATABASE
value: "postgres"
- name: KONG_PG_HOST
value: "postgres-primary"
- name: KONG_ADMIN_LISTEN
value: "0.0.0.0:8444 ssl"
ports:
- containerPort: 8000 # proxy
- containerPort: 8444 # admin
Use a Service of type LoadBalancer (kong-proxy) to expose port 80/443. The Admin API (8444) should be bound to an internal IP and protected with the mTLS setup described earlier.
Caching Strategies to Reduce Upstream Load
- Response caching plugin (v0.35) stores GET responses in Redis (TTL = 60 s).
- Cache‑Key set to
service.path + request.headers["Accept-Language"]to honor localisation. - For write‑heavy resources, enable Cache‑Control splits on the upstream to let Kong respect
Cache-Control: privateheaders.
💡 Pro Tip: Combine response caching with Kong’s upstream keepalive (set
upstream_keepaliveto 60) to maintain persistent TCP connections to services, shaving 1‑2 ms per request.
5. Implementation Deep Dive: Key Kong Plugins and Configuration – Plugin Development and IaC
Core Plugin Configuration: Rate Limiting, Proxy, and Logging
# Apply a declarative config file (Kong DB‑less mode) using Kong 3.5.0
cat > kong.yml <<'EOF'
_format_version: "3.0"
services:
- name: user-service
url: http://user-svc.nileshblog.tech
routes:
- name: user-route
paths:
- /api/v1/users
plugins:
- name: jwt
config:
claim: sub
- name: rate-limiting
config:
minute: 200
hour: 5000
- name: file-log
config:
path: /var/log/kong/user-access.log
EOF
kong config db_import kong.yml
The file‑log plugin writes raw request data to a volume mounted from the host, preserving audit trails for compliance audits.
Advanced Use Cases with Custom Plugins (Lua & Go)
Lua Example – Multi‑Variable Rate Limiter (shown earlier)
Deploy the plugin as a custom plugin container (kong/custom-plugin:latest) that mounts /usr/local/kong/custom_plugins.
Go Example – High‑Performance JWT Verifier
// go.mod (Kong 3.5.0, Go 1.22)
module github.com/nileshblog/kong-go-jwt
go 1.22
require (
github.com/kong/go-pdk v0.13.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
)
package main
import (
"github.com/kong/go-pdk"
"github.com/kong/go-pdk/server"
"github.com/dgrijalva/jwt-go"
"log"
)
func JWTHandler(kong *pdk.PDK) {
tokenStr, err := kong.Request.GetHeader("Authorization")
if err != nil {
kong.Response.Exit(401, []byte(`{"msg":"Missing token"}`), nil)
return
}
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
// HS256 only
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return []byte("super-secret-key"), nil
})
if err != nil || !token.Valid {
kong.Response.Exit(401, []byte(`{"msg":"Invalid token"}`), nil)
return
}
// Token valid – continue downstream
}
func main() {
err := server.StartServer(JWTHandler)
if err != nil {
log.Fatalf("failed to start plugin server: %v", err)
}
}
Compile with GOOS=linux GOARCH=amd64 go build -o /usr/local/kong/custom_plugins/go-jwt . and mount the binary in the Kong container. This Go plugin runs outside the NGINX worker process, freeing the Lua VM from CPU‑heavy cryptographic work.
Automating Deployments with Infrastructure‑as‑Code
Terraform (v1.6) snippet to provision Kong on EKS (v1.27) with an ALB Ingress Controller:
provider "aws" {
region = "us-east-1"
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
cluster_name = "nileshblog-eks"
cluster_version = "1.27"
subnets = module.vpc.private_subnets
vpc_id = module.vpc.vpc_id
node_groups = {
default = {
desired_capacity = 4
instance_type = "m5.large"
}
}
}
resource "helm_release" "kong" {
name = "kong"
repository = "https://charts.konghq.com"
chart = "kong"
version = "2.18.0"
namespace = "kong"
set {
name = "image.tag"
value = "3.5.0"
}
set {
name = "admin.type"
value = "NodePort"
}
set {
name = "env.database"
value = "postgres"
}
depends_on = [module.eks]
}
Running terraform apply creates a fully HA Kong deployment, backed by a PostgreSQL RDS multi‑AZ instance.
6. Observability and Maintenance in Production – Metrics, Logging, and Deploy Strategies
Metrics Collection, Dashboards, and Alerting (Prometheus 2.45 / Grafana 10)
- Enable the Prometheus plugin on the Kong service.
- Scrape
/metricsfrom each Kong pod.
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kong
labels:
release: prometheus
spec:
selector:
matchLabels:
app: kong
endpoints:
- port: proxy
path: /metrics
interval: 15s
Create a Grafana dashboard that visualizes:
- Request latency (
kong_http_latency_ms) per route. - Rate‑limit rejections (
kong_rate_limiting_total{status="rejected"}). - Cache hit ratio (
kong_cache_hits / kong_cache_total).
Set alerts:
- Latency > 150 ms for >5 % of requests → page on‑call.
- Cache miss > 30 % → investigate upstream performance.
Centralized Logging for Audit Trails and Troubleshooting
Configure Kong’s syslog plugin to ship JSON logs to a Loki stack (v2.9).
# syslog plugin config via Admin API
curl -i -X POST http://localhost:8444/services/user-service/plugins \
--data "name=syslog" \
--data "config.host=loki-logging.nileshblog.tech" \
--data "config.port=1514" \
--data "config.scheme=tcp" \
--data "config.timeout=5000" \
--data "config.log_level=info"
In Loki, query {{app="kong"}} | json to filter by consumer.id for compliance reports.
Versioning, Canary Releases, and Blue‑Green Deployments
- Canary: Deploy a new Kong version (e.g., 3.6.0) to 10 % of pods, route a fraction of traffic using a weighted service (
weight: 10). Observe metrics before full rollout. - Blue‑Green: Spin up a second Kong Deployment (
kong-green) with new plugins, switch the external LoadBalancer fromkong-bluetokong-greenin one atomic operation.
⚠️ Warning: Never mix DB‑less config files with a DB‑backed cluster; the node will reject startup with “Database configuration conflict”.
7. Real‑World Case Studies and Lessons Learned – From nileshblog.tech to Production
Trade‑off Analysis: Latency vs. Consistency in Rate Limiting
| Approach | Latency Impact | Consistency | Maintenance |
|---|---|---|---|
| Redis‑backed counters | +1 ms (network) | Strong (single key) | Low (managed) |
| Postgres row lock | +3 ms (SQL) | Strong (transaction) | Medium (migration) |
| In‑memory Lua counters | +0.5 ms | Weak (per‑worker) | High (state sync) |
For nileshblog.tech, we chose Redis because it offered sub‑millisecond latency while keeping a single source of truth across all Kong nodes. The trade‑off: we had to implement a TTL reset to avoid stale keys.
Cost‑Benefit: Self‑Managed Kong vs. Kong Enterprise (Kong Cloud)
| Metric | Self‑Managed (K8s) | Kong Cloud (Enterprise) |
|---|---|---|
| Monthly Ops Cost | $1,200 (infra) | $2,500 (SaaS) |
| Feature Set | Core + community | Advanced analytics, RBAC, DevPortal |
| Vendor Lock‑in | Low | Medium (proprietary UI) |
| Support SLA | Community (24 h) | 99.9 % response (Enterprise) |
When traffic exceeded 200 k RPS, the managed service’s auto‑scaling saved us three weeks of engineering effort. For smaller workloads, self‑managed remains the cheaper choice.
Performance Benchmarks and Capacity Planning
Using k6 (v0.50) we generated a steady load of 30 k RPS against a 4‑node Kong cluster (3.5.0, DB‑backed PostgreSQL). Results:
- Average latency: 4.2 ms (95th percentile: 6.1 ms)
- CPU: 68 % per pod (2 vCPU limit)
- Memory: 750 MiB (peak)
- Plugin overhead: Adding the custom Go JWT plugin added ~0.8 ms per request.
Increasing the replica count to 6 reduced latency to 3.4 ms and kept CPU under 55 %. This demonstrates linear scalability when the datastore is sized appropriately (Postgres with 3 synchronous replicas).
My take: The moment you start adding heavy plugins, the bottleneck shifts from the network to the Lua/Go runtime. Profiling each plugin in isolation before merging them into production saves weeks of mystery debugging.
Common Errors & Fixes
| Symptom | Likely Cause | Fix |
|---|---|---|
502 Bad Gateway from Kong | Admin API reachable only over HTTP (no TLS) | Enable admin.tls.enabled=true and restrict access to internal CIDRs. |
| Rate‑limit plugin always returns 429 | Redis key never expires (TTL missing) | Add expire call or set config.policy=redis with config.limit_by=consumer. |
| JWT verification fails intermittently | Clock skew between Kong node and auth server | Sync NTP on all nodes or set leeway in JWT plugin config. |
| Kong pod crashes with “out of memory” | Large request bodies held in memory by a custom plugin | Stream body parsing, set config.body_filter=off, or increase pod memory limit. |
Config changes not applied after kong reload | Using DB‑less mode with cached declarative config | Run kong config db_import again or switch to DB‑backed for live updates. |
CTA
If you found this guide helpful, drop a comment below, share it with your team, or subscribe to the newsletter at nileshblog.tech for more deep‑dive articles on API gateways, microservices, and cloud‑native engineering.
Author Bio:
I’m Nilesh Raut, a Software Development Engineer with 2+ years of experience, specializing in Go, JavaScript, Python, Docker, Kubernetes, Git, Jenkins, microservices, and system design (LLD/HLD), backed by a strong foundation in data structures and algorithms. Alongside my engineering journey, I bring 4+ years of hands‑on experience in SEO, where I’ve worked extensively on content strategy, keyword research, technical SEO, and organic growth, helping products and businesses scale efficiently by aligning solid technology with search‑driven performance.



