Protect Applications with Web Application Firewall
The Traefik Hub API Gateway integrates Coraza Web Application Firewall to provide enterprise-grade protection against web attacks. Coraza is a powerful, ModSecurity-compatible WAF that runs the OWASP Core Rule Set (CRS) to defend your applications against the OWASP Top 10 and many other attack vectors.
Why Use WAF Protection?
A Web Application Firewall acts as a shield between your applications and the internet, inspecting HTTP traffic to detect and block malicious requests before they reach your services. The Coraza middleware in Traefik Hub provides:
- OWASP Core Rule Set Integration: Massive rules engine with hundreds of detection patterns for SQL injection, XSS, RCE, LFI, and more
- ModSecurity Compatibility: SecLang syntax support for custom rules and fine-tuned protection
- Native Performance: Runs natively inside Traefik Hub (no WASM or sidecar indirection)
- Flexible Configuration: Run in detection-only mode for testing or full blocking mode for enforcement
How WAF Protection Works
When the Coraza middleware is applied to a route, every request passes through the WAF engine for inspection:
The WAF uses an anomaly scoring system: each matched rule adds to a running score based on its severity (Critical=5, Error=4, Warning=3, Notice=2). CRS blocks a request once the total score crosses a threshold. Three rule files handle this:
REQUEST-901-INITIALIZATION.confdefines the default thresholds: 5 for inbound requests, 4 for outbound responses.REQUEST-949-BLOCKING-EVALUATION.confevaluates the inbound score against its threshold and blocks with a 403 if it's exceeded.RESPONSE-959-BLOCKING-EVALUATION.confevaluates the outbound score the same way, once the upstream response comes back.
These three load automatically with the full CRS.
Outbound rules need SecResponseBodyAccess On, which @coraza.conf-recommended already sets.
See Request and Response Body Limits below.
Quick Start: Enable WAF Protection
Detection-Only Mode (Recommended First Step)
Start by deploying the WAF in detection-only mode to observe what would be blocked without actually blocking requests:
- Detection Mode
- IngressRoute
- Service & Deployment
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-detection
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/*.conf
- SecRuleEngine DetectionOnly
- SecRequestBodyAccess On
- SecDataDir /tmp/
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: protected-app
namespace: apps
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.example.com`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: waf-detection
If you don't have a service to test against, deploy traefik/whoami.
It's a small HTTP server that echoes request details back in its response, useful for confirming the WAF is actually in the request path.
kind: Deployment
apiVersion: apps/v1
metadata:
name: whoami
namespace: apps
spec:
replicas: 3
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
---
apiVersion: v1
kind: Service
metadata:
name: whoami
namespace: apps
spec:
ports:
- port: 80
name: whoami
selector:
app: whoami
Set crsEnabled: true whenever you include @coraza.conf-recommended, @crs-setup.conf.example, or any @owasp_crs/*.conf file, so the embedded CRS filesystem is mounted.
Without it, Coraza fails to load the directives with an error like failed to readfile: open @coraza.conf-recommended: no such file or directory, and the router serving that route is disabled.
With SecRuleEngine DetectionOnly, the WAF logs matched rules but does not block requests. This allows you to review logs and tune your configuration before enforcing blocks.
Verify detection is working by sending a request with a suspicious payload and checking both the response and the WAF's own logs:
curl -s -o /dev/null -w '%{http_code}\n' "https://api.example.com/?id=1%27%20OR%20%271%27=%271"
In detection-only mode this still returns 200, because the WAF never blocks. Check the middleware's logs to confirm it still detected the request:
kubectl logs -n traefik -l app.kubernetes.io/name=traefik --tail=50 | grep -i "coraza\|942100"
You should see a matched-rule entry (a SQL injection detection rule, typically in the 942xxx range) even though the request went through.
A Coraza configuration that Traefik Hub rejects at load time (a missing crsEnabled, an unresolvable urn:k8s:secret: reference elsewhere in the route, or similar) disables the whole router, not only the WAF.
The symptom is a bare 404 with nothing in the response about the WAF.
Check curl -s http://<dashboard>/api/http/routers | jq '.[] | select(.status!="enabled")' to confirm whether a router is disabled and see why.
Blocking Mode (Enforcement)
Once you've validated the configuration, switch to blocking mode:
- Blocking Mode
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-blocking
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecRequestBodyAccess On
- SecDataDir /tmp/
With SecRuleEngine On, the WAF actively blocks malicious requests. Update the IngressRoute's middlewares list to reference waf-blocking instead of waf-detection, then send the same request again:
curl -s -o /dev/null -w '%{http_code}\n' "https://api.example.com/?id=1%27%20OR%20%271%27=%271"
This time it returns 403: the WAF blocked the request instead of only logging it.
SecDataDir must be writable by the Hub pod. The examples use /tmp, but if your container mounts /tmp with noexec or restricts writes, point SecDataDir to a dedicated writable path.
SecDataDir is where Coraza spills request/response bodies to disk once they exceed the in-memory limit (SecRequestBodyInMemoryLimit).
It isn't used for per-IP persistent state by default, since CRS's own collections for that are disabled out of the box.
If your custom rules use initcol for IP-based tracking (for example, brute-force counters), each Gateway replica has its own local /tmp, and that state isn't shared across replicas unless you mount a shared volume.
Customizing Allowed HTTP Methods
By default, the CRS allows common HTTP methods. To customize which methods are permitted (for example, adding PUT for REST APIs):
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-with-put
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
# Allow PUT method for REST APIs
- SecAction "id:900200,phase:1,pass,t:none,nolog,setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE'"
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecRequestBodyAccess On
- SecDataDir /tmp/
Customizing Allowed Content Types
CRS also rejects request bodies whose Content-Type isn't on its allowlist (tx.allowed_request_content_type). If your API accepts a content type the default list doesn't cover, extend it the same way as allowed methods:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-with-content-types
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
# Extend the default allowed Content-Type list
- SecAction "id:900220,phase:1,pass,t:none,nolog,setvar:'tx.allowed_request_content_type=
|application/json|
|application/x-www-form-urlencoded|
|multipart/form-data|
|text/xml|
|application/xml|
|application/x-protobuf|
|application/merge-patch+json|'"
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecRequestBodyAccess On
- SecDataDir /tmp/
Redefining tx.allowed_request_content_type (rule ID 900220) replaces the entire list rather than appending to it. Include every content type your API needs, not only the new one.
Common Use Cases
Out-of-the-Box WAF Protection
The most comprehensive protection loads all OWASP CRS rules to defend against the entire spectrum of web attacks:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-full-protection
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecRequestBodyAccess On
- SecDataDir /tmp/
This configuration protects against SQL injection, XSS, RCE, file inclusion, session attacks, and more.
Selective Attack Protection
For specific use cases, you can load only the rule categories you need. See CRS Rule Categories below for the full list of rule files and what each one covers.
- SQL Injection Only
- XSS Only
- File Inclusion Only
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-sqli-only
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/REQUEST-901-INITIALIZATION.conf
# Load only SQL injection protection rules
- Include @owasp_crs/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
- Include @owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf
- SecRuleEngine On
- SecDataDir /tmp/
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-xss-only
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/REQUEST-901-INITIALIZATION.conf
# Load only XSS protection rules
- Include @owasp_crs/REQUEST-941-APPLICATION-ATTACK-XSS.conf
- Include @owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf
- SecRuleEngine On
- SecDataDir /tmp/
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-lfi-only
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/REQUEST-901-INITIALIZATION.conf
# Load only Local File Inclusion protection
- Include @owasp_crs/REQUEST-930-APPLICATION-ATTACK-LFI.conf
- Include @owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf
- SecRuleEngine On
- SecDataDir /tmp/
Always include REQUEST-901-INITIALIZATION.conf to set CRS defaults (scores, allowed methods, content types) and REQUEST-949-BLOCKING-EVALUATION.conf to enable the anomaly scoring and blocking mechanism.
Path-Based Protection
Block access to sensitive paths or files using custom SecRule directives:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-path-protection
namespace: apps
spec:
plugin:
coraza:
directives:
- SecRuleEngine On
# Block access to admin paths
- SecRule REQUEST_URI "@rx ^/admin" "id:1001,phase:1,deny,status:403,log,msg:'Admin path blocked'"
# Block access to environment files
- SecRule REQUEST_URI "@rx \.env(\?|$)" "id:1002,phase:1,deny,status:403,log,msg:'Environment file access blocked'"
# Block access to git directories
- SecRule REQUEST_URI "@rx /\.git/" "id:1003,phase:1,deny,status:403,log,msg:'Git directory access blocked'"
- SecDataDir /tmp/
SecDataDir only sets a storage path and takes effect once the configuration is parsed, so its position relative to SecRule directives doesn't matter.
REQUEST_URI includes the query string, so an end-anchored regex like \.env$ blocks /foo.env but not /foo.env?x=1.
The \.env(\?|$) form above matches either a trailing ? or the true end of the string, so it catches both.
Method and Protocol Enforcement
Enforce strict HTTP method allowlists without loading the full CRS:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-method-enforcement
namespace: apps
spec:
plugin:
coraza:
directives:
- SecRuleEngine On
# Only allow GET and POST methods
- SecRule REQUEST_METHOD "!@rx ^(GET|POST)$" "id:2001,phase:1,deny,status:405,log,msg:'Method not allowed'"
- SecDataDir /tmp/
Understanding OWASP Core Rule Set
The OWASP Core Rule Set (CRS) is a collection of hundreds of detection rules that protect against common web application attacks.
When you enable crsEnabled: true, you gain access to the CRS rule library.
CRS Rule Categories
The CRS organizes rules into categories based on attack type:
| Rule File | Attack Category | Description |
|---|---|---|
REQUEST-901-INITIALIZATION.conf | Initialization | Sets CRS defaults: anomaly score thresholds, allowed methods, allowed content types |
REQUEST-905-COMMON-EXCEPTIONS.conf | Common Exceptions | Bypasses well-known false-positive sources, such as health checks, before the rest of the rules run |
REQUEST-911-METHOD-ENFORCEMENT.conf | Method Enforcement | Validates HTTP methods |
REQUEST-913-SCANNER-DETECTION.conf | Scanner Detection | Flags known vulnerability-scanner and bot user agents |
REQUEST-920-PROTOCOL-ENFORCEMENT.conf | Protocol Violations | Enforces HTTP protocol compliance |
REQUEST-921-PROTOCOL-ATTACK.conf | Protocol Attacks | Detects HTTP smuggling, request splitting |
REQUEST-922-MULTIPART-ATTACK.conf | Multipart Attacks | Detects malformed or malicious multipart/form-data requests |
REQUEST-930-APPLICATION-ATTACK-LFI.conf | Local File Inclusion | Prevents unauthorized file access |
REQUEST-931-APPLICATION-ATTACK-RFI.conf | Remote File Inclusion | Blocks remote file loading |
REQUEST-932-APPLICATION-ATTACK-RCE.conf | Remote Code Execution | Detects command injection attempts |
REQUEST-933-APPLICATION-ATTACK-PHP.conf | PHP Injection | Protects against PHP code injection |
REQUEST-934-APPLICATION-ATTACK-GENERIC.conf | Generic / SSRF & Node.js | Detects generic injections including Node.js patterns and SSRF payloads |
REQUEST-941-APPLICATION-ATTACK-XSS.conf | Cross-Site Scripting | Blocks XSS payloads |
REQUEST-942-APPLICATION-ATTACK-SQLI.conf | SQL Injection | Prevents database attacks |
REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf | Session Fixation | Protects session integrity |
REQUEST-944-APPLICATION-ATTACK-JAVA.conf | Java Attacks | Detects Java-specific exploits |
REQUEST-949-BLOCKING-EVALUATION.conf | Inbound Anomaly Scoring | Evaluates the inbound score and blocks |
REQUEST-999-COMMON-EXCEPTIONS-AFTER.conf | Common Exceptions | Cleans up transaction variables after inbound rules have run |
RESPONSE-950-DATA-LEAKAGES.conf | Data Leakage | Detects generic sensitive-data leaks in responses |
RESPONSE-951-DATA-LEAKAGES-SQL.conf | Data Leakage | Detects SQL error messages leaking in responses |
RESPONSE-952-DATA-LEAKAGES-JAVA.conf | Data Leakage | Detects Java stack traces leaking in responses |
RESPONSE-953-DATA-LEAKAGES-PHP.conf | Data Leakage | Detects PHP errors leaking in responses |
RESPONSE-954-DATA-LEAKAGES-IIS.conf | Data Leakage | Detects IIS error messages leaking in responses |
RESPONSE-955-WEB-SHELLS.conf | Web Shells | Detects web-shell output in responses |
RESPONSE-956-DATA-LEAKAGES-RUBY.conf | Data Leakage | Detects Ruby on Rails errors leaking in responses |
RESPONSE-959-BLOCKING-EVALUATION.conf | Outbound Anomaly Scoring | Evaluates the outbound score and blocks (the response-side counterpart to REQUEST-949) |
RESPONSE-980-CORRELATION.conf | Correlation | Adds transaction correlation data used by audit logging |
This is the full set bundled with coraza-coreruleset v4.25.0; upgrading the dependency may add or rename files.
To use the full CRS, include all rules with:
- Include @owasp_crs/*.conf
For more details on CRS capabilities, paranoia levels, and tuning, see the OWASP CRS documentation.
Protecting Against OWASP API Security Threats
The OWASP API Security Top 10 (2023) identifies the most critical API security risks. While WAF is a powerful tool, a defense-in-depth approach combining multiple Traefik Hub middlewares provides the most robust protection:
| API Security Threat | WAF Protection | Primary Mitigation | Complementary Middlewares |
|---|---|---|---|
| API1: Broken Object Level Authorization | 🟡 Partial - Custom rules for object ID validation | Authorization logic in application | JWT, OIDC, OPA |
| API2: Broken Authentication | 🟡 Partial - Detects common auth-bypass payloads but does not enforce auth | Strong authentication enforcement | OIDC, JWT, OAuth |
| API3: Broken Object Property Level Authorization | 🟡 Partial - Custom rules for excessive data exposure | API design and field filtering | JWT claims validation |
| API4: Unrestricted Resource Consumption | 🟡 Partial - Detects abuse patterns | Rate limiting per client/endpoint | Rate Limiting, Request size limits |
| API5: Broken Function Level Authorization | 🟡 Partial - Method + path enforcement | Role-based access control | JWT, OIDC, OPA |
| API6: Unrestricted Access to Sensitive Business Flows | 🟡 Partial - Custom rules/bot heuristics | Rate limiting and CAPTCHA | Rate Limiting, OIDC |
| API7: Server Side Request Forgery (SSRF) | 🟡 Partial - SSRF heuristics in REQUEST-934 | Input validation and allowlisting | N/A |
| API8: Security Misconfiguration | 🟡 Partial - Blocks common protocol/header issues | Secure defaults and hardening | Headers, TLS configuration |
| API9: Improper Inventory Management | 🔴 No - Governance issue | API documentation and lifecycle management | N/A |
| API10: Unsafe Consumption of APIs | 🟡 Partial - Input validation for inbound traffic | Validate upstream responses | ServersTransport configuration |
Defense-in-Depth Approach
For comprehensive API security, combine WAF with other Traefik Hub security middlewares.
See Combining with Other Middlewares below for the OIDC, rate limiting, and Secret setup this needs, and the chained IngressRoute example.
Middleware order matters:
- If you list the WAF first (used below): every request is inspected, including unauthenticated traffic. Coraza's checks are stateless, so this costs less than running rate limiting or OIDC first.
- If you list OIDC first: unauthenticated requests are redirected before reaching the WAF, so anonymous traffic is never inspected or logged, but the WAF also never runs against requests OIDC would have rejected anyway.
Either order is reasonable depending on what you want covered; the example below isn't the only valid ordering.
You can extend the same chain with a Headers middleware as an additional layer, applying security headers such as HSTS and CSP once the request has passed WAF and authentication checks.
Advanced Configuration
Request and Response Body Limits
Control how much data the WAF inspects to balance security and performance:
- Request Limits
- Response Limits
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-with-limits
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/*.conf
- SecRuleEngine On
# Enable request body inspection
- SecRequestBodyAccess On
# Maximum request body size: 12.5MB
- SecRequestBodyLimit 13107200
# In-memory buffer limit: 128KB (larger bodies use disk)
- SecRequestBodyInMemoryLimit 131072
# Reject requests exceeding the limit
- SecRequestBodyLimitAction Reject
- SecDataDir /tmp/
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-response-inspection
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecRequestBodyAccess On
# @coraza.conf-recommended already sets SecResponseBodyAccess On with
# text/plain, text/html, and text/xml in the mime list. This adds
# application/json so JSON API responses are inspected too.
- SecResponseBodyAccess On
- SecResponseBodyMimeType text/plain text/html text/xml application/json
# Maximum response body size to inspect: 512KB
- SecResponseBodyLimit 524288
# Process partial response if limit exceeded
- SecResponseBodyLimitAction ProcessPartial
- SecDataDir /tmp/
Key directives explained:
SecRequestBodyLimit: Maximum total request size (bytes). Requests larger than this are rejected or partially processed.SecRequestBodyInMemoryLimit: Amount of request body kept in memory. Bodies larger than this are buffered to disk.SecRequestBodyLimitAction: What to do when limit is exceeded -Reject(block) orProcessPartial(inspect what fits).SecResponseBodyAccess: Enable inspection of response bodies (useful for data leakage prevention). AlreadyOnby default via@coraza.conf-recommended.SecResponseBodyMimeType: Only inspect responses matching these content types. The default already coverstext/plain text/html text/xml; redefining it replaces the whole list, so include every type you need.
SecRequestBodyAccess On (and especially SecResponseBodyAccess On) makes Coraza buffer and scan full request/response bodies.
This can raise memory usage significantly under load, particularly for high-traffic routes or large payloads.
Watch pod memory under real traffic once you turn on body inspection, and tune SecRequestBodyLimit/SecResponseBodyLimit (or disable body access) if consumption becomes a problem.
For detailed information on body processing and limits, see the Coraza directives documentation.
Anomaly Score Tuning
Adjust the anomaly score thresholds to make the WAF more or less sensitive:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-tuned
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
# Set stricter threshold (block at score 3 instead of 5)
- SecAction "id:900110,phase:1,pass,t:none,nolog,setvar:tx.inbound_anomaly_score_threshold=3"
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecDataDir /tmp/
Lower thresholds increase security but may cause more false positives. Higher thresholds reduce false positives but may miss some attacks.
Paranoia Levels
CRS paranoia levels add progressively more aggressive rules. Level 1 is the default; higher levels include more patterns but increase false positive risk:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-paranoia-level-2
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
# Set paranoia level to 2 (more aggressive detection)
- SecAction "id:900000,phase:1,pass,t:none,nolog,setvar:tx.blocking_paranoia_level=2"
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecDataDir /tmp/
For more on paranoia levels and tuning strategies, see the CRS documentation on false positives.
Handling False Positives
When legitimate requests are blocked, you can exclude specific rules or paths:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-with-exclusions
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
# Disable rules for specific paths before the CRS rules that would match them are loaded
# (ctl:ruleRemoveById only affects rules that execute after this point)
- SecRule REQUEST_URI "@beginsWith /api/upload" "id:3001,phase:1,pass,nolog,ctl:ruleRemoveById=920420"
- Include @owasp_crs/*.conf
# Disable specific rule (e.g., rule 942100 causes false positive)
- SecRuleRemoveById 942100
- SecRuleEngine On
- SecDataDir /tmp/
For comprehensive guidance on tuning and exclusions, refer to the Coraza documentation.
Combining with Other Middlewares
Create defense-in-depth by chaining WAF with authentication and rate limiting:
- OIDC Middleware
- RateLimit Middleware
- IngressRoute
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: oidc-auth
namespace: apps
spec:
plugin:
oidc:
issuer: https://idp.example.com
clientId: "urn:k8s:secret:oidc-client:client_id"
clientSecret: "urn:k8s:secret:oidc-client:client_secret"
redirectUrl: /callback
The urn:k8s:secret:oidc-client:client_id form reads the client_id key from a Secret named oidc-client in the middleware's namespace. Create it first:
apiVersion: v1
kind: Secret
metadata:
name: oidc-client
namespace: apps
stringData:
client_id: my-oauth-client-ID # Set your ClientID here
client_secret: my-oauth-client-secret # Set your client secret here
See the OIDC middleware documentation for the full setup, including identity-provider-specific notes.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: rate-limit
namespace: apps
spec:
plugin:
distributedRateLimit:
limit: 100
period: 1s
store:
redis:
endpoints:
- redis.default.svc.cluster.local:6379
# Use the field password of the Secret redis in the same namespace
password: urn:k8s:secret:redis:password
The urn:k8s:secret:redis:password form reads the password key from a Secret named redis in the middleware's namespace. Create it first:
apiVersion: v1
kind: Secret
metadata:
name: redis
namespace: apps
stringData:
password: my-redis-password # Set your Redis password here
The Distributed RateLimit middleware requires a Redis instance and a store.redis.endpoints list; see the Distributed RateLimit middleware documentation for the full Redis setup.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: layered-security
namespace: apps
spec:
entryPoints:
- websecure
routes:
- match: Host(`secure.example.com`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: waf-blocking
- name: rate-limit
- name: oidc-auth
Monitoring and Audit Logs
Enable audit logging to track WAF activity and investigate blocked requests.
Basic Audit Logging
Configure audit logs to capture relevant security events:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: waf-with-audit
namespace: apps
spec:
plugin:
coraza:
crsEnabled: true
directives:
- Include @coraza.conf-recommended
- Include @crs-setup.conf.example
- Include @owasp_crs/*.conf
- SecRuleEngine On
- SecRequestBodyAccess On
- SecDataDir /tmp/
# Enable audit logging for relevant events
- SecAuditEngine RelevantOnly
# Write logs to stdout (captured by Kubernetes)
- SecAuditLog /dev/stdout
# Use JSON format for easy parsing
- SecAuditLogFormat JSON
- SecAuditLogType Serial
# Log entries to include (see explanation below)
- SecAuditLogParts ABCFHKZ
# @coraza.conf-recommended already sets this pattern, which covers 400-419 and
# 500-519 only. Coraza itself has no default: the filter applies once a directive
# sets it, and then filters every entry by response status, including ones a rule
# already flagged for audit logging.
- SecAuditLogRelevantStatus "^(?:(5|4)(0|1)[0-9])$"
This pattern is already active through @coraza.conf-recommended. Coraza itself has no default here, so the filter only applies once a directive sets it.
It then filters every entry by response status, including ones a rule already flagged for audit logging.
A custom rule denying with a status outside this pattern (status:429, for example) still blocks the request, but writes no audit entry at all, because 429 doesn't match.
Widen the regex (for example, ^(?:4|5)\d\d$) if you want every 4xx/5xx entry logged, including ones the default pattern would silently skip.
Audit Log Parts Explained
The SecAuditLogParts directive controls what information is included in audit logs. Each letter represents a section:
- A: Audit log header (timestamp, transaction ID)
- B: Request headers
- C: Request body (as received)
- F: Final response headers (may be empty if the request is interrupted before an upstream response)
- H: Audit log trailer (disposition/interruption info)
- I: Alternative request body (replacement for part C)
- J: Information about files uploaded
- K: Matched rules (include this to populate
messages[].data,messages[].message, andmessages[].actionset; without it, each entry still appears but only carries a flaterror_messagestring) - Z: Final boundary marker
- D/E/G: Reserved and not implemented by Coraza
The bundled @coraza.conf-recommended uses ABIJDEFHZ, which omits matched rule details (K) and the raw request body (C). Add K (and C if you want to see the body) as shown above.
For complete details on each part, see the Coraza audit logging documentation.
Understanding Audit Logs
This is Coraza's own audit log.
It's written wherever SecAuditLog points in your directives (/dev/stdout in the Basic Audit Logging example above), which lands in the Traefik Hub Gateway pod's container logs.
From there, it reaches whatever log pipeline your cluster forwards stdout to, such as Elasticsearch or Loki.
When a request is blocked or triggers rules, the audit log contains:
{
"transaction": {
"timestamp": "2025/12/08 14:01:53",
"id": "ghYIPhbqaJRwWUaHAoF",
"client_ip": "192.0.2.1",
"client_port": 1234,
"request": {
"method": "POST",
"uri": "/api/users",
"headers": {
"content-length": ["35"],
"content-type": ["application/x-www-form-urlencoded"],
"host": ["example.com"]
},
"body": "username=admin\u0026password=' OR '1'='1"
},
"response": {
"status": 0
},
"is_interrupted": true
},
"messages": [
{
"message": "SQL Injection Attack Detected via libinjection",
"data": {
"file": "@owasp_crs/REQUEST-942-APPLICATION-ATTACK-SQLI.conf",
"id": 942100,
"severity": 2,
"data": "Matched Data: ... password: ' OR '1'='1",
"tags": [
"attack-sqli",
"paranoia-level/1",
"OWASP_CRS/ATTACK-SQLI"
]
}
},
{
"message": "Inbound Anomaly Score Exceeded (Total Score: 5)",
"data": {
"file": "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf",
"id": 949110
}
}
]
}
Key fields for investigation:
transaction.id: Unique request identifier for correlationtransaction.request.uriandtransaction.request.headers: Targeted endpoint and client metadatamessages[].data.id: Rule ID that triggered (useful for tuning)messages[].data.data: The actual malicious pattern detectedmessages[].message: Human-readable explanation (for example, libinjection detection or anomaly score evaluation)transaction.is_interrupted: Indicates the request was blocked;REQUEST-949-BLOCKING-EVALUATIONconfirms inbound blocking,RESPONSE-959-BLOCKING-EVALUATIONconfirms outbound blocking
When Coraza interrupts the request before an upstream response, response.status may be empty/0 even though the client receives a 403.
Investigating Blocked Requests
To investigate a blocked request:
- Find the audit log entry using the transaction ID or timestamp
- Identify the triggered rule from
messages[].id - Review the matched pattern in
messages[].data - Determine if it's a false positive by analyzing the request context
- Take action:
- If legitimate: Exclude the rule using
SecRuleRemoveByIdor path-specific exceptions - If attack: Keep the rule and monitor for similar patterns
- If legitimate: Exclude the rule using
For production environments, consider forwarding audit logs to a centralized logging system (Elasticsearch, Loki, Splunk) for easier analysis and alerting.
Performance Considerations
Every rule evaluated against a request adds latency, and the cost depends on how the WAF engine is deployed.
Traefik's own benchmarks found:
- A native Coraza WAF (as used by the Traefik Hub API Gateway) handled ~683 requests/second in their test.
- A WASM-based WAF handled ~29 requests/second under the same conditions. The native implementation was ~23.5x faster.
- Disabling the WAF entirely raised throughput about 4x above the native-WAF numbers.
Even with the faster native engine, WAF inspection has a measurable throughput cost. Instead of applying it uniformly:
- Enable WAF, and the full CRS, on routes handling untrusted input, admin APIs, or sensitive data.
- Leave lower-risk, high-throughput routes, such as internal service-to-service traffic or health checks, without WAF, or use a Selective Attack Protection rule subset instead of the full CRS.
- Use Detection-Only Mode to measure the actual latency impact on your own traffic before enabling blocking broadly.
Related Content
- Refer to the Coraza WAF Reference Documentation for complete configuration options
- Refer to the NGINX Ingress ModSecurity/WAF annotations reference if you're migrating WAF-protected routes from an NGINX Ingress Controller
- Refer to the OIDC middleware documentation for secure APIs with identity provider integration
- Refer to the JWT middleware documentation for token-based authentication
- Refer to the OAuth2 Client Credentials middleware documentation for machine-to-machine authentication
- Refer to the Distributed Rate Limiting middleware documentation to protect against abuse and DoS
- Refer to the Headers middleware documentation for adding security headers
- Refer to the TLS Certificates documentation for configuring TLS certificates
