Integrate APISIX with Coraza WAF
Install a verified Coraza Proxy-WASM module in an isolated Apache APISIX quickstart, enforce a custom rule and OWASP CRS, validate blocked and allowed traffic, and restore the original configuration.
Applies to:Apache APISIX 3.18.0
Deployments:DockerRoles:Security engineerTime:25 minutesImpact:Changes live traffic
Goal
Load a checksum-verified Coraza Proxy-WASM module into an isolated APISIX quickstart. First enforce one explicit URI rule, then replace it with the bundled OWASP Core Rule Set (CRS), validate blocked and allowed requests against a controlled upstream, and restore the exact original configuration.
Coraza's maintainers state that a WAF requires workload-specific configuration and tuning; do not treat the bundled CRS as a production-ready policy. The 0.6.0 module also has unresolved upstream memory-usage reports. Load-test and tune it with representative traffic before production use.
Before you begin
- Complete Get APISIX with the Docker quickstart.
- Install Docker, cURL, jq,
shasum, andunzip. - Use an isolated quickstart. This procedure temporarily changes the running APISIX configuration and traffic behavior.
Set the endpoints, release metadata, and Admin API key:
export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export GATEWAY_URL="http://127.0.0.1:9080"
export CORAZA_VERSION="0.6.0"
export CORAZA_SHA256="cca4e3c75cf6b2e615907f936a1b6dcd0955250e0fb7d3b1c2ecef807d84603c"
read -rsp "Admin API key (press Enter for an unchanged quickstart): " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echoProtect the fixed Route, container, APISIX module path, and local files:
test "$(curl -sS --connect-timeout 3 --max-time 10 -o /dev/null -w '%{http_code}' "${APISIX_ADMIN_URL}/apisix/admin/routes/coraza-guide-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}")" = 404 || { echo 'Stop: route coraza-guide-route already exists'; exit 1; }
test -z "$(docker ps -aq --filter name=^/coraza-guide-upstream$)" || { echo 'Stop: container coraza-guide-upstream already exists'; exit 1; }
docker exec apisix-quickstart test ! -e /usr/local/bin/coraza-proxy-wasm.wasm || { echo 'Stop: APISIX module path already exists'; exit 1; }
for file in coraza-proxy-wasm.zip coraza-proxy-wasm.wasm coraza-guide-config.yaml.backup coraza-guide-config.yaml.updated coraza-guide-body.txt; do
test ! -e "${file}" || { echo "Stop: ${file} already exists"; exit 1; }
doneSteps
Step 1: Download the module safely
Download the pinned official release and require its known SHA-256 digest before extracting it:
curl --fail-with-body -L --connect-timeout 10 --max-time 120 \
"https://github.com/corazawaf/coraza-proxy-wasm/releases/download/${CORAZA_VERSION}/coraza-proxy-wasm-${CORAZA_VERSION}.zip" \
-o coraza-proxy-wasm.zip
printf '%s %s\n' "${CORAZA_SHA256}" coraza-proxy-wasm.zip | shasum -a 256 -c -
unzip -q coraza-proxy-wasm.zip
test -s coraza-proxy-wasm.wasmThe digest above was calculated from the official 0.6.0 GitHub Release asset. Stop if verification fails; do not copy or execute the file.
What changed: a verified copy of the pinned WebAssembly module exists in the current directory.
Step 2: Preserve the complete APISIX configuration
Back up the exact configuration and stop if a top-level wasm block already exists, because that case requires a reviewed merge:
umask 077
docker cp apisix-quickstart:/usr/local/apisix/conf/config.yaml coraza-guide-config.yaml.backup
test "$(grep -Ec '^wasm:[[:space:]]*$' coraza-guide-config.yaml.backup)" = 0 || { echo 'Stop: merge with the existing top-level wasm configuration'; exit 1; }
cp coraza-guide-config.yaml.backup coraza-guide-config.yaml.updated
cat >> coraza-guide-config.yaml.updated <<'YAML'
wasm:
plugins:
- name: coraza-filter
priority: 7999
file: /usr/local/bin/coraza-proxy-wasm.wasm
YAMLCopy the module and updated configuration, validate them, and recover immediately if apisix test fails:
docker cp coraza-proxy-wasm.wasm apisix-quickstart:/usr/local/bin/coraza-proxy-wasm.wasm
docker cp coraza-guide-config.yaml.updated apisix-quickstart:/usr/local/apisix/conf/config.yaml
if ! docker exec apisix-quickstart apisix test; then
docker cp coraza-guide-config.yaml.backup apisix-quickstart:/usr/local/apisix/conf/config.yaml
docker exec apisix-quickstart rm -f /usr/local/bin/coraza-proxy-wasm.wasm
docker exec apisix-quickstart apisix reload
exit 1
fi
docker exec apisix-quickstart apisix reloadWhat changed: APISIX loads the verified module while retaining every pre-existing configuration field.
Step 3: Start a controlled upstream
Start a pinned local HTTP server and wait for its listener:
docker run -d --name coraza-guide-upstream \
--network apisix-quickstart-net \
hashicorp/http-echo:1.0.0 -listen=:5678 -text=coraza-guide-upstream
for attempt in {1..15}; do
docker exec apisix-quickstart bash -c '</dev/tcp/coraza-guide-upstream/5678' >/dev/null 2>&1 && break
sleep 1
done
docker exec apisix-quickstart bash -c '</dev/tcp/coraza-guide-upstream/5678'What changed: allowed requests can be verified against a deterministic upstream without an external network dependency.
Step 4: Enforce one explicit rule
Create a fixed-ID Route. The rule lowercases the URI and denies paths that begin with /coraza-guide/archive:
curl -sS --connect-timeout 3 --max-time 15 "${APISIX_ADMIN_URL}/apisix/admin/routes/coraza-guide-route" -X PUT \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H "Content-Type: application/json" \
-d '{
"uri": "/coraza-guide/*",
"plugins": {
"coraza-filter": {
"conf": {
"directives_map": {
"default": [
"SecDebugLogLevel 0",
"SecRuleEngine On",
"SecRule REQUEST_URI \"@beginsWith /coraza-guide/archive\" \"id:101,phase:1,t:lowercase,deny,status:403\""
]
},
"default_directives": "default"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"coraza-guide-upstream:5678": 1}
}
}' | jq -e '.value.id == "coraza-guide-route" and .value.plugins["coraza-filter"].conf.default_directives == "default"'What changed: the custom Coraza policy protects only /coraza-guide/* and does not enable verbose debug logging.
Require the blocked path to return 403, then require an allowed path to reach the intended upstream:
test "$(curl -sS --connect-timeout 3 --max-time 15 -o /dev/null -w '%{http_code}' \
"${GATEWAY_URL}/coraza-guide/archive/test")" = 403
test "$(curl --fail-with-body -sS --connect-timeout 3 --max-time 15 -o coraza-guide-body.txt -w '%{http_code}' \
"${GATEWAY_URL}/coraza-guide/public")" = 200
test "$(cat coraza-guide-body.txt)" = "coraza-guide-upstream"Step 5: Evaluate the bundled OWASP CRS
Replace the same guide-owned Route with the recommended base configuration and bundled CRS:
curl -sS --connect-timeout 3 --max-time 15 "${APISIX_ADMIN_URL}/apisix/admin/routes/coraza-guide-route" -X PUT \
-H "X-API-KEY: ${APISIX_ADMIN_KEY}" -H "Content-Type: application/json" \
-d '{
"uri": "/coraza-guide/*",
"plugins": {
"coraza-filter": {
"conf": {
"directives_map": {
"default": [
"Include @recommended-conf",
"Include @crs-setup-conf",
"Include @owasp_crs/*.conf",
"SecRuleEngine On"
]
},
"default_directives": "default"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"coraza-guide-upstream:5678": 1}
}
}' | jq -e '.value.id == "coraza-guide-route"'What changed: the same bounded Route now evaluates the module's bundled CRS. This is an evaluation baseline, not a tuned production policy.
Validate
Require a representative XSS payload to be blocked, then confirm ordinary traffic remains allowed:
test "$(curl -sS --connect-timeout 3 --max-time 15 -o /dev/null -w '%{http_code}' \
"${GATEWAY_URL}/coraza-guide/public" -H "Cookie: input=<body onload='alert(xss)'>")" = 403
test "$(curl --fail-with-body -sS --connect-timeout 3 --max-time 15 -o coraza-guide-body.txt -w '%{http_code}' \
"${GATEWAY_URL}/coraza-guide/public")" = 200
test "$(cat coraza-guide-body.txt)" = "coraza-guide-upstream"All assertions must pass. Review APISIX error logs for false positives and resource pressure during a broader, representative test before selecting production rules.
Roll back / Clean up
Delete the Route and upstream, restore the exact APISIX configuration, then remove the module and local files:
curl -sS --connect-timeout 3 --max-time 15 -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/routes/coraza-guide-route" -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
docker rm -f coraza-guide-upstream
docker cp coraza-guide-config.yaml.backup apisix-quickstart:/usr/local/apisix/conf/config.yaml
docker exec apisix-quickstart apisix test
docker exec apisix-quickstart apisix reload
docker exec apisix-quickstart rm -f /usr/local/bin/coraza-proxy-wasm.wasm
rm -f coraza-proxy-wasm.zip coraza-proxy-wasm.wasm coraza-guide-config.yaml.backup \
coraza-guide-config.yaml.updated coraza-guide-body.txt
unset APISIX_ADMIN_URL APISIX_ADMIN_KEY GATEWAY_URL CORAZA_VERSION CORAZA_SHA256Expected: the fixed Route, upstream, module, and local files are absent, and APISIX is running with the byte-for-byte pre-guide configuration.
Troubleshoot
| Symptom | Cause | Fix |
|---|---|---|
| SHA-256 verification fails. | The download is incomplete or does not match the reviewed 0.6.0 asset. | Delete the ZIP and stop. Re-establish trusted release provenance before updating the digest or version. |
apisix test rejects the configuration. | A top-level key conflicts or the module is incompatible with the running APISIX image. | Use the automatic recovery result; do not reload the rejected configuration. Test compatibility in a separate environment. |
| The blocked request reaches the upstream. | The module did not load, the Route does not contain the intended policy, or the request path does not match. | Read the fixed Route and APISIX startup errors; do not weaken SecRuleEngine On. |
Ordinary requests return 403. | The CRS produces a false positive for the workload. | Switch to DetectionOnly in an isolated tuning environment, inspect rule IDs, and create reviewed exclusions before enforcement. |
| Memory grows during load testing. | The module or selected rules consume more memory than the deployment budget. | Remove the Route and module using this rollback, capture a reproducible profile, and follow the linked upstream issue before production use. |
Related
- Coraza Proxy-WASM releases — upstream artifacts and release notes.
- Coraza Proxy-WASM configuration guidance — production tuning warning and bundled directives.
- Plugins — explains plugin loading and execution order.
- Chaitin WAF (opens in Plugin Hub docs) — another APISIX WAF integration.