Let Rootquill scan your site.
If a scan comes back HTTP 403 or 429, your WAF or application firewall is blocking us. Allowlist the scanner once per provider — Cloudflare, nginx, Apache, Vercel, Netlify, Caddy, or straight in your app code. Pick yours below.
Identify our scanner
Rootquill-Scanner/0.1 (+https://rootquill.com/docs/scanner)X-Rootquill-Scan: 151.81.87.112Match on the header rather than the user-agent when possible — header matches are harder to forge and don't break if we tweak the UA later.
1 Pick your provider
Cloudflare — WAF custom rule (recommended)
Takes 30 seconds in the dashboard. Works on free and paid plans.
- Go to your zone → Security → WAF → Custom rules → Create rule.
- Rule name:
Allow Rootquill scanner - Field: Custom header · Name:
X-Rootquill-Scan· Operator: equals · Value:1 - Action:
Skip· tick All remaining custom rules, Browser Integrity Check, User Agent Blocking, Rate limiting, Zone lockdown, Managed Rules. - Click Deploy. Live in a few seconds.
Cloudflare — via API
If you'd rather script it (or apply to 10+ zones at once):
# ZONE_ID = your zone id · CF_TOKEN = a token with Zone:WAF:Edit curl -X POST \ -H "Authorization: Bearer $CF_TOKEN" \ -H "Content-Type: application/json" \ "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_firewall_custom/entrypoint" \ -d '{ "rules": [{ "description": "Allow Rootquill scanner", "expression": "(any(http.request.headers[\"x-rootquill-scan\"][*] eq \"1\") or starts_with(http.user_agent, \"Rootquill-Scanner\"))", "action": "skip", "action_parameters": { "ruleset": "current", "products": ["bic", "hot", "rateLimit", "securityLevel", "uaBlock", "waf", "zoneLockdown"] }, "enabled": true }] }'
nginx — bypass in your server block
If you've got a WAF module (ModSecurity, naxsi, etc.) or a rule set that blocks by UA, add a tiny exception at the top of the relevant server block:
server {
listen 443 ssl;
server_name yoursite.com;
# Allow Rootquill scanner — must appear BEFORE any deny/limit_req / modsecurity rules
set $rq_scan 0;
if ($http_x_rootquill_scan = "1") { set $rq_scan 1; }
if ($http_user_agent ~ "Rootquill-Scanner") { set $rq_scan 1; }
if ($rq_scan = 1) {
modsecurity off; # if you run ModSec
# skip whatever else blocks you — rate limiting, UA deny, etc.
}
# ... rest of your config ...
}
Reload with nginx -t && systemctl reload nginx.
Apache — allow via .htaccess or vhost
# In your VirtualHost or .htaccess, BEFORE any deny rules: SetEnvIfNoCase User-Agent "Rootquill-Scanner" rq_scanner SetEnvIf X-Rootquill-Scan "1" rq_scanner <RequireAll> Require all granted Require not env block_bots </RequireAll> <If "%{reqenv:rq_scanner} == 'rq_scanner'"> UnsetEnv block_bots </If>
If you use mod_security, add SecRuleRemoveByTag "..." conditionally on %{ENV:rq_scanner}.
Caddy — matcher + handler
yoursite.com {
@rootquill_scanner {
header X-Rootquill-Scan 1
header_regexp User-Agent ^Rootquill-Scanner
}
# If you've got rate limiting or caddy-waf, skip them for @rootquill_scanner
# Example: caddy-waf wants you to add the matcher to its exempt list.
reverse_proxy @rootquill_scanner localhost:3000
reverse_proxy localhost:3000 # default handler
}
Vercel — Firewall rule
- Open Project → Firewall → Custom Rules → Add Rule.
- Name:
Allow Rootquill scanner - If:
Request Header·x-rootquill-scan· equals ·1 - Then:
Allow. Save.
If you use middleware.ts to block traffic before the handler runs, add the same header check to bail out early:
import { NextResponse } from "next/server"; export function middleware(req) { // Allow Rootquill scanner through any gates below if (req.headers.get("x-rootquill-scan") === "1") { return NextResponse.next(); } // ...your other middleware logic... }
Netlify — Edge Function or _headers rule
Netlify doesn't have a WAF block-list by default, so this mostly applies if you've got a bot-protection edge function. In that function, add:
export default async (request) => { if (request.headers.get("x-rootquill-scan") === "1") { return; // let it pass through to origin } // ...your bot check... };
Node — bypass in your bot/VPN middleware
If your app has its own IP, VPN, datacenter, or bot check (not a provider), add a one-line bypass before the block check:
// Belt-and-suspenders: require BOTH the header AND the Rootquill source IP, // so nobody on the internet can forge the header to bypass your gate. const ROOTQUILL_IPS = new Set(["51.81.87.112"]); app.use((req, res, next) => { const isRootquill = req.headers["x-rootquill-scan"] === "1" && ROOTQUILL_IPS.has(req.ip); if (isRootquill) return next(); // skip VPN/bot gate // ...your existing VPN / bot check... });
Hono is the same shape — c.req.header('x-rootquill-scan') and c.env.CF_CONNECTING_IP (or your framework's equivalent).
X-Rootquill-Scan: 1. Always pair the header check with the source IP above.2 Verify
Re-scan from the dashboard
Open Dashboard → Scanner, paste your URL, and click Scan. A successful response shows the issues ranked by impact. If you still get 403 / 429, the block is happening before the layer you configured — work out which hop is blocking (origin? CDN? edge function?) and apply the rule there too.
3 After launch
- Rootquill scans every site on your account once a week by default. You can trigger ad-hoc scans any time from the dashboard.
- Scanner traffic shows up as
User-Agent: Rootquill-Scanner/0.1in your access logs — handy for debugging. Traffic rate is low: one request per scan per URL. - If you rotate your allowlist rules later, the error message in Rootquill's dashboard tells you exactly which HTTP status came back (403, 429, timeout, etc.) so you can tell whether we got through.