Linux, Docker, Windows, and macOS.
This is the multi-page printable view of this section. .
CheeseWAF
- 1: How CheeseWAF works
- 2: Install
- 2.1: Linux (systemd)
- 2.2: Docker Compose
- 2.3: Windows
- 2.4: macOS
- 3: Quick start
- 3.1: Initialize
- 3.2: Add the first site
- 3.3: Connect a model
- 4: Concepts
- 4.1: Request pipeline
- 4.2: Paranoia levels
- 4.3: Isolated vs embedded
- 4.4: Three management surfaces
- 5: Sites and reverse proxy
- 6: Protection
- 6.1: Semantic engine
- 6.2: Custom rules
- 6.3: IP, geo, and fingerprint
- 6.4: Bot challenge and CAPTCHA
- 6.5: Rate limit
- 6.6: ACL
- 6.7: Block pages
- 7: API security
- 8: Edge headers, cache, and compression
- 9: TLS and certificates
- 10: ALAP and the review queue
- 11: Monitor, logs, and attack map
- 12: Cluster
- 13: Storage and scheduler
- 14: Web console
- 15: CLI and TUI
- 16: REST API
- 17: Configuration reference
- 18: Operations
- 19: Build from source
- 20: License and related repositories
CheeseWAF is a self-hosted Web Application Firewall. It ships as one Go binary with an embedded SQLite store, a Web console, a CLI / TUI, and a REST management API.
The data plane inspects requests, then proxies them upstream. It does not call a large language model on every request. After the response is sent, an optional ALAP queue can review suspicious samples.
Download packaged builds from GitHub Releases. The project is licensed under Apache License 2.0.
How it works
- Data plane. Parameters are decoded, then parsed. Deterministic SQL injection, XSS, and command execution can be blocked immediately.
- ALAP. After the response is sent, ambiguous or embedded samples go to a background queue. Any OpenAI-compatible model can review them.
- Review results. Findings marked
highorcriticalcan become lasting IP, fingerprint, or signature rules when auto-agree is on.
ALAP stands for AI Large-Language-Model Auto Pilot.
Default listeners
| Plane | Default address | Role |
|---|---|---|
| Data plane | http://127.0.0.1:8080 | Receive site traffic, inspect, proxy upstream |
| Management plane | http://127.0.0.1:9443 | Web console, REST API, setup wizard. Docker defaults to HTTPS |
| Cluster plane | http://127.0.0.1:9444 | Node sync in cluster mode |
| Local controller | http://127.0.0.1:17943 | Windows / macOS desktop controller only |
Start here
1 - How CheeseWAF works
Traditional regex WAFs keep large signature libraries. They are expensive to maintain and easy to evade with encoding or wrapping.
Calling a large language model on every request adds network latency you cannot hide.
CheeseWAF splits the work.
Two planes
Data plane
The process that accepts HTTP, HTTPS, or HTTP/3 does this in order:
- IP, GeoIP, and client soft-fingerprint checks
- Bot challenge, rate limit, and waiting room
- Semantic analysis of decoded parameter values
- Reverse proxy to the configured upstream
This path must stay fast. It never waits on a remote model.
Control plane
The management listener hosts:
- the setup wizard at
/setup - the Web console
- the REST API under
/api - optional Prometheus metrics
Keep server.admin_public false unless you also enable TLS and restrict who can reach the port.
ALAP
After the client already has a response, CheeseWAF can enqueue:
- isolated hits that were blocked at paranoia 5
- embedded hits that were allowed at levels 2–4
- other borderline samples the engine marks for review
A worker calls the configured model. The operator (or auto-agree) then saves a lasting rule or dismisses the sample.
See ALAP and the review queue.
What ships together
| Piece | Role |
|---|---|
cheesewaf | Forwarding process. Default command is serve |
waf-cli | Same binary or a symlink. Default command is the TUI panel |
cheesewaf-gui | Loopback-only desktop controller on Windows and macOS |
| Web console | React UI served from the management plane |
| SQLite | Default store, CGO-free (modernc.org/sqlite) |
You do not need Redis, Nginx, or an external database to start.
Related pages
2 - Install
Pick one install path. Do not mix an NSIS install with a hand-copied Linux tree on the same host unless you know which process owns the ports.
Release files
Download Alpha pre-releases, or take the same files from Actions artifacts.
| File | Platform |
|---|---|
cheesewaf-*-linux-amd64.tar.gz | Linux x86_64 |
cheesewaf-*-linux-arm64.tar.gz | Linux ARM64 |
cheesewaf-*-linux-loong64.tar.gz | Linux LoongArch |
cheesewaf-*-darwin-amd64.tar.gz / .dmg | macOS Intel |
cheesewaf-*-darwin-arm64.tar.gz / .dmg | macOS Apple Silicon |
cheesewaf-*-windows-amd64.exe | Windows x86_64 CLI |
cheesewaf-*-windows-arm64.exe | Windows ARM64 CLI |
cheesewaf-*-windows-amd64.zip | Windows x86_64 portable tree |
cheesewaf-*-windows-arm64.zip | Windows ARM64 portable tree |
CheeseWAF-*-windows-*-setup.exe | Windows NSIS installer |
After install, continue with Quick start.
2.1 - Linux (systemd)
Use this path on a Linux VM or bare metal host.
Unpack
Replace amd64 with arm64 or loong64 when that is the CPU.
Install files
The Linux tarball includes systemd/cheesewaf.service.
Open http://<host>:9443/setup and continue with Initialize.
Paths
| Path | Role |
|---|---|
/usr/local/bin/cheesewaf | Binary |
/etc/cheesewaf/cheesewaf.yaml | Config |
/var/lib/cheesewaf | Data, SQLite, certs |
/var/log/cheesewaf | Logs |
Bind the data plane to a public address only after you have a site, an upstream, and a paranoia level you accept.
2.2 - Docker Compose
Use this path in a container host.
docker compose build produces linux/amd64 or linux/arm64 for the host CPU.
The image runs as UID 10001.
The root filesystem is read-only.
Compose file
The repository file is deploy/docker/docker-compose.yml.
A minimal copy:
Build context must be the CheeseWAF repository root when you use that Dockerfile.
Start
Open https://<host>:9443/setup.
The container uses a self-signed admin certificate by default.
The first-run token is in the startup log.
docker compose down keeps the named volumes.
Site config and SQLite live in cheesewaf-data.
2.3 - Windows
Windows has three shapes. They are not three different WAFs.
A. Single-file CLI
- Download
cheesewaf-*-windows-amd64.exeor thearm64file. - Run:
The forwarding process does not need the installer.
The Web UI assets live in web/dist next to the executable in zip / DMG / tar packages.
B. Portable zip
- Unpack
cheesewaf-*-windows-amd64.zipto a directory such asD:\CheeseWAF. - Run:
C. NSIS installer
- Run
CheeseWAF-*-windows-amd64-setup.exeor thearm64setup. - Follow the wizard.
- Uninstall keeps
data\by default.
The installer may register a Windows service (sc.exe create CheeseWAF …). Treat that as best-effort.
Local controller
cheesewaf-gui is not a second admin console.
It only starts, stops, and opens the real management UI.
- Bind address:
127.0.0.1:17943 - Shows PID and running state
- Opens the Web console and the config folder
- Optional current-user autostart (
HKCU\Run)
The browser opens http://127.0.0.1:17943/.
2.4 - macOS
DMG
- Download
cheesewaf-*-darwin-arm64.dmg(Apple Silicon) orcheesewaf-*-darwin-amd64.dmg(Intel). - Open the image and drag CheeseWAF into Applications.
- Launch CheeseWAF from Launchpad or Applications.
The app starts the local controller. Use it to start, stop, and open the Web console.
Runtime data is under ~/Library/Application Support/CheeseWAF.
CLI tarball
If you only want the command line:
Then open http://127.0.0.1:9443/setup.
3 - Quick start
Do these three steps after the process is running.
The data plane works without a model.
ALAP review stays empty until you configure ai.
3.1 - Initialize
Open the wizard
On a local install open http://127.0.0.1:9443/setup.
On Docker open https://<host>:9443/setup and accept the self-signed certificate.
If the process prints a setup token, paste it when the wizard asks.
Create the admin
Set a username and a password that meets the console password policy. Save every generated secret the wizard shows. CheeseWAF will not print them again in clear text.
Confirm the listener
Leave server.admin_listen on loopback for a single-host install.
Set server.admin_public to true only with TLS and a network policy in front.
After setup, the same URL becomes the login page.
CLI users can also run waf-cli (TUI) or cheesewaf user.
3.2 - Add the first site
In the console open Sites → New site.
Domain
Enter the hostname clients already use, for example app.example.com.
CheeseWAF matches sites[].domains.
Upstream
Enter the origin address, for example 10.0.0.10:8000.
More than one upstream uses the site loadbalance policy (round_robin by default).
Paranoia
Use level 3 for a first production site. Level 3 blocks isolated attack values and allows embedded hits for later ALAP review.
Save
Save the site. The process reloads the site list without a full restart.
Point DNS or the local hosts file at the CheeseWAF data-plane address. Confirm the origin still answers through CheeseWAF before you raise the level.
Details: Sites and reverse proxy.
3.3 - Connect a model
ALAP is optional for a first day. Turn it on when you want asynchronous review and lasting rules.
In the console open AI.
| Field | Meaning |
|---|---|
| Enabled | Master switch (ai.enabled) |
| Provider | openai or anthropic |
| Endpoint | Chat Completions / Messages base URL, for example https://api.openai.com/v1 |
| API key | Secret for that endpoint |
| Model | Model name, for example gpt-4o-mini |
| Auto-agree | When on, high / critical findings can become lasting rules |
The sample config starts with ai.enabled: false.
ai.async stays true so the data plane never waits on the model.
Use Test connection in the console before you trust auto-agree.
4 - Concepts
Read these pages before you raise a site from level 3 to 4 or 5.
4.1 - Request pipeline
flowchart TB
Client[Client] --> Ingress[HTTP / HTTPS / HTTP3]
Ingress --> IP{IP / geo / fingerprint}
IP -->|deny list| Block[Block page]
IP -->|allow| Bot{Bot / rate limit / waiting room}
Bot -->|challenge| Challenge[CAPTCHA or queue]
Challenge -->|pass| Sem
Bot -->|allow| Sem[Semantic engine]
Sem --> Shape{Isolated or embedded}
Shape -->|isolated 2-5| Block
Shape -->|embedded 5| Block
Shape -->|embedded 2-4| Pass[Allow and enqueue]
Shape -->|clean| Origin[Upstream]
Pass --> Origin
Pass -.-> Queue[ALAP queue]
Sem -.->|level 5 block| Queue
Queue --> LLM[Configured model]
LLM --> Review{Decision}
Review -->|high / critical| Rule[Lasting rule]
Review -->|low or false positive| Dismiss[Archive or allow list]
Rule -.-> IPSolid arrows stay on the request path. Dashed arrows run after the client already has a response.
Site-level waf.mode can be block or a record-only style depending on paranoia.
Level 0 and 1 never block on semantic hits.
See Protection for each filter in this diagram.
4.2 - Paranoia levels
Set sites[].waf.paranoia_level per site.
Legal values are 0–5.
The default is 3.
The engine looks at one decoded parameter value at a time. Path and parameter names stay visible.
| Level | Name | Isolated | Embedded | Timed rise |
|---|---|---|---|---|
| 0 | Record only | Log, allow | Log, allow | No |
| 1 | Low monitor | Log, allow | Log, allow | No |
| 2 | Low-medium | Block | Allow, review later | No |
| 3 | Standard | Block | Allow, review later | No |
| 4 | Medium-high | Block | Allow, review later | Yes (rise to 5) |
| 5 | Strict | Block | Block, then review | Already max |
Temporary rise
At level 4, an embedded hit can raise the site to level 5 for promote_seconds (for example 300 seconds).
The deadline is stored in SQLite.
A process restart does not clear it.
Level 5 review
A sample blocked at level 5 still enters the review queue with a blocked mark.
You cannot flip it to allow.
You can save a lasting deny rule (feature, URL, IP, or fingerprint).
4.3 - Isolated vs embedded
The semantic engine classifies each decoded value as isolated or embedded. Paranoia levels treat the two shapes differently.
Isolated
The value is almost entirely an attack payload.
Weak wrappers such as @, a trailing semicolon, or /{${...}} still count as isolated.
Example: a search box that contains UNION SELECT 1,2,3.
Embedded
Attack-like tokens sit inside a long article, a product description, or a technical discussion.
Example: a forum post that quotes a SQL snippet.
Levels 2–4 allow embedded hits and enqueue them for ALAP. Level 5 blocks them.
Current isolation scope
This is an implementation fact, not a marketing promise:
- Isolated gadget coverage includes PHP/JSP live shells, Log4j JNDI, and short quoted/predicate SQL (at most 96 runes).
- XSS, command/RCE, SSTI, SSRF, and XXE use the document-shape guard. They are not on that gadget list.
- Hits marked embedded skip the block below level 5.
- Unclassified hits still follow
blockableHitevidence. They are not auto-treated as embedded. - Isolation lowers false positives on covered gadgets. It is not a free pass for every technical article.
4.4 - Three management surfaces
| Surface | When to use | How to reach it |
|---|---|---|
| Web console | Daily ops, rules, logs, attack map | http://127.0.0.1:9443/ after setup |
| CLI / TUI | Headless hosts, scripts | waf-cli or cheesewaf panel |
| REST | Automation, CI | /api/... with a session cookie or a management API token |
setup.three_end_unified is on in the sample config.
A user created in the console can use the CLI.
A token created under System can call REST with the same RBAC permissions.
Permissions live under apisec.permissions.
The sample grants admin: ["*"] and readonly: ["read:*", "read:cluster"].
Audit events write to apisec.audit.path when apisec.audit.enabled is true.
5 - Sites and reverse proxy
A site is one public hostname set plus one or more origins. CheeseWAF is the reverse proxy in front of those origins.
Create and edit
Console: Sites.
REST: GET/POST /api/sites, GET/PUT/DELETE /api/sites/{id}.
You can also import an Nginx server block with POST /api/nginx/import.
Fields that matter
| Field | Config key | Notes |
|---|---|---|
| Site id | sites[].id | Stable id, used in URLs |
| Name | sites[].name | Display name |
| Domains | sites[].domains | Host header match |
| Upstreams | sites[].upstreams[].address | host:port, optional weight |
| Listen port | sites[].listen_port | Optional extra listener |
| Load balance | sites[].loadbalance | Default round_robin |
| Enabled | sites[].enabled | Off = skip this site |
| WAF on | sites[].waf.enabled | |
| Mode | sites[].waf.mode | Usually block |
| Paranoia | sites[].waf.paranoia_level | 0–5 |
| Engines | sites[].waf.semantic_engines | sql, xss, rce, lfi, xxe, ssrf, nosql, ssti |
| Custom rules | sites[].waf.custom_rules | Regex on URI or other locations |
| Rewrite | sites[].waf.rewrite | Path rewrite or redirect |
| Health check | sites[].waf.health_check | Path, interval, thresholds |
| Trusted CIDRs | sites[].waf.access_control.trusted_cidrs | Real client IP behind another proxy |
Health checks
When health_check.enabled is true, CheeseWAF probes health_check.path on each upstream.
Unhealthy origins leave the pool after unhealthy_threshold failures.
Rewrites
A rewrite rule has pattern, replacement, and optional redirect_code.
redirect_code: 0 rewrites internally.
A 3xx code sends the client to the new path.
Per-site policy overlay
sites[].waf.protection_policy can override the global protection.policy keys:
web_attackapi_securitybot_ccthreat_intel
Empty strings inherit the global value (smart in the sample).
6 - Protection
Global defaults live under protection and protection.policy.
A site can overlay sites[].waf.protection_policy.
Console pages: Protection, Rules, IP, Bot challenge, Block pages.
6.1 - Semantic engine
The semantic engine does not ship a huge regex corpus as the primary detector. It decodes the parameter, then walks an abstract syntax tree for the enabled families.
Enable engines
Under sites[].waf.semantic_engines:
| Key | Looks for |
|---|---|
sql | SQL injection |
xss | Cross-site scripting |
rce | Command / RCE |
lfi | Local file include |
xxe | XML external entity |
ssrf | Server-side request forgery |
nosql | NoSQL injection |
ssti | Server-side template injection |
Turn an engine off when that family cannot appear on the site. Do not turn them all off and expect CheeseWAF to still catch web attacks.
Budget and allow lists
sites[].waf.semantic_policy:
budget_exhausted_policy:autofollows theweb_attackpolicy when the analysis budget is spentpath_allowlist: skip semantic analysis on these pathsparam_allowlist: skip these parameter names
sites[].waf.performance caps max_body_bytes, max_header_bytes, and proxy_timeout.
Response inspection
sites[].waf.response can scan the origin body for leaked secrets (AWS key pattern, password assignments, and similar).
Keep max_body_bytes modest.
How isolated vs embedded hits are treated: Isolated vs embedded.
6.2 - Custom rules
Custom rules sit next to the semantic engine. They are useful for admin probes, scanner paths, and one-off business denials.
Console: Rules.
REST: /api/rules and sites[].waf.custom_rules.
The sample ships this rule:
| Field | Meaning |
|---|---|
id | Stable id |
pattern | Regular expression |
location | Where to match. Sample uses uri |
action | Usually block |
severity | Shown in logs and review |
priority | Lower number runs earlier when the engine sorts that way — keep ids unique |
Do not try to rebuild a full ModSecurity ruleset here. Use custom rules for short, reviewable patterns.
6.3 - IP, geo, and fingerprint
Console: IP.
Config: protection.ip.
REST: /api/ip, /api/protection/ip, /api/ip/threat-intel/*.
Static lists
Allow-listed addresses skip later IP denies. Deny-listed addresses never reach the semantic engine.
GeoIP
Set geoip.enabled: true and point database at a MaxMind-style Country MMDB.
blocked_countries uses ISO country codes.
CheeseWAF does not download GeoLite2 for you.
Threat intel
Operators can import, export, sync, and test providers from the console.
Lookups are available at POST /api/ip/threat-intel/lookup.
Fingerprints
The data plane records a soft client fingerprint (not a hardware TPM identity). After a high-confidence review, ALAP can save a fingerprint deny rule. Treat fingerprint hits as supporting evidence, not as the only control.
When CheeseWAF sits behind another proxy, fill sites[].waf.access_control.trusted_cidrs or trusted_proxy_providers so the client IP is not the proxy’s address.
6.4 - Bot challenge and CAPTCHA
Console: Bot challenge, plus CAPTCHA lab for operators who design challenges.
Config: protection.bot and console.login.captcha.
The sample starts with protection.bot.enabled: false.
Turn it on only after you have a site that can complete a browser challenge.
Traffic challenge
| Key | Role |
|---|---|
js_challenge | Issue a JS clearance cookie |
captcha | Extra CAPTCHA after JS |
captcha_type | pow, image, or slider |
cookie_name | Default cheesewaf_js_clearance |
path_prefixes | Where the challenge applies |
exempt_path_prefixes | Skip, sample includes /health |
suspicious_user_agents | Extra scrutiny for curl, sqlmap, nuclei, and similar |
Keep secret out of git.
Let the process generate it into the data directory.
Challenge kinds
- PoW / Altcha. Header
X-CheeseWAF-Altchaby default. - Slider. Geometry and min-drag live under
slider_captcha_*. - Image. Length, size, and audio-limit knobs.
- Behavior pack. Curve draw, scratch, icon click, and related lab types. Use the lab before you enable them on production traffic.
Upload custom assets under /api/captcha/assets.
Quota and remote source tests are on the same console page.
Login CAPTCHA
console.login.captcha protects the management login, not the data plane.
The sample uses a slider with an optional PoW.
console.login.security_entry can hide the login behind a secret path and cookie.
Waiting room
waiting_room plus waiting_room_max_active queues excess clients instead of dropping them immediately.
See also Rate limit.
6.5 - Rate limit
Config: protection.ratelimit.
REST: PUT /api/protection/ratelimit.
This is a token bucket on the data plane.
It is not the same as apisec.rate_limits, which match one method + path on discovered APIs.
When the bucket is empty, CheeseWAF can:
- return a 429-style block page
- or send the client to the waiting room when that is enabled
Start with the sample numbers.
Lower requests only after you have a week of logs.
6.6 - ACL
Config: protection.acl.
REST: PUT /api/protection/acl.
The sample denies /debug:
Empty method means any method.
Set header + header_value to require or reject a header.
ACL runs early. Use it for operator-known junk paths. Use custom rules when you need a regex, not a prefix.
6.7 - Block pages
Config: block_page.
Console: Block pages.
REST: /api/block-pages/*.
GET /api/block-pages/templates lists built-in templates.
POST /api/block-pages/preview and the /block-pages/preview window show the rendered page without publishing it.
Upload custom HTML with POST /api/block-pages/upload.
Delete it with DELETE /api/block-pages/custom.
A block page can include a trace id. Give that id to the operator when you open a log detail. Do not put origin hostnames or internal IPs in custom HTML.
7 - API security
Console: API security.
Config: apisec.
REST: /api/apisec/* plus the permission tables used by every other /api route.
Discovery
When apisec.discovery.enabled is true, CheeseWAF samples recent traffic (sample_limit, window) and lists endpoints.
ignore_prefixes skips static assets.
GET /api/apisec/endpoints returns the current map.
POST /api/apisec/validate checks one request against a schema.
Validation
Enable a schema only after you have confirmed the path and required fields.
JWT
apisec.auth can require JWT issuers, audiences, scopes, and algorithms.
Keys can come from a shared secret, a PEM file, inline PEM, a JWKS file, inline JWKS, or a remote jwks_url.
Remote JWKS is cached in jwks_cache_file and refreshed every jwks_refresh_interval.
API rate limits
This is per discovered API route, not the global data-plane bucket.
Permissions
Management routes use names such as read:sites, write:protection, use:ai, approve:ai, manage:api_tokens.
See REST API for the route-to-permission map.
8 - Edge headers, cache, and compression
Console: Edge.
Config: edge.
REST: GET/PUT /api/edge.
Headers
Use set to add a marker.
Use delete to strip origin-only headers before they reach the client.
Cache
Only cache prefixes you know are static. Do not cache authenticated HTML.
Compression
content_types in the sample covers text/, JSON, JavaScript, XML, and SVG.
9 - TLS and certificates
Console: SSL.
Config: server.admin_tls, tls, and per-site ACME calls.
REST: /api/acme/providers, POST /api/sites/{id}/acme/issue.
Admin listener
Docker images turn admin TLS on with a self-signed cert. A public admin listener must use a real certificate.
Site TLS
server.listen_tls and server.listen_http3 bind the data plane.
HTTP/3 needs server.http3.enabled and a TLS listener.
ACME
The console lists DNS providers at GET /api/acme/providers.
POST /api/sites/{id}/acme/issue requests a certificate for that site.
Keep account keys in the data directory, not in the git repo.
10 - ALAP and the review queue
Console: AI and Review.
Config: ai.
REST: /api/ai/* and /api/review/*.
Queue
After the response, CheeseWAF can enqueue samples for the model.
The worker uses Chat Completions or Messages, depending on ai.provider.
Keep ai.async: true.
The data plane must not wait on this path.
Decisions
GET /api/review lists items.
POST /api/review/{id}/decide records allow, deny, or save as a rule.
At paranoia 5, a blocked item cannot be flipped to allow. You can still save a lasting rule.
Auto-agree
When auto-agree is on, high and critical findings can become IP, fingerprint, or signature rules without a human click.
Start with auto-agree off until you have reviewed a week of queue items.
Assistant and tools
POST /api/ai/assistant (and the stream variant) chats with tools that can change config.
Dangerous tools go through /api/ai/tools/approvals.
Roles:
use:ai— analyzewrite:ai— change AI config, run self-learningapprove:ai— approve a pending tool call
Self-learning
POST /api/ai/self-learning/run starts a scheduled-style pass over recent samples.
The scheduler can also run this on a timer. See Storage and scheduler.
11 - Monitor, logs, and attack map
Console: Dashboard, Logs, Monitor, Attack map.
Config: logging, monitor.
REST: /api/stats, /api/logs, /api/monitor, /api/metrics, /api/notifications, /api/audit.
Logs
GET /api/logs lists events.
/logs/{traceId} in the console opens one request.
Optional sinks: PostgreSQL, ClickHouse, VictoriaLogs. See Storage.
Prometheus
When public is false, scrape /api/metrics with a management token.
When public is true, the same path is exposed on the router root. Do not do that on the internet.
monitor.remote_write can push to a remote Prometheus-compatible endpoint.
Alerts
The sample defines high-block-rate and disk-usage rules.
Notifiers support webhook endpoints (monitor.notifiers).
In-app notifications use /api/notifications.
Attack map
/attack-map and /attack-map/screen plot recent blocks.
console.map.china_boundary can load a reviewed China boundary file.
Do not point source at an untrusted URL (allow_insecure / allow_private stay false unless you know why).
12 - Cluster
Console: Cluster.
Config: cluster.
CLI: cheesewaf cluster.
REST: /api/cluster/*.
The sample is a single node:
Turn clustering on
- Set
cluster.enabled: trueand pick acluster_id. - Give every node a unique
node_id. - Point
interconnect.advertise_addrat an address other nodes can reach. - Keep
mtls_required: true. Fillca_file,cert_file,key_file.
cluster.protection.freeze_writes_without_majority stops config writes without a majority.
allow_traffic_in_protection_mode decides whether the data plane still forwards during that freeze.
Join
POST /api/cluster/join-tokens mints a token (token_ttl, default 15m).
A new node calls POST /api/cluster/join.
require_approval: true waits for an operator.
Operations
| Action | Route |
|---|---|
| Status | GET /api/cluster/status |
| Nodes | GET /api/cluster/nodes |
| Heartbeat | POST /api/cluster/nodes/{id}/heartbeat |
| Rotate cert | POST /api/cluster/nodes/{id}/rotate-certificate |
| Revoke node | POST /api/cluster/nodes/{id}/revoke |
| Ansible pack | POST /api/cluster/deploy/ansible |
| Rolling upgrade | POST /api/cluster/orchestrate/rolling-upgrade |
| Rollback | POST /api/cluster/orchestrate/rolling-upgrade/{id}/rollback |
| Consensus | GET /api/cluster/consensus |
cluster.consensus.provider is builtin in the sample.
etcd_endpoints is reserved for an external provider and stays empty unless you switch.
13 - Storage and scheduler
Config: storage, setup.data_dir, scheduler.
Console: Operations, System.
REST: /api/storage, /api/backup/*, /api/scheduler/*.
Default store
SQLite holds users, review items, promote deadlines, and operational state.
It uses modernc.org/sqlite (no CGO).
Extra sinks
| Sink | When to enable |
|---|---|
storage.postgresql | Share logs with an existing Postgres |
storage.clickhouse | High-volume log analytics |
storage.victorialogs | VictoriaLogs HTTP ingest |
storage.redis | Optional cache / coordination. Off in the sample |
Private endpoints stay blocked unless allow_private_endpoint is true.
POST /api/system/storage/test checks a backend before you switch.
Scheduler
GET /api/scheduler/tasks and PUT /api/scheduler/tasks edit the list.
GET /api/scheduler/history shows runs.
Backup
POST /api/backup/export downloads a backup.
POST /api/backup/restore applies one.
POST /api/storage/cleanup and POST /api/system/reclaim free disk after you have a backup.
14 - Web console
After setup, open the management URL (default http://127.0.0.1:9443/).
The console is a React app served by the same process. It uses session cookies plus CSRF. Login can require a login CAPTCHA.
Page map
| Console route | This manual |
|---|---|
/ Dashboard | Monitor |
/sites | Sites |
/ssl | TLS |
/rules | Custom rules |
/review | ALAP |
/logs | Monitor |
/ip | IP and geo |
/protection | Protection |
/bot-challenge | Bot and CAPTCHA |
/edge | Edge |
/ai | ALAP |
/monitor | Monitor |
/apisec | API security |
/users | Operations |
/ops | Storage |
/updates | Operations |
/block-pages | Block pages |
/attack-map | Monitor |
/cluster | Cluster |
/system | Operations |
/captcha-lab | Bot and CAPTCHA |
Themes (light, dark, and several color packs) are local to the browser. They do not change the data plane.
15 - CLI and TUI
The same binary answers to two names.
| Invoked as | Default command |
|---|---|
cheesewaf | serve |
waf-cli | interactive TUI (panel) |
Global flags:
Language order: flag, then environment, then data-dir, then the OS locale.
Commands
| Command | Purpose |
|---|---|
serve | Start the WAF |
panel | TUI |
status | Is the process up |
healthcheck | Exit non-zero when unhealthy (Compose uses this) |
stop | Stop a running process |
restart | Stop then serve |
user | Manage local users |
cluster | Join, certs, runtime |
version | Version, channel, build time |
lang | Persist CLI language |
logs | Pack or inspect logs |
Examples:
On Windows, copy cheesewaf.exe to waf-cli.exe if you want the TUI name.
The desktop controller is a separate binary: Windows install.
16 - REST API
The management API lives under /api on the admin listener, not on the data plane.
Auth
Two ways in after setup:
- Session.
POST /api/auth/login, then send the session cookie. State-changing calls need the CSRF middleware. - Management token. Create one at
POST /api/system/api-tokens(manage:api_tokens). Send it as a bearer token on later calls.
Public before login:
| Method | Path |
|---|---|
| GET | /health, /health/live, /health/ready, /health/cluster |
| GET | /api/auth/login-options |
| POST | /api/auth/captcha, /api/auth/captcha/verify, /api/auth/login |
| POST | /api/setup, /api/setup/probe |
| GET/PATCH | /api/setup/draft |
| POST | /api/cluster/join |
| POST | /api/cluster/nodes/{id}/heartbeat |
Permission map
Common require("…") names from the router:
| Prefix | Examples |
|---|---|
read: / write: sites | List and edit sites, ACME issue |
read: / write: rules | Custom rules |
read: / write: protection | IP, ACL, bot, rate limit, review decide |
read: / write: threat_intel | Import, sync, lookup |
read: / write: edge | Header / cache / compression policy |
read: / write: ai, use:ai, approve:ai | Config, analyze, assistant, approvals |
read: / write: cluster | Nodes, join tokens, rolling upgrade |
read: / write: system | Version, time sync, backup |
manage:api_tokens | Create and revoke tokens |
read: / write: users | Local users and 2FA |
read: logs | Access logs and review list |
read: monitor | Stats, metrics, notifications |
read: audit | Audit log |
read: realtime | SSE /api/realtime/events, WebSocket /api/realtime/ws |
read: / write: ops | Scheduler |
read: / write: storage | Stats and cleanup |
read: apisec | Discovered endpoints |
admin: ["*"] in the sample bypasses individual checks.
Errors
Failed calls return JSON with an error field and an HTTP status. Do not retry login blindly after a CAPTCHA failure — request a new challenge.
17 - Configuration reference
First start writes cheesewaf.yaml into the data directory.
The template is configs/cheesewaf.yaml in the product repo.
| Key | Manual |
|---|---|
server | TLS, Intro |
tls | TLS |
setup | Initialize, Storage |
deployment / cluster | Cluster |
console | Bot and CAPTCHA, Monitor |
sites | Sites |
protection | Protection |
block_page | Block pages |
storage | Storage |
logging | Monitor |
ai | ALAP |
update | Operations |
scheduler | Storage |
edge | Edge |
monitor | Monitor |
apisec | API security |
Timeouts
server.read_timeout, write_timeout, and idle_timeout apply to the HTTP servers.
sites[].waf.performance.proxy_timeout applies to the origin.
Reload
Saving a site or a protection policy in the console hot-reloads that slice.
A change to listen addresses still needs a process restart (cheesewaf restart or systemd).
18 - Operations
Console: Users, System, Updates.
Users
GET/POST /api/users, PUT /api/users/{id}.
Each user can enable TOTP: /api/users/{id}/2fa/setup, enable, disable, recover.
CLI: cheesewaf user.
Do not share the first admin password.
Create a readonly role account for people who only need logs.
Time sync
GET /api/system/time-sync shows the current clock source.
POST /api/system/time-sync/reselect picks again.
POST /api/system/time-sync/sync syncs now.
JWT and TOTP break when the host clock is wrong. Fix time before you debug “invalid token”.
Updates
Keep auto_update_binary false until you trust the OTA server and the public key.
verify_signature must stay true.
System
GET /api/system and PUT /api/system read and write system settings.
GET /api/version prints the running version.
See also Storage and scheduler for backup and cleanup.
19 - Build from source
You only need this page if you compile CheeseWAF yourself. Operators should use Releases.
Toolchain
- Go 1.26 or newer
- Node.js 24.x and npm
Build
Tests
cheesewaf-corpus runs the built-in attack corpus against the analyzer.
It is a development check, not a production daemon.
20 - License and related repositories
CheeseWAF source code is licensed under Apache License 2.0.
The CheeseSec and CheeseWAF names, and the product logo, are not licensed as trademarks. See the NOTICE file in CheeseSec_pages when you reuse branding.
Repositories
| Repo | Role |
|---|---|
| LaokeQwQ/CheeseWAF | Product source |
| LaokeQwQ/CheeseSec_Docs | This documentation site |
| LaokeQwQ/CheeseSec_pages | Marketing site |
Report product bugs on the CheeseWAF issue tracker. Report documentation bugs on CheeseSec_Docs.