← Back to DevBytes

KrakenD API Gateway Security Hardening and Best Practices

KrakenD API Gateway Security Hardening: A Comprehensive Guide

KrakenD is a high-performance, stateless API gateway designed for microservices architectures. Its declarative configuration model and extensive middleware ecosystem make it a powerful entry point for your backend services. However, as the single ingress point for all client traffic, the gateway becomes a critical security boundary — a compromise here exposes everything behind it. This tutorial walks you through practical security hardening techniques, from basic configuration hygiene to advanced threat mitigation, with complete code examples you can deploy today.

What Is KrakenD API Gateway Security Hardening?

Security hardening in KrakenD means systematically reducing the attack surface of the gateway itself and enforcing strict policies on every request that passes through it. This includes:

Why Security Hardening Matters

The API gateway sits at a privileged network position. Without hardening, it becomes a single point of failure and a high-value target. Attackers can exploit weak gateways to:

Hardening the gateway means you stop these threats at the perimeter, protecting both your infrastructure and your users' data. It also helps meet compliance requirements like PCI-DSS, GDPR, and SOC2 by enforcing consistent security controls across all API traffic.

1. Authentication & Authorization at the Gateway Edge

🚀 Deploy your AI agent in 10 minutes

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

Try it free →

KrakenD supports multiple auth mechanisms. The best practice is to offload authentication to the gateway so backends never handle raw credentials. Here are the most common patterns.

JWT Validation with jwks-url

This configuration validates JWTs against a JWKS endpoint (like Auth0, Okta, or a custom OIDC provider). Invalid tokens are rejected before any backend receives the request.

{
  "version": 3,
  "name": "Secure API Gateway",
  "timeout": "3000ms",
  "endpoints": [
    {
      "endpoint": "/api/v1/{service}",
      "method": "GET",
      "output_mode": "noauth",
      "input_headers": ["Authorization"],
      "extra_config": {
        "auth/validator": {
          "alg": "RS256",
          "jwks_url": "https://auth.example.com/.well-known/jwks.json",
          "audience": ["api://example"],
          "issuer": "https://auth.example.com/",
          "roles": ["user", "admin"],
          "roles_key": "custom:roles",
          "propagate": true
        }
      },
      "backend": [
        {
          "url_pattern": "/v1/{service}",
          "host": ["http://backend-service:8080"],
          "encoding": "noauth"
        }
      ]
    }
  ]
}

Key security settings in this block:

API Key Validation (For Internal Services or Legacy Systems)

For machine-to-machine communication where JWT overhead is undesirable, use API keys. Store them in a backend or local registry.

{
  "endpoint": "/internal/events",
  "method": "POST",
  "input_headers": ["X-API-Key"],
  "extra_config": {
    "auth/apikey": {
      "keys": ["{{ .env.EVENTS_API_KEY }}"],
      "header": "X-API-Key",
      "cache_duration": "3600s"
    }
  },
  "backend": [
    {
      "url_pattern": "/events",
      "host": ["http://event-processor:8080"]
    }
  ]
}

Notice the use of {{ .env.EVENTS_API_KEY }} — this pulls the key from environment variables at startup, never hardcoded in the config file. The cache_duration reduces lookup overhead for high-frequency endpoints.

OAuth2 Client Credentials (Introspection)

For opaque tokens, use token introspection against the authorization server. This is heavier but works with any OAuth2-compliant provider.

{
  "extra_config": {
    "auth/oauth2-introspection": {
      "url": "https://auth.example.com/introspect",
      "endpoint_params": {
        "token": "{{ .token }}"
      },
      "endpoint_headers": {
        "Authorization": ["Bearer {{ .env.INTROSPECTION_TOKEN }}"]
      },
      "cache_duration": "300s",
      "required_scopes": ["read:events"]
    }
  }
}

The cache_duration is critical — without it, every request triggers an introspection call, creating a potential DoS vector against your auth server. Always cache introspection results.

2. Rate Limiting and DoS Protection

Unchecked traffic can overwhelm both the gateway and your backends. KrakenD's rate limiting works at the endpoint level and uses Redis for distributed counters in multi-instance deployments.

Endpoint-Level Rate Limiting with Redis

