# Tuning the WAF

> Fix a false positive without lowering your defences: trusted IP addresses, and per-domain exceptions scoped to a URL, a method and a rule.

Source: https://www.corepanel.net/docs/security/waf-tuning/
Last updated: 2026-08-15
Part of the CorePanel documentation — https://www.corepanel.net/docs

---

Sooner or later a WAF rule will block something legitimate: a page builder posting HTML
that looks like XSS, an import tool sending SQL fragments, a plugin using a URL that
looks like traversal. CorePanel gives you two tools for that, and the order in which you
reach for them matters.

```
Is the whole client trusted (your office, a monitoring service, a payment callback)?
├── Yes → Trusted IP
└── No  → Is it one URL of one site misfiring on a known rule?
          ├── Yes → Exception (URL prefix + rule IDs)
          └── No  → Turn that specific rule off, per domain if your edition allows it
                    (turning a whole family off is the last resort)
```

Every step down that list costs you more protection. Stay as high up as you can.

## Trusted IPs

A trusted IP skips WAF rule inspection entirely — rule phases and greylisting. Access
control still applies: trusting an IP does not grant it access to anything, it only stops
the firewall from inspecting its requests.

Use it for:

- your own office or VPN address while you debug something;
- an uptime or security scanner you run yourself;
- a payment gateway or webhook source whose callbacks trip a rule.

Entries can be single addresses or CIDR ranges (`203.0.113.7`, `203.0.113.0/24`), IPv4 or
IPv6. Each entry is validated as a real address literal, so a typo is rejected rather than
silently producing a bypass that never matches. A list holds up to 200 entries.

Two levels:

| Level | Applies to | Editions |
|---|---|---|
| **Server-wide** | Every site on the server | All, including Personal |
| **Per domain** | One domain only | Pro and above |

Changes are applied on reload — no restart, no dropped connections.

> **The server itself never needs to be trusted**
>
> Requests arriving over **loopback** (`127.0.0.1`, `::1`) — cron, wp-cron, the cache
> revalidator, a local monitoring probe, one site calling another on the same box — are
> permanently exempt from greylisting. Rules still apply to them, so a local request that
> really is an injection is still refused; what cannot happen is the machine building a bad
> reputation against itself and then being refused wholesale. Do **not** add `127.0.0.1` to
> the trusted list to get that: it is already there in the only sense you want, and
> trusting it would additionally stop rule inspection for every local request, which is
> exactly the traffic a compromised site on the server would use.
> **A trusted IP is a hole, on purpose**
>
> An address on the list is unprotected. Never add a shared address (a whole datacenter
> range, a public proxy), and never add a CDN's edge ranges — you would be trusting every
> request the CDN forwards, which is to say everyone. Review the list periodically and take
> out the temporary entries you added while debugging.
## Exceptions

An exception is a scalpel: it skips **specific rule IDs** for requests whose path starts
with a **URL prefix**, optionally narrowed to certain **HTTP methods** and **client IPs**.

Typical shape of a real one:

| Field | Example | Meaning |
|---|---|---|
| **URL prefix** | `/wp-admin/admin-ajax.php` | Only requests to this path |
| **Methods** | `POST` | Only POSTs; GETs stay protected |
| **IPs** | *(empty)* | Any client — or restrict to your editors' addresses |
| **Rule IDs** | `XSS-002` | Only this rule is skipped; everything else still applies |

That exception lets a page builder save its content while leaving SQL injection, path
traversal and every other XSS vector intact on the same URL — and leaving `XSS-002`
itself active everywhere else on the site.

Exceptions are **per domain**, and a domain holds up to 200 of them.

### Writing a good exception

1. **Find the event.** The [event log](https://www.corepanel.net/docs/security/waf#the-event-log) gives you the
   rule ID, the exact URL and the method.
2. **Scope it to that URL**, not to `/`. A prefix of `/` is a domain-wide disable of that
   rule with extra steps.
3. **Name only the rules that actually fired.** Not the family, not a guess.
4. **Add methods** when the false positive only happens on one verb — usually POST.
5. **Add IPs** when the legitimate traffic comes from a known place. An exception limited
   to your editors' addresses is barely a hole at all.
6. **Verify**, then check the event log a day later to confirm nothing else broke.

Rule IDs, IP literals and method tokens are all validated, and a validation error points
at the offending row — so a typo cannot silently produce an exception that never matches,
or one that matches far more than you meant.

> **The protocol-integrity floor cannot be excluded**
>
> `STD-004`–`STD-008` (null bytes, path traversal, CRLF injection) are refused in
> exceptions just as they are refused in rule toggles. A legitimate request never contains
> a null byte or a bare CR in a header — if one of these fires on your traffic, the traffic
> is malformed and the fix belongs upstream.
## When an exception is the wrong answer

If a rule misfires **everywhere on a site**, an exception per URL is not maintainable —
turn the rule off for that domain (Pro and above) or server-wide, and write down why.

If a whole family misfires, the assumption is wrong rather than the rule. SQLI and XSS
cannot be lowered — they always block — so the answer there is per-rule: turn the specific
signature off server-wide, add the exceptions you need, and turn it back on. WordPress
Extended does have a *detect* mode: running it in detect for a few days, watching the event
log and then returning it to *block* with the right exceptions is a better route than
leaving the package off.

## A tuning session, end to end

1. A customer reports a 403 on their site.
2. **Event log**, filtered by their domain: `XSS-002`, `POST /wp-admin/admin-ajax.php`.
3. Confirm it is legitimate — the timestamps match what they were doing.
4. Add an exception on that domain: prefix `/wp-admin/admin-ajax.php`, method `POST`,
   rule `XSS-002`.
5. Ask them to retry. It works.
6. The next day, check that domain's log: no new blocks on that URL, and the other rules
   still firing on the attacks they should.
