You know the screen from the big CDNs: one second of "checking your browser", then you are in. That screen does two things — keep a known-bad address out entirely, and put a doubtful one over a hurdle. What makes it work, though, is not the screen. It is the list behind it.
IP-DB Shield runs that same gate on your own server, with your name on it. No DNS change, no traffic routed through anybody, no account to upgrade: the decision is made inside your own PHP process, from a copy of the blocklist sitting on your disk. One file, no dependencies, PHP 7.0 or newer, MIT licence.
What's in the box
ipdb-shield.php— the gate itself; one file, no dependencies.config.php— the only file you need to edit.theme/screen.htmlandtheme/strings.json— the three visitor screens and their wording (Turkish, English, German, Spanish). Restyle freely.static/ipdb-shield.js— the edition for sites with no PHP at all.examples/— .htaccess, nginx, cron and WordPress snippets.
The three-minute install
Download the kit from the Shield page and create a free API key in the dashboard.
Only two values in config.php are required: api_key and the generated secret. Then arm the gate — either as the first thing your site runs:
…or without touching any code, in .htaccess:
Finally, add the cron line from examples/cron.txt so the list refreshes off the critical path.
Day one: block nobody
The kit ships with 'mode' => 'monitor', and it should stay that way for a day. In monitor mode nobody is stopped, but every decision is logged: the private panel URL that --status prints shows who would have been refused, for what reason, and how often. Reading a day of your real traffic and then switching to 'protect' is a far better decision than guessing at thresholds.
How a request is decided
The order is fixed and the first match wins:
| # | Case | Outcome |
|---|---|---|
| 1 | Not a page (assets, XHR, webhooks, wp-cron) | Untouched |
| 2 | Your own allow_ips | Through |
| 3 | Your own deny_ips | Refused |
| 4 | A trap path (/.env, /.git/…) | Refused, and reported if you opted in |
| 5 | A verified search crawler (forward-confirmed rDNS) | Through |
| 6 | On the blocklist, or score at/above block_score | Branded 403 page |
| 7 | Score at/above challenge_score, or a flood | Press and hold + proof of work, captcha optional |
| 8 | Everyone else | The one-second automatic check, then a signed cookie |
POST requests are never interrupted — a form submission cannot survive an interstitial — but a refused address is refused whatever the method.
Where the list comes from: feed, api, both
feed (the default): a local copy of the blocklist, refreshed every few hours. Nothing leaves your server while a visitor waits, no quota is spent per visit, and the lookup is a binary search over a sorted index: constant memory, microseconds.
api: one /check lookup per address you have not seen lately, cached for cache_ttl. Richer (score, country, network) and always current, but it spends your daily quota and adds a round trip the first time an address appears.
both: the list first, the API only for addresses it misses, up to api_daily_budget lookups a day, then list-only.
The command line and the private panel
The panel URL is derived from your secret — treat it as a password, and change the secret to rotate it. The panel shows the mode, the age and size of the local list, today's counters, the last seven days, and the most recent decisions with the reason for each.
SEO and accessibility
A visitor gate can genuinely cost you search traffic, so the details matter. Search engines are let through only when the reverse DNS name ends in the operator's domain and resolves back to the same address. The check screen answers 503 with Retry-After and carries noindex — a crawler sees a temporary gate rather than indexing a 200 page with your title on it. The screens are keyboard-operable, announce themselves to screen readers, respect prefers-reduced-motion and dark mode, work at phone width, and tell a visitor without JavaScript what is happening in their own language instead of leaving them on a spinner.
Privacy and security notes
- In
feedmode nothing about a visitor ever leaves your server. Inapiandbotha single IP address is sent — no URL, no user agent, no cookie, no page content. The screens say which of the two applies, in the visitor's language. - The only thing a visitor can forge is a header, so only
REMOTE_ADDRis trusted by default. A forwarded header is read when the connection itself came from a proxy you listed intrusted_proxies— otherwise anyone could hand you an address of their choosing and walk past the gate. - Each challenge is signed, expires in ten minutes, is bound to the browser and can be solved once: a captured proof of work is worthless the second time.
- Keep
config.phpandcache/out of reach over HTTP; the kit drops a deny-all.htaccessintocache/itself, andexamples/has rules for Apache and nginx.
Giving data back, and the quota
The kit ships with 'report' => true, and exactly two things are reported: a request for a trap path no browser would ever make, and an address that has failed a dozen challenges within an hour. At most one report per address per six hours; ordinary visitors are never reported, and nothing about your pages is sent. Set it to false in config.php if you would rather report nothing.
A reporting install moves your verified account to the contributor tier: 30,000 lookups a day instead of 1,000, for as long as the install has reported within the last 7 days.
Three things that usually go wrong
- My cron or API endpoint gets an HTML page. Add its path to
skip_paths. Anything called by a machine belongs there. - A page cache serves the check screen to everybody. The screens send
no-store, but some caches ignore it; installed withauto_prepend_file, the gate runs before the cache. - "blocklist: 0 entries". No API key, no outbound HTTPS, or an unwritable cache directory —
--statusnames the one that applies.
Frequently asked questions
Is my traffic routed through anyone? Do I have to change DNS?
No. Shield runs inside the PHP process on your own server; no DNS record changes and no traffic is routed through anybody. In the default 'feed' source the decision comes from a copy of the blocklist on your disk, so nothing leaves your server while a visitor waits.
Will it block Google? Does it hurt SEO?
Search engines are let through, but only when the address really is theirs: the reverse DNS name must end in the operator's domain and must resolve back to the same address. A User-Agent claiming to be Googlebot proves nothing. The check screen also answers 503 with Retry-After and noindex rather than a 200 page — which is exactly what a crawler should see for a temporary gate.
What if I lock myself out?
Put your own address in allow_ips in config.php — and that is why the install leaves the mode on 'monitor' for the first day. If you are already locked out, delete the cache/ directory and set the mode back to monitor. Every failure path also fails open: a missing list, an unreachable API or an unwritable cache directory leaves the site up and writes the reason to the PHP error log.
Visitors see the check screen on every page — why?
The cookie is not coming back. Check the server clock, check that no cache or CDN is stripping Set-Cookie, and check that pass_hours is greater than zero. If you run a full-page cache, exclude visitors who carry no ipdb_pass cookie.
If you run WordPress, the plugin guide is the better fit; to feed the list straight into a firewall see the IP blocking post, and to turn your own server into a sensor, the honeypot guide.
Comments 0
Share a question, your experience or a correction. You can comment as a registered user or anonymously.
No comments yet. Be the first.