{
  "endpoint": "/api/v1/public/{resource}",
  "method": "GET",
  "extra_config": {
    "qos/ratelimit/router": {
      "max_rate": 100,
      "client_max_rate": 10,
      "every": "1s",
      "strategy": "redis",
      "redis_host": "redis-cluster:6379",
      "redis_db": 0,
      "redis_prefix": "ratelimit-public",
      "tokenizer": "header:X-User-Id"
    }
  },
  "backend": [
    {
      "url_pattern": "/public/{resource}",
      "host": ["http://content-service:8080"]
    }
  ]
}

This configuration:

Bot Detection and Challenge

For endpoints prone to scraping, add a proof-of-work challenge using KrakenD's bot detection plugin:

{
  "extra_config": {
    "plugin/req-resp-modifier": {
      "name": ["bot-detector"],
      "bot-detector": {
        "challenge": "proof-of-work",
        "difficulty": 4,
        "endpoints": ["/api/v1/public/*"],
        "whitelist": ["10.0.0.0/8", "172.16.0.0/12"]
      }
    }
  }
}

This requires clients to solve a computational puzzle before their request reaches backends, effectively rate-limiting automated scrapers without impacting legitimate users.

3. TLS Configuration and Transport Security

Enforcing HTTPS on the Gateway

KrakenD can terminate TLS natively. The minimal secure configuration looks like this:

{
  "version": 3,
  "name": "TLS-Secured Gateway",
  "tls": {
    "public_key": "/etc/krakend/certs/fullchain.pem",
    "private_key": "/etc/krakend/certs/privkey.pem",
    "min_version": "TLSv1.2",
    "max_version": "TLSv1.3",
    "cipher_suites": [
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
    ],
    "prefer_server_cipher_suites": true,
    "disable_ssl3": true
  },
  "endpoints": []
}

Security decisions here:

TLS to Backends (mTLS)

Don't just secure ingress — encrypt traffic to your upstream services too. This prevents lateral movement if an attacker compromises the internal network.

{
  "backend": [
    {
      "url_pattern": "/secure-data",
      "host": ["https://internal-service:8443"],
      "tls": {
        "ca_cert": "/etc/krakend/certs/ca-cert.pem",
        "client_cert": "/etc/krakend/certs/client-cert.pem",
        "client_key": "/etc/krakend/certs/client-key.pem",
        "disable_system_ca": true
      }
    }
  ]
}

Setting disable_system_ca: true ensures KrakenD only trusts your private CA, not the public PKI, preventing certificate injection attacks against backend connections.

4. CORS Configuration — Restrict, Don't Relax

Overly permissive CORS settings expose your APIs to cross-origin attacks. The principle is: allow only what you need.

{
  "endpoint": "/api/v1/data",
  "extra_config": {
    "github.com/devopsfaith/krakend-cors": {
      "allow_origins": ["https://app.example.com", "https://admin.example.com"],
      "allow_methods": ["GET", "POST"],
      "allow_headers": ["Content-Type", "Authorization", "X-Request-ID"],
      "expose_headers": ["X-Request-ID", "X-RateLimit-Remaining"],
      "max_age": "3600s",
      "allow_credentials": true
    }
  }
}

Never use "allow_origins": ["*"] in production. Wildcard origins combined with allow_credentials: true are blocked by browsers, but some implementations mishandle this — explicitly list your origins. Also, restrict allow_methods to only the HTTP verbs your API actually uses; this prevents unexpected state-changing requests via OPTIONS probes.

5. Input Validation and Header Security

Rejecting Malformed Requests

Use KrakenD's validation components to enforce strict input schemas before any backend processing occurs.

{
  "endpoint": "/api/v1/users",
  "method": "POST",
  "extra_config": {
    "validation/json-schema": {
      "type": "object",
      "required": ["name", "email"],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "email": {
          "type": "string",
          "format": "email",
          "pattern": "^[a-zA-Z0-9._%+-]+@example\\.com$"
        },
        "age": {
          "type": "integer",
          "minimum": 0,
          "maximum": 150
        }
      },
      "additionalProperties": false
    }
  },
  "backend": [
    {
      "url_pattern": "/users",
      "host": ["http://user-service:8080"]
    }
  ]
}

