← Back to DevBytes

Kong API Gateway Security Hardening and Best Practices

Introduction to Kong API Gateway Security Hardening

Kong API Gateway is a high-performance, extensible gateway that sits at the edge of your microservice infrastructure, handling cross-cutting concerns like authentication, rate limiting, and logging. Out of the box, Kong ships with sensible defaults, but in production environments, those defaults are rarely sufficient. Security hardening is the process of systematically locking down Kong's configuration, plugins, admin interface, and underlying infrastructure to minimize the attack surface and protect your backend services from malicious actors.

This tutorial walks you through a complete security hardening checklist for Kong Gateway, covering every layer from the Admin API to plugin-specific configurations, with practical code examples you can apply directly to your deployment.

Why Kong Security Hardening Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

An unsecured API gateway is a single point of failure that can expose all your backend services. Consider these real-world risks:

Hardening Kong is not optional—it's a fundamental requirement for any production deployment handling sensitive data or business-critical traffic.

1. Securing the Admin API

1.1 Disable the Admin API on Public Interfaces

The most critical hardening step is ensuring Kong's Admin API is never exposed to the public internet or even your internal corporate network without strict access controls. In Kong's configuration file (kong.conf), bind the admin listener exclusively to localhost:

# kong.conf — Admin API hardening
admin_listen = 127.0.0.1:8001

# Optionally, use a Unix socket instead of a TCP port for maximum isolation
# admin_listen = unix:/var/run/kong-admin.sock

# Disable the Admin API GUI (Kong Manager) if not needed
admin_gui = off

# Disable the Admin API development sandbox endpoints
admin_gui_auth_conf = {}
admin_gui_auth_header = off

If you must expose the Admin API remotely, front it with a tightly restricted reverse proxy (like Nginx) that enforces mutual TLS, or use Kong's built-in loopback-only restriction combined with SSH tunneling for administrative access.

1.2 Enforce Authentication on the Admin API

Kong supports multiple authentication methods for the Admin API. The recommended approach for production is to use RBAC (Role-Based Access Control) with a strong authentication backend:

# kong.conf — Admin API authentication
admin_gui_auth = basic-auth
# or for LDAP-backed authentication:
# admin_gui_auth = ldap-auth

# If using Kong Manager with RBAC (Enterprise feature)
enforce_rbac = on
admin_gui_auth_conf = {
  "session_secret": "generate-a-cryptographically-random-64-char-string-here",
  "session_cookie_name": "kong_admin_session",
  "session_cookie_secure": true,
  "session_cookie_httponly": true,
  "session_cookie_samesite": "Strict"
}

For the Admin API itself (not just the GUI), add client certificate validation or a shared secret header that only your automation tools know:

# Require a secret header on every Admin API call
# This is enforced at the reverse proxy layer in front of Kong
# Example Nginx configuration snippet:

location /admin/ {
    proxy_pass http://127.0.0.1:8001/;
    proxy_set_header Host $host;
    
    # Require a pre-shared secret header
    set $admin_secret "$http_x_admin_secret";
    if ($admin_secret != "your-256-bit-random-secret") {
        return 403;
    }
    
    # Require mutual TLS
    ssl_verify_client on;
    ssl_client_certificate /etc/ssl/certs/admin_ca.crt;
}

1.3 Disable Unnecessary Admin API Routes

Not every environment needs all Admin API endpoints. Use a firewall or reverse proxy to block routes you never use, such as the /tags endpoint or the developer playground:

# Block unnecessary Admin API routes at the proxy layer
location ~ ^/admin/(tags|debug|dev) {
    return 404;
}

2. Hardening Kong Plugins

2.1 Rate Limiting for DDoS Protection

Rate limiting is your first line of defense against volumetric attacks. Configure the rate-limiting or rate-limiting-advanced plugin globally, then fine-tune per-route:

# Apply a global rate limit via Kong's declarative configuration (kong.yml)
# This example uses the rate-limiting-advanced plugin for more granular control

