DEV Community

Jeff W
Jeff W

Posted on

Beyond IP Blocking: Filtering Requests by Header, Query Param, and Cookie

Most "block bad traffic" advice starts and ends with IP blocklists. That's a start, but IP-based rules break down fast in practice: attackers rotate through residential proxies, legitimate users share IPs behind NAT/CGNAT, and a single blocked IP can be one bad actor or a whole office building.

A layer that gets skipped way more often than it should: matching on the request itself — a header, a query parameter, or a cookie — instead of (or alongside) the source IP. Here's when each actually earns its place, with working examples for nginx, Apache, and Express.

1. Gate an internal/staging endpoint with a shared-secret header

If you have an endpoint that should only ever be hit by your own tooling — a webhook receiver, an internal admin API, a staging environment — don't rely on obscurity or IP allowlisting alone (IPs change, CI runners are ephemeral). Require a header instead:

# nginx
location /internal/ {
    if ($http_x_internal_token != "your-secret-value") {
        return 403;
    }
}
Enter fullscreen mode Exit fullscreen mode
# Apache (mod_rewrite)
<Location /internal/>
    RewriteEngine On
    RewriteCond %{HTTP:X-Internal-Token} !^your-secret-value$
    RewriteRule ^ - [F]
</Location>
Enter fullscreen mode Exit fullscreen mode
// Express middleware
app.use('/internal', (req, res, next) => {
  if (req.headers['x-internal-token'] !== process.env.INTERNAL_TOKEN) {
    return res.status(403).send('Forbidden');
  }
  next();
});
Enter fullscreen mode Exit fullscreen mode

This is cheap, doesn't care what IP the request comes from, and rotates trivially if it ever leaks (just change the env var).

2. Block obvious bot/scraper traffic by User-Agent or missing headers

A shocking amount of low-effort scraping traffic doesn't bother sending a realistic User-Agent, or omits Accept-Language entirely — headers a real browser always sends. You can catch a meaningful chunk of junk traffic just by requiring these to be present, before it ever reaches your app logic:

# nginx
if ($http_user_agent = "") {
    return 403;
}
Enter fullscreen mode Exit fullscreen mode
# Apache (mod_rewrite)
RewriteCond %{HTTP_USER_AGENT} ^-?$
RewriteRule ^ - [F]
Enter fullscreen mode Exit fullscreen mode

This won't stop a sophisticated attacker (they'll just fake a normal browser UA), but it's nearly free and kills a surprising amount of naive automated traffic.

3. Feature-flag or A/B test at the edge with query params or cookies

Not security-specific, but the same matching mechanism: routing based on a query param (?preview=true) or a cookie your app sets is how a lot of preview/canary deployments work without needing a separate hostname.

# nginx
if ($arg_preview = "true") {
    proxy_pass http://staging-backend;
}
Enter fullscreen mode Exit fullscreen mode
# Apache (mod_rewrite + mod_proxy)
RewriteCond %{QUERY_STRING} preview=true
RewriteRule ^(.*)$ http://staging-backend%{REQUEST_URI} [P,L]
Enter fullscreen mode Exit fullscreen mode

The catch

None of this is exotic — the trade-off is that hand-maintained RewriteCond/if blocks scattered across config files get messy fast, and a typo in a security-relevant rule is exactly the kind of thing that silently fails open instead of loud. Worth testing each rule against both a request that should pass and one that shouldn't, not just the happy path.

(I work on a managed WAF, ShieldIngress — this is one of the things it does automatically, but everything above works with whatever you're already running.)

Top comments (0)