The "additionalProperties": false constraint is especially important — it rejects payloads with unexpected fields, blocking mass-assignment attacks where attackers inject fields like "role": "admin" that your backend might blindly accept.

Header Hardening — What You Strip and What You Add

KrakenD lets you control exactly which headers flow in each direction. Use this to prevent information leakage.

{
  "endpoint": "/api/v1/secure",
  "input_headers": [
    "Authorization",
    "Content-Type",
    "X-Request-ID",
    "Accept"
  ],
  "output_headers": [
    "Content-Type",
    "X-Request-ID",
    "X-RateLimit-Remaining"
  ],
  "extra_config": {
    "modifier/header": {
      "name": "X-Content-Type-Options",
      "value": "nosniff"
    },
    "modifier/header-2": {
      "name": "X-Frame-Options",
      "value": "DENY"
    },
    "modifier/header-3": {
      "name": "Content-Security-Policy",
      "value": "default-src 'none'; frame-ancestors 'none'"
    },
    "modifier/header-4": {
      "name": "Strict-Transport-Security",
      "value": "max-age=31536000; includeSubDomains; preload"
    }
  }
}

By whitelisting only Authorization, Content-Type, X-Request-ID, and Accept as input headers, you prevent header injection attacks and strip unnecessary metadata (like User-Agent, Referer, or custom tracking headers) that could be used for fingerprinting. On the output side, only three headers plus security headers are returned — backends that leak internal headers (like X-Powered-By, Server, or stack traces) are automatically sanitized.

6. Backend Isolation and Resilience

Circuit Breakers Prevent Cascading Failures

A compromised or overloaded backend shouldn't take down the entire gateway. Circuit breakers isolate failures:

{
  "backend": [
    {
      "url_pattern": "/risky-third-party",
      "host": ["http://external-api:9090"],
      "extra_config": {
        "qos/circuit-breaker": {
          "interval": "60s",
          "timeout": "10s",
          "max_errors": 5,
          "half_open_max_requests": 3,
          "closed_to_half_open_after": "30s",
          "open_to_half_open_after": "60s"
        }
      }
    }
  ]
}

After 5 consecutive failures within 60 seconds, the circuit opens and requests fail immediately (fast-fail) without touching the backend for 60 seconds. This protects the gateway thread pool and prevents backpressure from propagating.

Strict Timeouts

Every backend must have a timeout. Without it, a slow backend can consume all gateway connections.

{
  "timeout": "2000ms",
  "backend": [
    {
      "url_pattern": "/data",
      "host": ["http://slow-service:8080"],
      "timeout": "1500ms",
      "max_retries": 0
    }
  ]
}

Set max_retries: 0 for idempotent-safe backends when combined with circuit breakers — retries amplify load during partial outages. If you must retry, always use exponential backoff and limit attempts.

Backend Connection Hardening

Limit how KrakenD connects to backends to prevent resource exhaustion:

{
  "extra_config": {
    "backend/http": {
      "max_connections": 50,
      "max_connections_per_host": 10,
      "idle_connection_timeout": "30s",
      "disable_keep_alives": false,
      "connection_idle_timeout": "60s",
      "dial_timeout": "3s",
      "response_header_timeout": "2s"
    }
  }
}

These settings prevent connection leaks and limit the blast radius of any single misbehaving backend. The response_header_timeout is particularly effective against slow-loris style attacks where a backend sends headers byte-by-byte to hold connections open.

7. Secrets Management

Environment Variable Injection

KrakenD supports Go templates in configuration files. Use this to keep secrets out of your config repository:

{
  "extra_config": {
    "auth/apikey": {
      "keys": ["{{ .env.SERVICE_API_KEY }}"],
      "header": "X-API-Key"
    }
  },
  "backend": [
    {
      "host": ["{{ .env.BACKEND_HOST }}"],
      "url_pattern": "/internal",
      "extra_config": {
        "modifier/header": {
          "name": "X-Service-Token",
          "value": "{{ .env.BACKEND_AUTH_TOKEN }}"
        }
      }
    }
  ]
}

At startup, inject these via the environment:

# Production environment
export SERVICE_API_KEY="sk-prod-9a8b7c6d5e4f3a2b1c0d"
export BACKEND_HOST="https://secure-backend.internal:8443"
export BACKEND_AUTH_TOKEN="Bearer eyJhbGciOi..."