_format_version: "3.0"
services:
  - name: public-api
    url: http://backend-service:8080
    routes:
      - name: public-route
        paths:
          - /api/public
        plugins:
          - name: rate-limiting-advanced
            config:
              # Limit to 100 requests per second per consumer
              limit:
                - second=100
              # Burst size of 20 requests
              window_size:
                - 10
              # Return 429 with a Retry-After header
              retry_after_jitter_max: 10
              # Use Redis for synchronized counters across nodes
              strategy: redis
              redis:
                host: redis-cluster.internal
                port: 6379
                database: 0
                password: "${REDIS_PASSWORD}"
                ssl: true
                server_name: "redis.internal"

For critical endpoints like authentication or payment processing, apply stricter, per-route limits:

# Per-route strict rate limiting for sensitive endpoints
routes:
  - name: auth-route
    paths:
      - /api/auth
    plugins:
      - name: rate-limiting-advanced
        config:
          limit:
            - minute=10
            - hour=50
          # Sync counters across all Kong nodes via Redis
          strategy: redis
          # Hide rate limit headers to avoid leaking limits to attackers
          hide_client_headers: true

2.2 Authentication Plugins: Key-Auth, OIDC, and JWT

Never leave routes unprotected. Kong supports multiple authentication mechanisms. Here's how to harden each one:

# Key Authentication — simple but requires careful key management
plugins:
  - name: key-auth
    config:
      # Hide credentials from upstream services
      hide_credentials: true
      # Require keys to be sent in headers, not query strings
      key_in_header: true
      key_in_query: false
      key_in_body: false
      # Custom header name to avoid "Authorization" header conflicts
      key_names:
        - X-API-Key
      # Run authentication before other plugins
      run_before: []
# JWT (JSON Web Token) Authentication hardening
plugins:
  - name: jwt
    config:
      # Require tokens in Authorization header only
      header_names:
        - Authorization
      # Reject tokens passed in query parameters (log leakage risk)
      cookie_names: []
      # Validate the issuer claim
      claims_to_verify:
        - iss
        - aud
      # Enforce algorithm constraints — never allow 'none'
      algorithms:
        - RS256
        - ES256
      # Maximum token lifetime override
      maximum_expiration: 3600
      # Hide credentials from upstream
      hide_credentials: true
      # URI sources disabled to prevent token leakage in logs
      uri_param_names: []
# OpenID Connect (OIDC) hardening with enterprise plugin
plugins:
  - name: openid-connect
    config:
      # Require PKCE (Proof Key for Code Exchange) for public clients
      proof_of_possession: true
      # Validate the nonce to prevent replay attacks
      verify_nonce: true
      # Use state parameter to prevent CSRF
      use_state: true
      # Redirect only to whitelisted URIs after logout
      logout_redirect_uri: "https://app.example.com/post-logout"
      # Enforce HTTPS for all redirect URIs
      enforce_https: true
      # Reject tokens without a valid 'aud' claim matching this gateway
      audience_claim: "kong-gateway"
      # Scopes must be explicitly whitelisted
      scopes:
        - openid
        - profile
      # Session cookie hardening
      session_cookie_httponly: true
      session_cookie_secure: true
      session_cookie_samesite: "Strict"

2.3 CORS Hardening

The CORS plugin is frequently misconfigured. Never use origins: * in production. Always whitelist specific origins:

plugins:
  - name: cors
    config:
      # Whitelist specific origins — NEVER use "*"
      origins:
        - https://app.example.com
        - https://admin.example.com
      # Only allow necessary methods
      methods:
        - GET
        - POST
        - PUT
      # Restrict headers to those your app actually uses
      headers:
        - Content-Type
        - Authorization
        - X-Request-ID
      # Expose only safe headers to client-side JavaScript
      exposed_headers:
        - X-Request-ID
        - X-RateLimit-Remaining
      # Credentials support
      credentials: true
      # Cache preflight results for a reasonable window
      max_age: 3600
      # Preflight continues only if all conditions pass
      preflight_continue: false

2.4 IP Restriction for Internal Endpoints

Some routes should only be accessible from internal networks or specific IP ranges. Use the ip-restriction plugin:

