Skip to main content
Proxy → WAF brings the web application firewall into one place: what it caught, where it came from, and what to do about it. It shows once a DNZ Proxy is approved. Traefik and Caddy have no WAF. Each node counts what its WAF matched and keeps it. DNZ asks the nodes when you open the page, and adds a place and a network to each address from its own databases. No address is sent to an outside service.

What it shows

Pick a span (1 hour to 30 days), a proxy, private or public proxies and a hostname at the top.
Events come from the nodes’ request logs, which keep a sample of what they serve. Blocked requests get ten times the room, so the totals are exact while the event list can miss some. Nodes older than DNZ 2.5 can’t filter their log for the WAF: the page names them until they are updated.

Private or public proxies

All / Private / Public beside the proxy filter looks at the attacks on local sites and those on public sites apart: which addresses, from where, against what. The category is the proxy’s own access, the one Serve on’s All private and All public use. It can be set together with one proxy (both must hold), it stays in the page’s address (?access=private) so a view can be shared, and the line under the filters says which proxies are being shown. A filter that leaves no proxy (private with a public proxy picked, or no proxy marked private) shows nothing and says why. It never shows everything.
A block or a ban keeps its own scope. It goes where you pick in its dialog (these hostnames, every route with the WAF, every DNZ Proxy), whatever the page is showing, and the dialog says so. Unblock takes the value out of every firewall, those of other proxies too.
Nothing is added to the request path and nothing is stored: the filter only picks which nodes DNZ asks.

Look an address up

Click any address on the page.
  • Place: the country, and the region and city with a city database.
  • Network: its ASN and owner, such as AS14061 DigitalOcean, LLC. Hosting marks the network of a company that rents out servers: requests from there are usually scripts, not people.
  • Reverse name: the name the address points back to. Confirmed means that name points to the address again; an unconfirmed name proves nothing, since anyone can write any name in their own reverse zone.
  • First and last seen, the rules it set off, the hostnames, paths and user agents it used, and its latest events.
  • The firewalls already blocking it.

Block an attacker

1

Pick what to block

In an address’s details: Block address, Block its network (the /24 of an IPv4 address, the /48 of an IPv6 one) or Block its country. A country’s panel and each event offer the same.
2

Pick where

These hostnames (the default) blocks it on the routes serving the hostnames it went for, so nothing else is touched. Every route with the WAF blocks it on every DNZ Proxy route whose WAF is on.
3

Block

Its requests get the 403 page before they reach your servers, within seconds on every node.
A block is a rule in each route’s firewall: waf-page-addresses holds the addresses and ranges, waf-page-countries the countries. Both go after the firewall’s own rules, so your allow rules still act first. A route without a firewall gets the shared waf-blocklist firewall. A firewall other routes use blocks the address there too. Unblock takes it out of every firewall again.

Bans that expire

A block stays until you take it out. A ban ends by itself: an address the WAF blocked too often is refused for a while, then let back in. It works like fail2ban, but across nodes. Bans are a DNZ Proxy feature on HTTP routes: Traefik and Caddy have no WAF to count, and a TCP or UDP route has no page to answer with. They are off by default. Turn them on per proxy, in its DNZ Proxy settings under Bans that expire: The node that sees an address cross the number bans it at once, without waiting for DNZ, and the other nodes have the ban within seconds. Only blocks count: a route in log-only mode bans nobody. A banned visit gets the 403 Access blocked page with its request ID, never a dropped connection, so a real visitor banned by mistake sees why and has something to send you. The request log shows block ban:<rule>, with the rule that led to the ban.
A ban never stops the entry point’s trusted forwarders, addresses the route’s WAF allows, what the route’s firewall lets through with an allow rule, or DNZ’s own uptime checks.
Banned now lists them, newest first, with Unban. To ban one address by hand for an hour, pick Ban for an hour in the block dialog; this works without the settings above. Ranges and countries are for blocks. On the map and the chart, banned requests have a colour of their own, and the switch above the map folds them into blocked. Either way they count as blocked in the totals, in Analytics and in attack alerts. Off, a visit reads one setting it already holds and does nothing more; on, it does one lookup, which measured the same as off with 10,000 bans held (Proxy benchmarks). A node older than DNZ 2.5 doesn’t ban, and the panel names it. A node restarted while DNZ is away holds no bans until it connects again. Decoy names ban through these too, whether or not automatic bans are on: a visit to a name nobody has a reason to open is refused from then on with the note block ban:decoy. Decoy names on this page lists who touched what.

Lists of bad networks

Outside lists of bad networks, kept up to date by DNZ. Your nodes never reach the internet for them: DNZ downloads each list and sends it to the nodes that need it. Lists are off by default, and a list does nothing by itself. A DNZ Proxy firewall rule names them, so it applies to the routes you pick and costs one lookup per request only there (nothing measurable with a list of 100,000 ranges, which takes a node about 8 MiB):
1