# Then start KrakenD
krakend run -c krakend.json

Never commit actual secret values to configuration files. Use a secrets manager like HashiCorp Vault, AWS Secrets Manager, or Kubernetes Secrets with a sidecar that populates environment variables before KrakenD starts.

Partial Configuration with Secrets Templates

For complex deployments, use KrakenD's flexible configuration to split secrets into a separate file that's never checked into version control:

{
  "version": 3,
  "name": "Production Gateway",
  "import": [
    "endpoints.json",
    "rate_limits.json",
    "circuit_breakers.json",
    "/run/secrets/krakend_secrets.json"
  ]
}

The /run/secrets/ path is typical in Docker/Kubernetes environments where secrets are mounted as tmpfs volumes. The secrets file contains only the sensitive portions of configuration and is mounted read-only at runtime.

8. Logging and Monitoring for Security

Audit Logging Configuration

Security-relevant events must be logged for incident response and compliance:

{
  "extra_config": {
    "telemetry/logging": {
      "level": "INFO",
      "format": "json",
      "fields": {
        "request_id": true,
        "remote_ip": true,
        "method": true,
        "path": true,
        "status": true,
        "latency": true,
        "user_agent": true
      },
      "filter": {
        "status_codes": [401, 403, 429, 500]
      }
    }
  }
}

JSON-formatted logs integrate with centralized logging systems (ELK, Splunk, Loki). Filtering to security-relevant status codes (401 unauthorized, 403 forbidden, 429 rate limited, 500 server errors) reduces noise while capturing all authentication failures and potential attack indicators.

Metrics for Anomaly Detection

Expose metrics that help detect attacks in real-time:

{
  "extra_config": {
    "telemetry/metrics": {
      "collection_period": "30s",
      "endpoint": "/__metrics",
      "expose": {
        "requests_total": true,
        "requests_per_second": true,
        "response_time_p99": true,
        "response_time_p95": true,
        "status_codes": true,
        "auth_errors": true,
        "rate_limit_hits": true,
        "circuit_breaker_trips": true
      }
    }
  }
}

Sudden spikes in auth_errors indicate credential stuffing. Increases in rate_limit_hits suggest automated scanning. Frequent circuit_breaker_trips may signal backend exploitation attempts. Pipe these metrics to Prometheus/Grafana with alerting thresholds.

9. Deployment Hardening

KrakenD as Non-Root User

Always run KrakenD as a non-privileged user. In Docker:

FROM devopsfaith/krakend:latest
COPY krakend.json /etc/krakend/krakend.json
COPY certs/ /etc/krakend/certs/
RUN addgroup -S krakend && adduser -S krakend -G krakend
USER krakend:krakend
EXPOSE 8080 8443
ENTRYPOINT ["krakend", "run", "-c", "/etc/krakend/krakend.json"]

In Kubernetes, add a security context:

apiVersion: v1
kind: Pod
spec:
  containers:
    - name: krakend
      image: devopsfaith/krakend:latest
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
  volumes:
    - name: config
      configMap:
        name: krakend-config
    - name: secrets
      secret:
        secretName: krakend-secrets
        defaultMode: 0400

The readOnlyRootFilesystem: true and allowPrivilegeEscalation: false settings prevent attackers from writing files or escalating privileges if they somehow compromise the gateway process.

Network Segmentation

Place KrakenD in a DMZ network segment. It should be able to reach backends but backends should not be able to initiate connections to the gateway:

# Kubernetes NetworkPolicy example
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: krakend-isolation
spec:
  podSelector:
    matchLabels:
      app: krakend
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - ipBlock:
            cidr: 10.100.0.0/24  # External load balancer subnet only
      ports:
        - port: 443
          protocol: TCP
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: backend-service
      ports:
        - port: 8080
          protocol: TCP

This ensures KrakenD only accepts traffic from your load balancer and can only communicate with designated backend pods — lateral movement is contained.

10. Complete Security-Hardened Configuration Example

Below is a production-ready configuration combining multiple hardening techniques. Adapt it to your environment:

{
  "version": 3,
  "name": "Hardened API Gateway",
  "timeout": "3000ms",
  "tls": {
    "public_key": "/etc/krakend/certs/fullchain.pem",
    "private_key": "/etc/krakend/certs/privkey.pem",
    "min_version": "TLSv1.2",
    "max_version": "TLSv1.3",
    "cipher_suites": [
      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
      "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
    ],
    "prefer_server_cipher_suites": true
  },
  "extra_config": {
    "backend/http": {
      "max_connections": 100,
      "max_connections_per_host": 20,
      "idle_connection_timeout": "30s",
      "response_header_timeout": "2s",
      "dial_timeout": "3s"
    },
    "telemetry/logging": {
      "level": "INFO",
      "format": "json",
      "fields": {
        "request_id": true,
        "remote_ip": true,
        "method": true,
        "path": true,
        "status": true,
        "latency": true
      }
    }
  },
  "endpoints": [
    {
      "endpoint": "/api/v1/users/{id}",
      "method": "GET",
      "input_headers": ["Authorization", "X-Request-ID"],
      "output_headers": ["Content-Type", "X-Request-ID"],
      "extra_config": {
        "auth/validator": {
          "alg": "RS256",
          "jwks_url": "{{ .env.JWKS_URL }}",
          "audience": ["api://production"],
          "issuer": "{{ .env.OIDC_ISSUER }}",
          "roles": ["user"],
          "propagate": true
        },
        "qos/ratelimit/router": {
          "max_rate": 200,
          "client_max_rate": 20,
          "every": "1s",
          "strategy": "redis",
          "redis_host": "{{ .env.REDIS_HOST }}",
          "tokenizer": "header:X-User-Id"
        },
        "modifier/header": {
          "name": "Strict-Transport-Security",
          "value": "max-age=31536000; includeSubDomains; preload"
        },
        "modifier/header-2": {
          "name": "X-Content-Type-Options",
          "value": "nosniff"
        }
      },
      "backend": [
        {
          "url_pattern": "/users/{id}",
          "host": ["{{ .env.USER_SERVICE_HOST }}"],
          "timeout": "1500ms",
          "max_retries": 0,
          "extra_config": {
            "qos/circuit-breaker": {
              "interval": "60s",
              "timeout": "10s",
              "max_errors": 5
            }
          }
        }
      ]
    },
    {
      "endpoint": "/api/v1/admin/{resource}",
      "method": "POST",
      "input_headers": ["Authorization", "Content-Type"],
      "output_headers": ["Content-Type"],
      "extra_config": {
        "auth/validator": {
          "alg": "RS256",
          "jwks_url": "{{ .env.JWKS_URL }}",
          "audience": ["api://production"],
          "issuer": "{{ .env.OIDC_ISSUER }}",
          "roles": ["admin"],
          "propagate": true
        },
        "validation/json-schema": {
          "type": "object",
          "required": ["action"],
          "properties": {
            "action": {
              "type": "string",
              "enum": ["refresh_cache", "reload_config", "health_check"]
            }
          },
          "additionalProperties": false
        }
      },
      "backend": [
        {
          "url_pattern": "/admin/{resource}",
          "host": ["{{ .env.ADMIN_SERVICE_HOST }}"],
          "encoding": "noauth"
        }
      ]
    }
  ]
}

This configuration demonstrates defense in depth: TLS termination at the gateway, JWT validation with role enforcement, rate limiting tied to user identity, input validation with strict schemas, security headers on all responses, circuit breakers on backends, and all secrets pulled from environment variables. Each layer independently contributes to security, so a failure in one doesn't compromise the entire system.

Conclusion

KrakenD API gateway security hardening isn't a single checkbox — it's a layered approach that spans authentication, transport security, input validation, rate limiting, backend isolation, secrets management, and operational practices. By implementing the techniques in this tutorial, you transform the gateway from a simple reverse proxy into a robust security enforcement point that protects your microservices ecosystem. Start with the fundamentals: TLS everywhere, JWT validation at the edge, and strict header controls. Then layer on rate limiting, input schemas, and circuit breakers as your threat model demands. Finally, ensure your deployment pipeline runs KrakenD as a non-root user on a read-only filesystem with minimal network privileges. The complete configuration example above gives you a production-ready starting point — adapt it, test it against your specific requirements, and deploy with confidence that your API gateway is genuinely hardened against real-world threats.

🚀 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