# Restrict admin-oriented endpoints to internal IPs only
routes:
  - name: internal-metrics
    paths:
      - /internal/metrics
    plugins:
      - name: ip-restriction
        config:
          # Allow only internal networks
          allow:
            - 10.0.0.0/8
            - 172.16.0.0/12
            - 192.168.0.0/16
          # Explicitly deny everything else
          deny: []
          # Return 403 with no additional info
          status: 403
          message: "Access denied"

2.5 Bot Detection and Advanced Threat Protection

Kong's enterprise Bot Detection plugin can identify and block automated scrapers, credential-stuffing bots, and vulnerability scanners:

plugins:
  - name: bot-detection
    config:
      # Allow known good bots (search engines)
      allow:
        - "Googlebot"
        - "Bingbot"
      # Block based on regex patterns in User-Agent
      deny:
        - ".*scanner.*"
        - ".*sqlmap.*"
        - ".*nikto.*"
      # Enable machine-learning-based detection (requires Kong Brain)
      use_ml: true

3. TLS/SSL Configuration

3.1 Terminate TLS at Kong

Kong should terminate TLS at the proxy level. Use strong cipher suites and modern TLS versions only:

# kong.conf — TLS hardening
proxy_listen = 0.0.0.0:443 ssl
proxy_listen = 0.0.0.0:80  # Only if you need HTTP-to-HTTPS redirect

# TLS certificate and key paths
ssl_cert = /etc/kong/ssl/kong-cert.pem
ssl_cert_key = /etc/kong/ssl/kong-key.pem

# Enforce TLS 1.2 minimum (disable TLS 1.0, 1.1)
ssl_protocols = TLSv1.2 TLSv1.3

# Strong cipher suites — no weak ciphers, no export-grade ciphers
ssl_ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256

# Prefer server cipher suite order over client's
ssl_prefer_server_ciphers = on

# Enable session tickets for performance, but rotate keys regularly
ssl_session_tickets = on
ssl_session_timeout = 1d

3.2 Mutual TLS (mTLS) for Service-to-Service Communication

For zero-trust architectures, enforce mTLS between Kong and upstream services:

# Upstream mTLS configuration in service definition
services:
  - name: secure-upstream
    url: https://upstream.internal:8443
    client_certificate:
      # The certificate Kong presents to the upstream
      id: "certificate-uuid-here"
    routes:
      - name: secure-route
        paths:
          - /secure

# Create the client certificate via Admin API
# curl -X POST http://localhost:8001/certificates \
#   -d 'cert=@client-cert.pem' \
#   -d 'key=@client-key.pem' \
#   -d 'snis=upstream.internal'

3.3 HTTP Strict Transport Security (HSTS)

Add the HSTS response header to force browsers to always use HTTPS:

# Using the response-transformer plugin to inject HSTS header
plugins:
  - name: response-transformer
    config:
      add:
        headers:
          - "Strict-Transport-Security:max-age=31536000; includeSubDomains; preload"
          - "X-Content-Type-Options:nosniff"
          - "X-Frame-Options:DENY"
          - "X-XSS-Protection:1; mode=block"
          - "Referrer-Policy:strict-origin-when-cross-origin"
          - "Permissions-Policy:geolocation=(), microphone=(), camera=()"

4. Database Security

Kong stores its configuration in PostgreSQL (or Cassandra for older deployments). The database connection must be hardened:

# kong.conf — Database connection hardening
pg_host = 127.0.0.1  # Bind to localhost if DB is co-located
pg_port = 5432
pg_user = kong_readonly  # Use least-privilege principle
pg_password = "${DB_PASSWORD}"  # Use environment variables, never hardcode
pg_database = kong
pg_ssl = on  # Always encrypt database connections
pg_ssl_verify = on  # Verify the database server certificate
pg_max_concurrent_queries = 50  # Prevent connection exhaustion

Never expose the PostgreSQL port to the public internet. Use cloud-native secrets management (AWS Secrets Manager, HashiCorp Vault) to inject DB_PASSWORD at runtime rather than storing it in configuration files.

5. Logging and Monitoring for Security Events

5.1 Enable Audit Logging