Add a rule to a firewall

In Proxy → Middlewares, open a firewall and add a rule with the condition Network list. DNZ downloads a list as soon as a rule names it.
2

Pick what it does

A rule on a list challenges by default: the Under attack browser check, which real browsers pass on their own and scripts don’t. It can block or only log instead.
3

Give the firewall to your routes

In the route dialog, or with batch edit.
In JSON, the condition is { "field": "list", "op": "in", "values": ["tor-exit", "spamhaus-drop"] }.
Saving such a rule is refused while a node of a proxy using that firewall is older than DNZ 2.5, naming the node: an older node would turn the routes using it off. What isn’t a browser (an app, a monitor) can’t pass a challenge and gets a 403, so add an allow rule above it for those you trust.
DNZ leaves out private and reserved ranges and anything wider than a /8 (/16 for IPv6), and keeps the last good copy when a download fails. Lists are read by the node’s firewall, so a Traefik or Caddy proxy has none. A list can’t be turned off while a rule names it.

Campaigns

The addresses the WAF matched, grouped by network (ASN), across hostnames, with what they did:
AS4134 Chinanet: 3 addresses, 291 requests, 2 hostnames, Path traversal and SQL injection
Without the ASN database (see Location data) they group by /24 (/48 for IPv6). Times and hostnames come from the latest events, so they may leave out some on a busy day. Open an address for its details, or block the campaign’s addresses or their /24s: the same block as above. Blocking a whole network by its ASN isn’t offered.

Bots

Decide what AI crawlers may do on a route, and catch visitors who only claim to be a search engine. Both are off by default and run on DNZ Proxy.
Traefik and Caddy can’t do this. A setting on one of their routes is kept but does nothing: the proxy’s page warns AI crawler settings are DNZ Proxy only, and the crawler gets in.
Only routes that ask look at a request’s user agent: one with an AI crawler setting, or one whose firewall has a rule on bots. Every other route runs exactly as before and counts nothing.

What a route lets AI crawlers do

GPTBot, ClaudeBot, CCBot, Bytespider, PerplexityBot and the others, by their user agent. Pick in the route dialog under Who gets in, in the Bots section of this page, or with --ai-crawlers: For As robots.txt says, the node reads /robots.txt from the site’s server after a crawler’s first visit, never while a visit waits, and keeps it for an hour.
A crawler is let in until the file is known, and whenever it can’t be read: a 404, a server error or a timeout all count as no rules for ten minutes. So a crawler’s first visit to a hostname always gets through. To keep it out from the first request, use Block.
/robots.txt itself stays open to crawlers, in Block too, so a polite one can read that it isn’t welcome. A firewall rule that allows the visit first wins over the setting. A route with a setting other than Allow looks at every request’s user agent: about 3 µs of CPU on one CPU (Proxy benchmarks). Allow costs nothing.
Google-Extended and Applebot-Extended are only words in robots.txt: their requests say Googlebot and Applebot, so no route can refuse them. Write them in your robots.txt.

Verified bots

A visitor claiming Googlebot, Bingbot, Applebot, DuckDuckBot, YandexBot, Baiduspider, UptimeRobot or Pingdom is first treated as a plain visitor. DNZ checks the address in the background, against the ranges the vendor publishes or by forward-confirmed reverse DNS (the reverse name must be in the vendor’s domains, such as *.googlebot.com, and that name must point back to the address). The verdict holds for a day: verified or fake, and a fake one’s log line says fake bot. None of it holds up a request. If the check can’t finish (a resolver that fails, a list that can’t be read), there is no verdict and it stays a plain visitor: a DNS failure never makes a real crawler fake. The same goes for a claim from a private address, which is usually a forwarder you haven’t told the entry point to trust. The names ship with DNZ, from each vendor’s own documentation, and reach the nodes only while a route or firewall of that proxy uses them. Names DNZ knows in the section lists them all.

Rules on bots

Add a rule with the condition Bot to a firewall in Proxy → Middlewares. It takes verified, fake and AI crawler, with any action:
A visitor with no verdict yet is none of them, so its first visits pass. A node refuses a firewall with a field it doesn’t know, and the routes using it: so a rule on bots and an AI crawler setting are refused while a node older than DNZ 2.5 serves that proxy, naming the node to update. Update a node before it joins.

The section

Humans here means what is left of the requests told apart: people, bots DNZ doesn’t know, and visitors whose claim hasn’t been checked yet. Routes that don’t tell bots apart count in none of these. Per hostname you see what its routes let AI crawlers do, and can change it there or for every route; each change is a version in route history. Click a fake bot’s address for its details.

Preview a higher sensitivity

What would this route block at High? Let it run the extra rules in shadow mode for a while before you raise it. A preview is off by default, per route, and turns itself off after 24 hours. Start one with Preview High in the route dialog’s WAF group, under Previews of a higher sensitivity on this page, or in a batch edit. While it runs, the rules above the route’s sensitivity only count, with the note shadow waf:<rule>:
  • the visitor gets exactly what they got before;
  • nothing shows as blocked or logged: not in the totals, the map, the chart, the attack alerts or a SIEM’s security events;
  • no address gets closer to a ban.
