Privacy Tools Guide

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

2. Configure PostgreSQL

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

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

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

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


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

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

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

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

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

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