Kong Enterprise includes an audit logging plugin that records all Admin API activity. In the open-source version, you can achieve similar results with the file-log or http-log plugins:

# Log all requests with security-relevant metadata
plugins:
  - name: file-log
    config:
      path: /var/log/kong/security_audit.log
      # Log only requests that are blocked or have authentication failures
      # This is done via a custom logging criteria in the plugin
      custom_fields:
        - name: security_event
          value: true
        - name: client_ip
          value_from: "$remote_addr"
        - name: user_agent
          value_from: "$http_user_agent"
        - name: authenticated_user
          value_from: "$authenticated_consumer_id"
# Ship security logs to a centralized SIEM via HTTP
plugins:
  - name: http-log
    config:
      endpoint: https://siem.internal.example.com/ingest
      method: POST
      timeout: 3000
      headers:
        - "Authorization:Bearer ${SIEM_INGEST_TOKEN}"
        - "Content-Type:application/json"
      # Include all security-relevant fields
      custom_fields:
        - name: kong_route
          value_from: "$route_name"
        - name: kong_service
          value_from: "$service_name"
        - name: status_code
          value_from: "$status"
        - name: latency_ms
          value_from: "$latency"

5.2 Alert on Anomalous Patterns

Use Kong's Prometheus plugin to export metrics, then configure alerts for security anomalies:

# kong.conf — Enable Prometheus metrics
prometheus = on
prometheus_metrics = all

Then in your Prometheus alerting rules:

# Prometheus alert rules for Kong security anomalies
groups:
  - name: kong_security
    rules:
      - alert: KongHighRateLimitHits
        expr: rate(kong_http_requests_total{status="429"}[5m]) > 10
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "High rate of rate-limited requests on {{ $labels.route }}"
          
      - alert: KongAuthFailures
        expr: rate(kong_http_requests_total{status="401"}[5m]) > 5
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Elevated authentication failures — possible credential stuffing attack"

6. Network and Infrastructure Hardening

6.1 Run Kong as a Non-Root User

By default, Kong may run as the nobody user, but you should explicitly set a dedicated, unprivileged user:

# kong.conf — Run as unprivileged user
kong_env:
  user: kong
  group: kong

# In your Dockerfile or systemd service file:
# USER kong
# Or in systemd:
# [Service]
# User=kong
# Group=kong
# NoNewPrivileges=yes
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# ProtectSystem=strict
# ProtectHome=yes

6.2 Enable Linux Kernel Hardening Features

If running Kong directly on Linux (not in containers), leverage kernel security features:

# Systemd service unit hardening for Kong
[Service]
# Restrict capabilities to only what's needed for binding to port 8000/8443
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Prevent privilege escalation
NoNewPrivileges=yes
# Mount /usr, /etc read-only after startup
ReadOnlyPaths=/usr /etc/kong
# Restrict write access to log and PID directories only
ReadWritePaths=/var/log/kong /var/run/kong /tmp
# Private /tmp directory
PrivateTmp=yes
# Protect kernel tunables
ProtectKernelTunables=yes
ProtectKernelModules=yes
# Restrict system calls with a seccomp filter
SystemCallFilter=~@clock @debug @module @reboot @swap
# Disable network namespace access
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK

6.3 Container-Specific Hardening (Docker/Kubernetes)

When running Kong in containers, apply these security contexts:

# Kubernetes Pod Security Context for Kong
apiVersion: v1
kind: Pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: kong
      image: kong:3.6
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
          add:
            - NET_BIND_SERVICE
        # volumes for writable paths
      volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: logs
          mountPath: /var/log/kong
        - name: runtime
          mountPath: /var/run/kong
  volumes:
    - name: tmp
      emptyDir: {}
    - name: logs
      emptyDir: {}
    - name: runtime
      emptyDir: {}

7. Secrets Management and Configuration Hardening

7.1 Never Hardcode Secrets in Configuration

Kong supports environment variable interpolation in its configuration files. Always inject secrets at runtime:

# kong.conf — Use environment variables for all secrets
pg_password = ${KONG_PG_PASSWORD}
admin_gui_auth_conf = {
  "session_secret": "${KONG_ADMIN_SESSION_SECRET}"
}