The panel says what came of it in words:
So far High would also block 37 requests: 30 from addresses the WAF stopped nothing else from (mostly /search), which may be real visitors, and 7 from addresses it also stopped.
Look at the first number before you switch: those may be real visitors. Only the busiest addresses are told apart, so the second number is at least. The events’ Preview only filter shows the requests themselves, from the log’s sample. Switch to High raises the route and ends the preview, and route history keeps a version. The end is a time kept with the route: neither a restart nor DNZ being away extends it, since each node reads the time itself. A node too old for previews ignores it, and the panel names it.
While it runs, a preview costs that route’s requests one more pass over the extra rules. Measured on one CPU, a preview of High on a route at Medium cost a browser-like request about 5.5 µs (6% fewer requests a second at the most a node handles) and a plain one about 2 µs. A route without a preview pays nothing more.

The weekly report

Every Monday at 08:00 in the server’s time zone, DNZ sends the seven days before it through the notification providers that ticked Weekly WAF report: requests blocked and logged, the busiest countries, rules and addresses, new bans, how many routes have the WAF, and the nodes that didn’t answer, whose numbers are missing from it. A new provider doesn’t get it until you tick it. Only the server that leads a cluster sends it, never a secondary. DNZ remembers which Monday it sent, so a restart or a new leader doesn’t send it twice. A server that was down at 08:00 still sends it, up to 48 hours late. Send it now on this page sends the last seven days as a test, which doesn’t count as Monday’s.

Try a firewall rule on past traffic

Before you save a firewall rule, Try on past traffic in the rule editor says what it would have matched: how many requests, from how many addresses, with examples. Pick the last hour to the last 7 days. The rule is what’s in the editor, unsaved: nothing is written anywhere. It runs on the nodes, over each one’s request log, with the code the firewall itself runs, so a result can’t differ from the real rule. For a firewall that routes run, it looks only at those routes’ hostnames. What the rules above it stop never reaches it, as in the firewall. The log is a sample, and the answer says so. Each node keeps a part of what it serves (blocked requests get ten times the room):
This rule would have matched 340 of the 5,210 lines in the logs’ sample, from 12 addresses. The logs hold 3.1% of the 168,000 requests served in this window, which comes to about 11,000 requests in all (an estimate).
The estimate scales each hour and hostname by the requests the node counted, which are exact. It is an estimate and no more: a request the log keeps more often than others weighs differently. What the log can’t answer is named, never guessed. A log line holds the host, method, path with its query string, address, country, user agent and referer. On a route that rewrites paths, a line may hold the rewritten path. Lines that were already blocked or challenged count too, and are said apart. It runs off the request path: one at a time on each node, at most 50,000 lines per node unless you ask for more (200,000 at most), newest first, within 20 seconds. If it stops early the answer says where. A node too old to try a rule isn’t asked and is named, as is one that didn’t answer. WAF exceptions and rate limits can’t be tried this way yet.

When it wasn’t an attack

When the WAF stops a real visitor or app, use Not an attack… on its event and pick the narrowest exception: It goes into the route’s WAF exceptions, and route history keeps a version, so you can undo it there. When a shield stopped it, the one choice is to turn that shield off on those routes.

Location data

  • Countries on the map are counted on the nodes. Set a GeoIP database URL in the proxy’s DNZ Proxy settings, as for country rules in the firewall. Without one, DNZ places the busiest addresses with its own country database and says so above the map.
  • Places and networks of addresses come from DNZ’s own country and ASN databases. Download fetches DB-IP’s free IP to Country Lite and IP to ASN Lite (CC BY 4.0); DNZ downloads them again every month. MaxMind GeoLite2 works too.

From the command line

AI assistants get the MCP tools proxy_waf_overview, proxy_waf_events, proxy_waf_address, proxy_waf_coverage, proxy_waf_bans, proxy_waf_lists, proxy_waf_leaked_passwords, proxy_waf_campaigns and, with write access, waf_block, waf_unblock, waf_exception, waf_ban and waf_unban. Downloading lists and location databases is left to people. proxy_bot_traffic reads the Bots section and, with write proxy_waf_coverage, proxy_waf_bans, proxy_waf_lists, proxy_waf_campaigns, proxy_waf_previews, proxy_waf_report, try_firewall_rule and, with write access, waf_block, waf_unblock, waf_exception, waf_ban and waf_unban; set_waf starts a preview. Downloading lists and location databases and sending the report are left to people. proxy_bot_traffic reads the Bots section and, with write access, set_ai_crawlers sets what AI crawlers may do. dnz proxy waf shields —since 7d # each shield’s CVE, routes and hits; routes worth a look