How to Set Up a WAF with Kong Gateway
How to add WAF-style protection to Kong Gateway by composing its security plugins (Injection Protection, JSON/XML Threat Protection, Bot Detection, IP Restriction, Rate Limiting, Request Size Limiting), plus honest options for full OWASP CRS coverage via a fronting cloud WAF or a community Coraza plugin.
Kong Gateway is one of the most widely deployed open source API gateways, built on top of OpenResty and NGINX. It is not, however, a signature or rule based web application firewall in the way ModSecurity or Coraza are. Open source Kong ships no OWASP Core Rule Set (CRS) engine and no single "WAF plugin". Kong's own documentation instead frames WAF as a job the gateway does by composing security plugins: Kong acts as "a front door for your applications by enforcing authentication and authorization, applying rate limits, restricting abusive sources, and validating requests before they reach upstream services."
That distinction matters for how you plan this. Some of the plugins used here (Bot Detection, IP Restriction, Rate Limiting, Request Size Limiting) are bundled with open source Kong and have shipped with it for many major versions, so they carry no special version floor. The deeper application layer inspection plugins (Injection Protection, JSON Threat Protection, XML Threat Protection, Request Validator, Rate Limiting Advanced) are Kong Enterprise / Konnect plugins, and they set the real version floor for this guide: Injection Protection needs Kong Gateway 3.9+, JSON Threat Protection needs 3.8+ (POST/PUT/PATCH body validation needs 3.10+), and XML Threat Protection needs 3.1+. If you plan to follow the full guide, target 3.9+ and confirm licensing against your subscription before you build on them.
This guide walks through both layers honestly. First you harden Kong with the free plugins for rate limiting, IP control, size limits, and bot filtering. Then you layer on the Enterprise threat protection plugins for injection and payload attacks. Finally, because none of those run the OWASP CRS, we cover the two real paths to CRS grade coverage: fronting a Kong Dedicated Cloud Gateway with a cloud WAF, or running a community Coraza plugin for self hosted Kong. Configuration is shown as declarative decK config, which also maps cleanly to the Admin API and Konnect.
Prerequisites
- A running Kong Gateway instance (self hosted, hybrid, or Konnect). Use 3.9+ for the full guide, since Injection Protection requires Kong Gateway 3.9+; the open source baseline plugins carry no special version floor and have shipped with Kong for many major versions
- Kong Enterprise or Kong Konnect for the Injection Protection (Kong 3.9+), JSON Threat Protection (Kong 3.8+, POST/PUT/PATCH body validation 3.10+), XML Threat Protection (Kong 3.1+), Request Validator, and Rate Limiting Advanced plugins (the baseline plugins work on open source Kong)
- Admin API access, or the decK CLI configured against your control plane
- At least one Gateway Service and Route already proxying a backend through Kong
- Basic familiarity with Kong entities (Services, Routes, Plugins) and declarative configuration
- curl and a terminal for testing
Step-by-Step Guide
Understand How Kong Does WAF
Before enabling anything, be clear about what Kong is and is not. Kong is an API gateway built on OpenResty/NGINX. Open source Kong has no bundled ModSecurity or Coraza engine and does not run the OWASP Core Rule Set. Kong's WAF documentation describes WAF as a composition of plugins rather than a single rule engine. There are three realistic approaches:
- Compose Kong security plugins (this guide): Chain Injection Protection, JSON/XML Threat Protection, Bot Detection, IP Restriction, Rate Limiting, and Request Size Limiting on your Services and Routes. This covers common API threats but is not CRS.
- Front the gateway with a real WAF (managed/cloud): For a public Dedicated Cloud Gateway, put a CDN plus a cloud WAF (for example AWS WAF managed rules on CloudFront) in front and validate origin traffic. Covered in a later step.
- Run a community Coraza plugin (self hosted): Community projects embed the OWASP Coraza engine and CRS v4 into Kong as a plugin server. Not an official Kong plugin; you own the build and support. Covered in a later step.
Also note the tier and version split: Bot Detection, IP Restriction, Rate Limiting, and Request Size Limiting are in open source Kong, while Injection Protection (Kong 3.9+), JSON Threat Protection (Kong 3.8+), XML Threat Protection (Kong 3.1+), Request Validator, and Rate Limiting Advanced are Enterprise / Konnect plugins.
Add Baseline Protection with Open Source Plugins
Start with the plugins bundled in open source Kong. This declarative kong.yaml attaches IP Restriction, Request Size Limiting, and Bot Detection globally, plus basic Rate Limiting on a Service:
_format_version: "3.0"
services:
- name: my-api
url: http://upstream:8080
routes:
- name: my-api-route
paths:
- /api
plugins:
# Block oversized bodies (returns HTTP 413)
- name: request-size-limiting
config:
allowed_payload_size: 10
size_unit: megabytes
require_content_length: false
# Block known bad user agents by regex (returns HTTP 403)
- name: bot-detection
config:
deny:
- "(C)|(c)url"
- "python-requests"
# Deny abusive source ranges (returns HTTP 403)
- name: ip-restriction
config:
deny:
- 203.0.113.0/24
status: 403
message: "Access denied"
Then add rate limiting per client. On open source Kong use the Rate Limiting plugin; on Enterprise/Konnect prefer Rate Limiting Advanced for sliding windows and shared counters. For a single node, strategy: local needs no external store. For multiple nodes, use strategy: redis, which requires a config.redis block and works in hybrid, DB-less, and Konnect deployments:
plugins:
- name: rate-limiting-advanced
service: my-api
config:
limit:
- 100
window_size:
- 60
window_type: sliding
identifier: ip
strategy: redis
sync_rate: 1
redis:
host: redis
port: 6379
Block Injection Attacks with the Injection Protection Plugin
The Injection Protection plugin (Enterprise / Konnect, Kong Gateway 3.9+) matches request content against built in regex patterns for injection classes. It is the closest thing Kong ships to signature based WAF detection. Configure which injection types and which request locations to inspect, and start in log_only mode:
plugins:
- name: injection-protection
service: my-api
config:
injection_types:
- sql
- js
locations:
- path_and_query
- body
- headers
enforcement_mode: log_only
error_status_code: 400
error_message: "Bad Request"
The configuration reference lists the full set of injection_types (sql, sql_low_sensitivity, js, java_exception, ssi, xpath_abbreviated, xpath_extended) and locations (body, headers, path, path_and_query, query). You can also add custom_injections with a name and regex for patterns specific to your app. Note the plugin's defaults: enforcement_mode defaults to block (not log_only) and locations defaults to [path_and_query], so the explicit settings above override both. A partial copy that omits them will block from the first request and never scan request bodies.
Guard Structured Payloads with Threat Protection Plugins
APIs are attacked through oversized and deeply nested payloads as well as injection strings. The JSON Threat Protection plugin (Kong 3.8+; POST/PUT/PATCH body validation requires Kong 3.10+) enforces structural limits on JSON bodies:
plugins:
- name: json-threat-protection
service: my-api
config:
max_body_size: 1000000
max_container_depth: 20
max_object_entry_count: 100
max_object_entry_name_length: 100
max_array_element_count: 1000
max_string_value_length: 10000
enforce_mode: block
error_status_code: 400
If you accept XML, add the XML Threat Protection plugin (Kong 3.1+), which similarly caps element depth, attribute counts, and value sizes to defend against XML bomb and entity expansion attacks. Note an important difference between the two: JSON Threat Protection supports a log only phase through enforce_mode: log_only, so set that first if you are unsure of your legitimate payload shapes, then switch to enforce_mode: block. XML Threat Protection has no enforce mode and no log only mode; it always blocks on a limit violation, so validate its max_* limits carefully against real XML payloads before you enable it.
Validate Requests Against a Schema
The strongest API layer defense Kong offers is positive validation: rejecting anything that does not match an expected schema. The Request Validator plugin (Enterprise / Konnect) validates request bodies and parameters against a JSON Schema (Draft 4, version: draft4) or Kong's native schema (version: kong) that you supply directly per Route (optionally generated from an OpenAPI spec with deck file openapi2kong, a separate decK tool, not a feature of the plugin itself):
plugins:
- name: request-validator
route: my-api-route
config:
version: draft4
body_schema: |
{
"type": "object",
"required": ["id"],
"properties": {
"id": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}
allowed_content_types:
- application/json
verbose_response: false
Because it allowlists structure rather than blocklisting known attacks, a request validator catches whole classes of injection and parameter tampering that signature matching misses, at the cost of maintaining a schema per Route.
Add OWASP CRS Grade Protection Where You Need It
None of the plugins above run the OWASP Core Rule Set. If your compliance or threat model requires CRS, choose one of two honest paths:
Managed / cloud path (Dedicated Cloud Gateways): A public Dedicated Cloud Gateway exposes a DNS hostname, so you front it with a CDN that can attach a cloud WAF. Per Kong's public network security guide, put AWS WAF managed rules on a CloudFront distribution, then lock the origin so traffic cannot bypass the CDN. CloudFront injects a shared secret header and Kong requires it:
# Reject any request that did not come through the CDN
plugins:
- name: request-termination
config:
status_code: 403
message: "Direct origin access denied"
# ...enabled via a Route that matches when the
# X-Origin-Verify header is absent or wrong.
# Also allowlist the CDN egress ranges:
- name: ip-restriction
config:
allow:
- 130.176.0.0/16 # example CDN egress range
Kong recommends storing the shared header value in a Vault and rotating it periodically.
Self hosted path (community Coraza plugin): Community projects embed the OWASP Coraza engine and CRS v4 into Kong as a Go plugin server, compatible with hybrid, traditional, and DB-less topologies. You build a custom Kong image and enable the plugin server via environment variables, for example:
KONG_PLUGINS=bundled,kong-waf
KONG_PLUGINSERVER_NAMES=kong-waf
KONG_PLUGINSERVER_KONG_WAF_QUERY_CMD="/usr/local/bin/kong-waf -dump"
The plugin is then configured dynamically through the Admin API with SecLang/CRS directives.
Apply the Configuration
Push the declarative config to your control plane with decK, which validates and syncs the full state:
# Validate the file first
deck gateway validate kong.yaml
# Sync it to Kong (Admin API or Konnect control plane)
deck gateway sync kong.yaml
You can also enable each plugin imperatively through the Admin API or the Konnect UI. Confirm the plugins are active on your Service or Route:
curl -s http://localhost:8001/plugins | \
grep -o '"name":"[^"]*"'
Test the Protections
With Injection Protection switched to enforcement_mode: block, send known attack payloads and confirm each plugin rejects them:
# SQL injection in the query string (Injection Protection, 400)
curl -i 'http://localhost:8000/api?id=1%20OR%201=1'
# SQL injection in a JSON body (Injection Protection, 400)
curl -i -X POST http://localhost:8000/api \
-H 'Content-Type: application/json' \
-d '{"id":"1 OR 1=1"}'
# Denied bot user agent (Bot Detection, 403)
curl -i -A 'python-requests/2.31' http://localhost:8000/api
# Oversized body (Request Size Limiting, 413)
head -c 20000000 /dev/zero | \
curl -i -X POST --data-binary @- http://localhost:8000/api
# Flood past the limit (Rate Limiting, 429)
for i in $(seq 1 200); do \
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8000/api; done
# A normal request (should return 200)
curl -i 'http://localhost:8000/api?id=42'
Tune and Move from Logging to Blocking
Run the tunable inspection plugins in log only mode for one to two weeks and review the Kong logs for legitimate requests that would have been rejected. Injection Protection emits a structured log line naming the threat type, action, and location for every match, which is where you identify false positives.
Tune by narrowing the locations Injection Protection inspects, loosening a specific max_* limit in the threat protection plugins, or refining the body_schema in Request Validator so real traffic validates. Once the logs are clean, flip enforcement_mode (Injection Protection) and enforce_mode (JSON Threat Protection) to block and re-sync with decK. XML Threat Protection has no log only mode and already blocks on any violation, so there is nothing to flip there; just confirm its max_* limits match your real XML traffic before it goes live.
# After tuning, enforce
deck gateway sync kong.yaml
# Watch for new false positives in the logs
kubectl logs -f deploy/kong-gateway # or docker/systemd logs
Conclusion & Next Steps
Kong Gateway now has layered, API focused protection: open source plugins for IP control, rate limiting, size limits, and bot filtering, plus the Enterprise threat protection plugins for injection and payload attacks, and positive schema validation where you can afford to maintain schemas. Be honest with stakeholders about the boundary: this is a composition of security plugins, not the OWASP Core Rule Set, and it only protects traffic that flows through Kong.
Next steps:
- Keep the tunable inspection plugins in log only mode until the logs are clean, then switch to block (XML Threat Protection always blocks, so validate its limits up front instead)
- Export Kong logs and metrics to your SIEM so plugin decisions and rate limit counters are visible
- Manage all plugin config as decK declarative files in version control and CI
- If you need CRS coverage, front a Dedicated Cloud Gateway with a cloud WAF or evaluate a community Coraza plugin, and pin the CRS version
- Consider third party WAF engines with native Kong plugins (open-appsec, Wallarm) if signature or ML based detection matters more than the plugin approach
- Re-test the full attack suite after every Kong upgrade, since plugin defaults and fields can change between versions
Troubleshooting
Kong syncs but no attacks are blocked
Check that the tunable inspection plugins are in enforcing mode, not logging. Injection Protection uses enforcement_mode: block and JSON Threat Protection uses enforce_mode: block. In log only mode Kong records the match but proxies the request. XML Threat Protection has no enforce mode and always blocks on a limit violation, so if XML attacks get through the issue is your limit configuration, not a mode setting. Also confirm the plugin is attached to the Service or Route the traffic actually hits, not an unused entity.
Query-string attacks are blocked but body attacks are not
Injection Protection only inspects the locations you list. If locations is left at its default of path_and_query, request bodies are never scanned. Add body (and headers if needed) to the locations array so POST and JSON payloads are inspected.
Legitimate requests are rejected with 400 after enabling threat protection
Usually a max_* limit in JSON Threat Protection is tighter than your real traffic, for example max_container_depth or max_string_value_length. For JSON Threat Protection, revert to enforce_mode: log_only, sample real payloads, raise the specific limit that trips, then re-enforce. XML Threat Protection has no log only mode, so raise the offending XML limit and re-sync directly. The same tuning applies to an over strict body_schema in the Request Validator.
The Injection Protection or Request Validator plugin will not enable
These are Kong Enterprise / Konnect plugins, and Injection Protection additionally requires Kong Gateway 3.9+. On open source Kong, or on an older Enterprise version, the Admin API returns a "plugin not found" or licensing error. Verify your subscription and Kong version, or fall back to the open source plugins (Bot Detection, IP Restriction, Rate Limiting, Request Size Limiting) plus a community Coraza plugin for deeper inspection.
Rate limiting counts are wrong across multiple nodes
With Rate Limiting Advanced set to strategy: local, each node counts independently, so a client can exceed the limit by hitting different data planes. Use strategy: redis with a config.redis block (host and port at minimum) and a suitable sync_rate so counters are shared across the fleet; this works in hybrid, DB-less, and Konnect deployments. The cluster strategy also shares counters but is only supported in traditional (DB-backed) mode, so it is unavailable in hybrid, DB-less, and Konnect: use redis for those topologies.
Attackers reach the gateway directly and bypass the fronting WAF
On a public Dedicated Cloud Gateway, the origin DNS name is reachable unless you enforce origin validation. Require the CDN injected header (for example X-Origin-Verify) and reject requests without it using Request Termination or a Route match, and allowlist the CDN egress ranges with the IP Restriction plugin. Store and rotate the shared secret in a Vault.
Bots and scanners still get through Bot Detection
The Bot Detection plugin matches only the User-Agent header against regex. Attackers that send a browser UA are not caught. Add custom deny patterns for the signatures you actually see in logs, and rely on rate limiting and injection inspection for behavior a spoofed UA cannot hide.
High latency or CPU after enabling many plugins
Every enabled plugin runs in the request path in the Kong worker. Body inspecting plugins (Injection Protection, JSON/XML Threat Protection) buffer and scan payloads, which is the most expensive work. Limit locations to what you need, cap body sizes with Request Size Limiting so huge payloads are rejected before deep inspection, and scope heavy plugins to the Routes that need them rather than globally.
Frequently Asked Questions
Does Kong Gateway have a built-in WAF plugin like ModSecurity or Coraza?
Not in the CRS sense. Open source Kong has no bundled ModSecurity or Coraza engine and does not run the OWASP Core Rule Set. Kong's documented WAF approach is to compose security plugins, such as Injection Protection, JSON/XML Threat Protection, Bot Detection, IP Restriction, and Rate Limiting, into a front door for your APIs. For true CRS coverage you either front the gateway with a cloud WAF or run a community Coraza plugin.
Which Kong WAF plugins are free and which require Enterprise?
Bot Detection, IP Restriction, Rate Limiting, and Request Size Limiting are bundled with open source Kong. Injection Protection (Kong 3.9+), JSON Threat Protection (Kong 3.8+), XML Threat Protection (Kong 3.1+), Request Validator, and Rate Limiting Advanced are Kong Enterprise / Konnect plugins. Always confirm plugin availability and the minimum Kong version against your specific subscription, since tiering and version requirements can change between releases.
Can I run the OWASP Core Rule Set with Kong Gateway?
Not with Kong's own plugins. Two paths give you CRS: front a public Dedicated Cloud Gateway with a CDN and a cloud WAF (for example AWS WAF managed rules on CloudFront) and validate origin traffic, or embed the OWASP Coraza engine and CRS v4 into self hosted Kong via a community plugin server. The community route is unofficial and unsupported by Kong, so pin the CRS version and test it against your Kong version.
How do I protect a Kong Dedicated Cloud Gateway with a WAF?
A public Dedicated Cloud Gateway exposes a DNS hostname, so you place a CDN that supports DNS origins (such as CloudFront) in front and attach a cloud WAF to the distribution. To stop attackers bypassing the CDN, configure origin validation: the CDN injects a shared secret header like X-Origin-Verify, and Kong rejects requests that lack it using Request Termination or a Route match, plus IP Restriction allowlisting the CDN egress ranges.
How is Kong's Injection Protection different from a real WAF engine?
Injection Protection provides regex matching for common injection classes (SQL, JS, SSI, XPath, and more) across configurable request locations. It is signature style detection, but it is a fixed built in pattern set, not the tunable, community maintained OWASP Core Rule Set with paranoia levels and anomaly scoring. It is effective against common payloads and a good API layer defense, but it is narrower than a full CRS deployment. Note that it is an Enterprise / Konnect plugin requiring Kong Gateway 3.9+.
Do Kong's WAF plugins add latency?
Kong plugins run in process in the same worker that handles routing and auth, so there is no extra proxy hop, which is Kong's main advantage over a separate WAF appliance. The cost comes from body inspecting plugins that buffer and scan payloads. Limit the inspected locations, cap body sizes with Request Size Limiting before deep inspection, and scope heavy plugins to the Routes that need them to keep overhead low.
Should I use Kong's plugins or a third-party WAF engine?
Use Kong's plugins when your threat model is API centric and you want security managed alongside routing and auth with no extra infrastructure. Consider a third party engine with a native Kong plugin, such as open-appsec (ML driven) or Wallarm (API security), when you need signature or machine learning detection beyond what the built in plugins offer. For CRS specifically, a fronting cloud WAF or a community Coraza plugin is the path.
Related Guides
WAF Security Best Practices Guide
Essential best practices for configuring and maintaining your Web Application Firewall for optimal security.
How to Install and Configure ModSecurity with NGINX
Complete guide to deploying ModSecurity 3.x with NGINX for free, open-source WAF protection using the OWASP Core Rule Set.
How to Protect Nginx with Coraza WAF Using Docker
Step-by-step guide to deploying Coraza WAF as a reverse proxy in front of Nginx using Docker and docker-compose, with OWASP CRS protection out of the box.