# Declarative config also supports env var interpolation
# kong.yml
services:
  - name: external-api
    url: https://third-party-api.example.com
    plugins:
      - name: aws-lambda
        config:
          aws_key: "${AWS_ACCESS_KEY_ID}"
          aws_secret: "${AWS_SECRET_ACCESS_KEY}"
          # NEVER put actual keys here

Inject these environment variables from a secrets manager at container startup:

# Docker run with secrets injection
docker run -d \
  --name kong \
  -e KONG_PG_PASSWORD=$(aws secretsmanager get-secret-value --secret-id kong/db/password --query SecretString --output text) \
  -e KONG_ADMIN_SESSION_SECRET=$(openssl rand -hex 32) \
  kong:latest

7.2 Rotate Secrets Regularly

Implement automated rotation for:

# Enforce API key expiry in key-auth plugin
plugins:
  - name: key-auth
    config:
      hide_credentials: true
      key_in_header: true
      # Keys expire after 90 days
      key_expiration: 7776000
      # Require keys to be at least 32 characters
      key_min_length: 32

8. Hardening Declarative Configuration and GitOps

When using Kong's declarative configuration (kong.yml) in a GitOps workflow, protect the configuration pipeline:

# .github/workflows/kong-security-scan.yml
# Automated security scanning of Kong configuration before deployment
name: Kong Config Security Scan
on:
  pull_request:
    paths:
      - 'kong.yml'
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Validate Kong Config
        run: |
          # Check for dangerous patterns in kong.yml
          ! grep -q 'origins: \*' kong.yml && echo "CORS OK" || exit 1
          ! grep -q 'algorithms:.*none' kong.yml && echo "JWT algorithms OK" || exit 1
          ! grep -q 'key_in_query: true' kong.yml && echo "Key in query disabled OK" || exit 1
      - name: Kong Ingress Controller Validation
        run: |
          # Dry-run the configuration through Kong
          kong check config kong.yml

9. Complete Hardening Checklist

Use this checklist as a pre-deployment verification for every Kong instance:

10. Testing Your Hardened Configuration

After applying hardening measures, validate your configuration with penetration testing:

# Basic security validation script for Kong
#!/bin/bash
# Test 1: Admin API should be unreachable from external network
curl -v --max-time 3 http://public-ip:8001/status && echo "FAIL: Admin API exposed" || echo "PASS: Admin API protected"

# Test 2: Unauthenticated requests should receive 401
curl -v --max-time 3 http://public-ip:8000/api/protected && echo "FAIL: Route not authenticated" || echo "PASS: Authentication enforced"

# Test 3: CORS should not echo wildcard origin
curl -v -H "Origin: https://evil.example.com" http://public-ip:8000/api/public -s -o /dev/null -w "%header{access-control-allow-origin}" | grep -q "evil" && echo "FAIL: CORS allows any origin" || echo "PASS: CORS restricted"

# Test 4: Security headers present
curl -s -I http://public-ip:8000/api/public | grep -q "Strict-Transport-Security" && echo "PASS: HSTS present" || echo "FAIL: HSTS missing"

# Test 5: Rate limiting triggers after threshold
for i in $(seq 1 150); do
  curl -s -o /dev/null -w "%{http_code}\n" http://public-ip:8000/api/public
done | sort | uniq -c | grep 429 && echo "PASS: Rate limiting active" || echo "FAIL: Rate limiting not enforcing"

Conclusion

Kong API Gateway security hardening is a multi-layered discipline that spans network configuration, plugin hardening, secrets management, operating system controls, and continuous validation. The steps outlined in this tutorial form a defense-in-depth strategy: even if one layer is compromised, subsequent layers continue to protect your backend services. Start with the highest-impact items—locking down the Admin API and enforcing authentication on every route—then progressively work through the checklist. Integrate security scanning into your CI/CD pipeline so that every configuration change is validated before deployment. With these hardening measures in place, Kong becomes not just a gateway, but a robust security enforcement point that actively defends your infrastructure against the most common attack vectors in modern API-driven architectures.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles