Last updated: March 22, 2026

Secure API Gateway Setup with Kong

Kong is an open-source API gateway that sits in front of your services and enforces authentication, rate limiting, logging, and TLS. without touching application code. This guide covers a production-grade setup using Kong Gateway (OSS) 3.x on Ubuntu 24.04 with PostgreSQL, deployed behind Nginx for TLS termination.

Architecture Overview

Client → Nginx (TLS) → Kong (8000) → Upstream Services
                   ↓
            Kong Admin (8001, localhost-only)

Kong handles - JWT validation, rate limiting, IP allowlisting, request logging. Nginx handles: certificate renewal, HTTPS, and hiding Kong’s admin port.


  1. Install Kong and PostgreSQL
Add Kong repository
curl -fsSL https://packages.konghq.com/public/gateway-38/gpg.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/kong-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/kong-archive-keyring.gpg] \
  https://packages.konghq.com/public/gateway-38/deb/ubuntu \
  $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/kong.list

sudo apt update && sudo apt install -y kong-enterprise-edition || \
sudo apt install -y kong

Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib

  1. Configure PostgreSQL
sudo -u postgres psql <<'SQL'
CREATE USER kong WITH PASSWORD 'StrongPassHere42!';
CREATE DATABASE kong OWNER kong;
SQL

  1. Configure Kong
sudo cp /etc/kong/kong.conf.default /etc/kong/kong.conf
sudo tee /etc/kong/kong.conf > /dev/null <<'EOF'
Database
database = postgres
pg_host = 127.0.0.1
pg_port = 5432
pg_user = kong
pg_password = StrongPassHere42!
pg_database = kong

Proxy (public)
proxy_listen = 0.0.0.0:8000
Admin (local only. NEVER expose to internet)
admin_listen = 127.0.0.1:8001

TLS. Kong itself can do TLS, but we'll use Nginx in front
proxy_ssl = off

Log level
log_level = warn
EOF

Initialize database
sudo kong migrations bootstrap -c /etc/kong/kong.conf

Start Kong
sudo systemctl enable --now kong
sudo systemctl status kong

Test Kong is running:

curl -s http://127.0.0.1:8001/ | python3 -m json.tool | grep version

  1. Add a Service and Route

Services represent upstream APIs. Routes define what paths/hosts map to a service.

ADMIN="http://127.0.0.1:8001"

Create a service pointing to your backend
curl -s -X POST $ADMIN/services \
  -d name=my-api \
  -d url=http://127.0.0.1:3000

Create a route for that service
curl -s -X POST $ADMIN/services/my-api/routes \
  -d 'paths[]=/api' \
  -d strip_path=false

Verify
curl -s $ADMIN/services | python3 -m json.tool

  1. Enable Rate Limiting

Protect backend services from brute-force and DoS:

Global rate limiting - 100 requests per minute per IP
curl -s -X POST $ADMIN/plugins \
  -d name=rate-limiting \
  -d config.minute=100 \
  -d config.policy=local \
  -d config.hide_client_headers=false

Per-service rate limiting - stricter limit for sensitive endpoint
curl -s -X POST $ADMIN/services/my-api/plugins \
  -d name=rate-limiting \
  -d config.minute=30 \
  -d config.hour=500 \
  -d config.policy=redis \
  -d config.redis_host=127.0.0.1 \
  -d config.redis_port=6379

Use Redis-backed policy in multi-node deployments to share rate limit state across Kong instances.


  1. Enable JWT Authentication
Enable the JWT plugin on your service
curl -s -X POST $ADMIN/services/my-api/plugins \
  -d name=jwt

Create a consumer
curl -s -X POST $ADMIN/consumers \
  -d username=api-client-1

Generate a JWT credential
curl -s -X POST $ADMIN/consumers/api-client-1/jwt

Response includes:
{
  "key": "rsa_key_or_hs256_key",
  "secret": "your_shared_secret",
  "algorithm": "HS256"
}

Clients must include the JWT in the Authorization - Bearer <token> header. Generate tokens in your application:

import jwt, time

payload = {
    "iss": "rsa_key_from_kong",   # must match the 'key' field from Kong
    "sub": "api-client-1",
    "iat": int(time.time()),
    "exp": int(time.time()) + 3600,
}
token = jwt.encode(payload, "your_shared_secret", algorithm="HS256")
print(token)

  1. Enable IP Restriction

Block or allow specific IP ranges:

Allow only your office IP and VPN exit node
curl -s -X POST $ADMIN/services/my-api/plugins \
  -d name=ip-restriction \
  -d 'config.allow[]=203.0.113.10' \
  -d 'config.allow[]=10.0.0.0/8'

Block a known bad actor IP range
curl -s -X POST $ADMIN/plugins \
  -d name=ip-restriction \
  -d 'config.deny[]=198.51.100.0/24'

  1. Enable Request Logging
Log to file (for local ELK or Splunk ingest)
curl -s -X POST $ADMIN/plugins \
  -d name=file-log \
  -d config.path=/var/log/kong/access.log \
  -d config.reopen=true

Or HTTP log (forward to logging service)
curl -s -X POST $ADMIN/plugins \
  -d name=http-log \
  -d config.http_endpoint=http://log-aggregator:9200/kong-logs \
  -d config.method=POST \
  -d config.timeout=10000 \
  -d config.keepalive=60000

  1. TLS Termination with Nginx
/etc/nginx/sites-available/api-gateway
server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_session_cache   shared:SSL:10m;

    # Forward to Kong proxy
    location / {
        proxy_pass         http://127.0.0.1:8000;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto https;
        proxy_read_timeout 60s;
    }
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri;
}
sudo certbot --nginx -d api.example.com
sudo nginx -t && sudo systemctl reload nginx

  1. Harden Kong Admin

The admin API must never be internet-accessible. Restrict it further:

Bind to Unix socket instead of TCP (if single-node)
In kong.conf:
admin_listen = unix:/run/kong/kong_admin.sock

If using TCP, add firewall rule
sudo ufw deny 8001
sudo ufw allow from 127.0.0.1 to any port 8001

Add basic auth to admin via Nginx proxy (optional)
Only expose admin behind VPN or bastion host

  1. Monitor with Prometheus
Enable Prometheus plugin globally
curl -s -X POST $ADMIN/plugins \
  -d name=prometheus

Kong exposes metrics at :8001/metrics
Add to prometheus.yml:
- job_name: kong
  static_configs:
    - targets: ['127.0.0.1:8001']
  metrics_path: /metrics

Key metrics - kong_http_requests_total, kong_latency_bucket, kong_bandwidth_bytes_total.


Security Checklist


Related Articles