Coraza
Coraza WAF middleware for Fiber.
Compatible with Fiber v3.
Go version support
We only support the latest two versions of Go. Visit https://go.dev/doc/devel/release for more information.
Install
go get github.com/gofiber/fiber/v3
go get github.com/gofiber/contrib/v3/coraza
Signature
coraza.New(config ...coraza.Config) fiber.Handler
coraza.NewEngine(config coraza.Config) (*coraza.Engine, error)
Config
| Property | Type | Description | Default |
|---|---|---|---|
| Next | func(fiber.Ctx) bool | Defines a function to skip this middleware when it returns true | nil |
| BlockHandler | func(fiber.Ctx, coraza.InterruptionDetails) error | Custom handler for blocked requests | nil |
| ErrorHandler | func(fiber.Ctx, coraza.MiddlewareError) error | Custom handler for middleware failures | nil |
| DirectivesFile | []string | Coraza directives files loaded in order | nil |
| RootFS | fs.FS | Optional filesystem used to resolve DirectivesFile | nil |
| BlockMessage | string | Message returned by the built-in block handler | "Request blocked by Web Application Firewall" |
| LogLevel | fiberlog.Level | Middleware lifecycle log level | fiberlog.LevelInfo in coraza.ConfigDefault |
| RequestBodyAccess | bool | Enables request body inspection | true in coraza.ConfigDefault |
| MetricsCollector | coraza.MetricsCollector | Optional custom metrics collector | nil (falls back to the built-in collector) |
If you want the defaults, start from coraza.ConfigDefault and override the fields you need.
For zero-value-backed settings such as RequestBodyAccess: false, LogLevel: fiberlog.LevelTrace, or resetting MetricsCollector to the built-in default, use ConfigDefault or the helper methods WithRequestBodyAccess, WithLogLevel, and WithMetricsCollector so the choice remains explicit.
By default, the middleware starts without external rule files. Set DirectivesFile to load your Coraza or CRS ruleset.
Request body size follows the Fiber app BodyLimit.
Wildcard entries in DirectivesFile are expanded before Coraza initializes. If a wildcard matches no files, initialization fails with an error and the middleware does not start.
Usage
package main
import (
"log"
"github.com/gofiber/contrib/v3/coraza"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
cfg := coraza.ConfigDefault
cfg.DirectivesFile = []string{"./conf/coraza.conf"}
app.Use(coraza.New(cfg))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
log.Fatal(app.Listen(":3000"))
}
Advanced usage with Engine
Use NewEngine when you need explicit lifecycle control, reload support, or observability data.
engineCfg := coraza.ConfigDefault
engineCfg.DirectivesFile = []string{"./conf/coraza.conf"}
engine, err := coraza.NewEngine(engineCfg)
if err != nil {
log.Fatal(err)
}
app.Use(engine.Middleware(coraza.MiddlewareConfig{
Next: func(c fiber.Ctx) bool {
return c.Path() == "/healthz"
},
BlockHandler: func(c fiber.Ctx, details coraza.InterruptionDetails) error {
return c.Status(details.StatusCode).JSON(fiber.Map{
"blocked": true,
"message": "request blocked by security policy",
})
},
}))
For production deployments, avoid returning rule identifiers or detailed match data to clients. Prefer a generic error body and log the matched rule metadata server-side when needed.
Note that the built-in block handler sets an X-WAF-Blocked: true response header and a
message naming the Web Application Firewall. If you want to avoid WAF fingerprinting,
provide a custom BlockHandler (or BlockMessage) that returns a neutral response.
Engine observability
The middleware does not open operational routes for you, but Engine exposes data-oriented methods that can be used to build your own endpoints:
engine.Reload()engine.MetricsSnapshot()engine.Snapshot()engine.Report()
Snapshot() and Report() include server filesystem paths (directive files) and raw
initialization error details. Only expose such endpoints internally or behind
authentication, never to untrusted clients.
Reload() swaps in the new WAF instance immediately and then waits until requests still
being inspected by the previous instance finish inspection before closing it. Inspection
ends before downstream handlers run, so the wait is bounded by WAF processing time and it
is safe to call Reload() from a handler running behind the middleware.
BlockRate is cumulative since process start or the most recent collector reset.
RecentLatencyMs and RecentBlockRate are EWMA-based recent-trend metrics with a fixed
alpha of 0.2. The EWMA is weighted per request, not per time unit: roughly the last 20
requests dominate the value, so under high traffic "recent" covers a very short time span
and under low traffic a long one. The measured latency covers the full downstream handler
chain, not just WAF inspection.
Reverse proxy / trusted proxy notes
When running behind Nginx, Caddy, Traefik, Cloudflare, or any other reverse proxy,
make sure Fiber trusted proxy settings are configured correctly before relying on
c.IP() or Coraza rules that depend on REMOTE_ADDR.
If trusted proxy validation is not configured correctly, spoofed forwarding headers may cause Coraza to evaluate rules against attacker-controlled client IP values.
Notes
- Request headers and request bodies are inspected.
- Request body size follows the Fiber app
BodyLimit. - Response body inspection is not supported.
coraza.New()starts successfully without external rule files, but it does not load any rules untilDirectivesFileis configured.- Invalid configuration causes
coraza.New(...)to panic during startup, which allows applications to fail fast.