DEV Community

Kai Ren
Kai Ren

Posted on

Self-hosted WAF without Docker — Caddy + Coraza + CRS in one binary

#go

If you already reverse-proxy with Caddy (or you’re tired of ModSecurity glue), there’s a boring option that works well on a single VPS: put OWASP CRS in front of your apps and keep the ops surface small.

This post is the path I use when I want app-layer filtering (SQLi/XSS/scanners) without a compose stack.

What you actually need

For most small/medium self-hosted setups:

Layer Job What does not solve it
Edge / scrubbing Volumetric DDoS, big botnets A reverse-proxy WAF alone
App-layer WAF SQLi, XSS, scanner junk, bad bots that look like browsers Geo-IP blocks forever
Auth / rate limits Credential stuffing, noisy clients CRS by itself

Don’t sell yourself a WAF as a DDoS product. Use CRS for the application junk; keep rate limits / challenges / an upstream scrubber for the rest.

The packaging tradeoff

Common self-hosted WAF paths:

  1. Docker WAFs (SafeLine and friends) — great UI, more moving parts.
  2. Roll your own — Caddy/Nginx + Coraza/ModSecurity + CRS + dashboards — flexible, glue-heavy.
  3. Single binary — reverse proxy + Coraza + CRS + admin UI in one process.

I’ve been using option 3 via Tiyi: one Go binary, SQLite, local admin UI/CLI, free on a single node. Demo (install → site → block SQLi):

https://www.tiyisec.com/assets/video/tiyi-demo.mp4

Install (Linux)

curl -fsSL https://www.tiyisec.com/install.sh | sh
tiyi run
Enter fullscreen mode Exit fullscreen mode

Open the admin UI, set the admin password on first run, then:

  1. Add an upstream (your real app)
  2. Add a site (hostname → upstream)
  3. Keep CRS enabled (default path)
  4. Hit the site with a trivial SQLi probe and confirm a block + security log

Exact CLI flags change with versions — the site/quickstart is the source of truth.

A minimal “did it work?” check

Once the site is live on the proxy port:

# should be blocked / challenged by CRS
curl -i "http://127.0.0.1:18080/?q=1%27%20OR%20%271%27%3D%271"
Enter fullscreen mode Exit fullscreen mode

Then check the security/events view in the UI (or CLI logs). If nothing shows up, you’re usually testing the wrong host/port or bypassing the proxy.

When not to use this

  • You need a full cloud WAAP / bot management suite
  • You must stay on Nginx/Apache modules only
  • You want multi-region active-active with a shared control plane on day one

For a homelab or a single production node where you already like Caddy semantics, the “one binary + CRS” shape is hard to beat.

Links

If you’re comparing this to SafeLine/BunkerWeb/ModSecurity stacks, comment with your constraints (Docker OK? Caddy required? UI needed?) — happy to map tradeoffs.

Top comments (0)