Skip to main content

Overview

Coraza is an open-source web application firewall that ships with the OWASP Core Rule Set (CRS). This template deploys the coraza-crs image, which runs Coraza inside the Caddy web server, as a reverse proxy in front of a workload you already run. Requests enter the WAF, are inspected against CRS and any rules you add, and only then are forwarded to the workload behind it. The template does not deploy the workload it protects, and it only inspects traffic that is actually routed through it.
This template does not create a GVC. It deploys into an existing GVC that you already have, and the workload it protects must already exist in that GVC.

What Gets Created

Prerequisites

1

The workload to protect

The workload you intend to protect must already exist in the same GVC, and the WAF must be able to reach it. Its internal firewall must be same-gvc, same-org, or a workload list that includes RELEASE_NAME-coraza-waf; an internal firewall of none blocks the WAF. Locking the Protected Workload shows the workload-list form.Set targetWorkload to its fully qualified internal address, WORKLOAD_NAME.GVC_NAME.cpln.local, and targetPort to the port it serves. Do not use the short workload name.

Installation

Install the template into the GVC that holds the workload to protect. targetWorkload is required; add --set targetPort=... as well if the workload does not serve on 8080:
To install using another method, follow the instructions for your preferred tool:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Image

  • Only *-caddy-alpine-* builds work. The -nginx- and -apache- builds of coraza-crs ship no Caddy binary, so this template cannot configure them. Upstream publishes a Caddy build for each CRS release; take the newest of those.
  • Pin a digest or a datecode, never a moving tag. Tags such as 4.25-caddy-alpine-lts and caddy-alpine are repointed upstream, which changes the image under a deployment nobody touched.
  • The Core Rule Set version comes from the image, so new CRS releases are picked up only when you change image.

Proxy Target

  • targetWorkload — the fully qualified internal address of the workload to protect, WORKLOAD_NAME.GVC_NAME.cpln.local.
  • targetPort — the port that workload serves on. The WAF forwards to it over plain HTTP.
  • WAFPort — the container port the WAF listens on, behind its public endpoint.

Resources and Timeout

Coraza inspects request bodies on the CPU, so resources.cpu and timeoutSeconds together set the largest request body the WAF accepts. As a rule of thumb:
The shipped defaults accept bodies of roughly 1.8 MB, enough for ordinary form posts and JSON APIs. A larger body returns a 504 from the WAF, not from your application, and GET traffic is unaffected, so a GET-only smoke test does not reveal the limit. To accept larger bodies, raise resources.cpu first (it inspects the same body faster), then timeoutSeconds, and raise resources.memory with them, since memory use grows with body size. Do not go below about 250m CPU. Two limits you cannot raise from values: Coraza stops inspecting bodies above 12.5 MiB (SecRequestBodyLimit, fixed in the image), and timeoutSeconds also caps how long your own workload may take to answer, so keep it above your slowest response. Bodies larger than the in-memory limit of Coraza are buffered to disk under /tmp/coraza and inspected in full; there is nothing to enable.

Placement

  • multiZone — when true, spreads replicas across availability zones. Not every location supports multi-zone, so confirm your location does before enabling it.

Rule Engine and Tuning

The chart runs the Coraza rule engine in blocking mode with the CRS defaults baked into the image. It exposes no value for the CRS paranoia level or for a detection-only mode; tuning is done with your own rules in the RELEASE_NAME-coraza-custom-rules secret. That secret is loaded after the Core Rule Set. It ships with one example rule, which blocks any request whose URI contains attack:
Write rules with seclang directives. Because the file loads after CRS, it can also switch off a CRS rule that produces false positives for your application:
CRS rule 920450 blocks any request carrying an Expect: 100-continue header, which some HTTP clients add automatically for large uploads. Removing that one rule leaves the rest of CRS in force. Give your own rules IDs outside the CRS ranges: a rule whose ID collides with a CRS rule silently overrides it. Changing Custom Rules shows how to apply an edit.

Logging

Coraza writes its access, audit and debug logs to standard output, so they are readable in the built-in logging interface alongside the [INFO] and [FATAL] lines from the startup hook:

Connecting

Read the canonical endpoint:
Then send one ordinary request and one that the bundled example rule blocks:
The first returns whatever your workload returns for /; the second returns 403 from the WAF.

Locking the Protected Workload

The WAF only protects traffic that passes through it. If the protected workload keeps its own public endpoint, clients can skip the WAF entirely. Close the protected workload to the internet and allow only the WAF on its internal firewall. In the Console, open the firewall settings of the protected workload, or set these fields in its workload spec:
If the protected workload was installed from another template, use the access values of that template instead (typically publicAccess.enabled: false and an internal access list naming RELEASE_NAME-coraza-waf), so a later upgrade of that template does not reopen it. A firewall change can take a few minutes to take effect, so re-test the own endpoint of the protected workload before relying on the lock.

Operations

Changing Configuration

Change image, targetWorkload, targetPort, WAFPort, resources, timeoutSeconds or multiZone in your values file, then upgrade:
The values file must use the chart’s nesting, exactly as the blocks in Configuration show. A flat dotted key is accepted without error and silently changes nothing. The WAF holds no data. Each new replica re-runs the startup hook, which points the reverse proxy at the current target.

Changing Custom Rules

Custom rules are not a Helm value. Edit the RELEASE_NAME-coraza-custom-rules secret in the Console, or from the CLI. Read the current rules first:
Write the complete new rule file into a secret manifest and apply it:
Rules are read when the WAF starts, and a running replica keeps the file it started with. Force a redeployment so every replica loads the new rules:
Then confirm the change with a request your new rule should affect. If the redeployment does not become ready, read the logs for the error.
The chart creates this secret with the example rule as its content. Whether a later cpln helm upgrade keeps your edited rules has not been verified, so keep your rule file under version control and re-check the secret after every upgrade.

Upgrading from 1.1.1 or Earlier

Version 1.2.0 changed three things:
  • diskBodyInspection (added in 1.1.0) was removed. Large-body disk buffering now always happens, so delete the key from your values file if it is there.
  • The resources defaults rose from 50m / 128Mi to 500m / 512Mi. If your values file pins the old figures, raise them, or POST bodies above a few tens of KB return 504.
  • timeoutSeconds was added, defaulting to 30. Earlier versions used a fixed 5-second timeout.
The WAF holds no data and the template creates no GVC, so an in-place cpln helm upgrade is the supported path.

Scaling and Availability

The WAF is stateless. It autoscales on CPU between one and three replicas, which the chart fixes; there is no value to change those bounds. While a single replica is running, a redeployment or a replica restart briefly interrupts traffic to the protected workload. Set multiZone: true to spread replicas across zones where your location supports it.

Troubleshooting

Symptom: The workload never becomes ready, and the deployment reports PostStartHook failed.Cause: The startup hook could not configure the reverse proxy and exits on purpose, so the WAF restarts rather than serve uninspected traffic. The usual cause is an image that is not a *-caddy-alpine-* build, or a moving tag that upstream repointed.Fix: Find the [FATAL] line that names the failed check, then pin a Caddy build by digest or datecode:
Symptom: The last log line is Launching caddy run ..., the container exits with exitCode: 127, and there is no [FATAL] line.Cause: image points at an nginx or apache build of coraza-crs, which has no Caddy binary.Fix: Set image to a *-caddy-alpine-* build and upgrade. Read the exit code here:
Symptom: GET requests work, but larger POST or upload requests return 504 from the WAF.Cause: Inspecting the body took longer than timeoutSeconds at the configured resources.cpu.Fix: Raise resources.cpu, then timeoutSeconds, and resources.memory with them, as described in Resources and Timeout.
Symptom: Large uploads are rejected with 403, while the same body succeeds without the WAF.Cause: The client sends Expect: 100-continue, which CRS rule 920450 blocks.Fix: Add SecRuleRemoveById 920450 to the custom rules secret and redeploy, as described in Changing Custom Rules.
Symptom: The WAF is ready, but requests to its endpoint fail instead of returning the response of the protected workload.Cause: The WAF cannot reach targetWorkload:targetPort. Typical reasons are a short workload name instead of the fully qualified address, a wrong targetPort, or an internal firewall on the protected workload that does not allow RELEASE_NAME-coraza-waf.Fix: Set targetWorkload to WORKLOAD_NAME.GVC_NAME.cpln.local, check the port, allow the WAF on the internal firewall of the protected workload, then upgrade. Read the WAF logs for the upstream error:

Important Notes

  • Lock the protected workload so it is reachable only through the WAF; otherwise requests bypass inspection.
  • Set targetWorkload to the fully qualified WORKLOAD_NAME.GVC_NAME.cpln.local address, never the short name.
  • Pin image by digest or datecode, and only to a *-caddy-alpine-* build.
  • Force a redeployment after editing the custom rules secret; running replicas keep the rules they started with.
  • Size resources.cpu and timeoutSeconds together; a body too large to inspect in time returns 504 from the WAF.
  • Do not override the command or args of the container; they carry the startup hook diagnostics into the logs.

External References

OWASP Coraza Documentation

Official Coraza WAF documentation and tutorials

OWASP CRS Documentation

Core Rule Set documentation and rule reference

seclang Directives

Directive reference for writing custom rules

Coraza CRS Docker

Image source, supported variants, and available tags

Coraza Template

View the source files, default values, and chart definition