Guide

Installing IP-DB Shield: A Visitor Gate That Runs on Your Own Server

A three-minute PHP install, monitor mode, the decision ladder, the feed and api sources, the private panel, and the SEO and privacy details.

IP-DB Team 8 min read 13 reads 0 comments

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.html and theme/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.

# 1) upload the folder, e.g. /home/you/public_html/ipdb-shield/ # 2) generate a secret and put it in config.php php ipdb-shield.php --secret # 3) download the list once and check the install php ipdb-shield.php --refresh php ipdb-shield.php --status

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:

require __DIR__ . '/ipdb-shield/ipdb-shield.php';

…or without touching any code, in .htaccess:

php_value auto_prepend_file "/home/you/public_html/ipdb-shield/ipdb-shield.php"

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:

#CaseOutcome
1Not a page (assets, XHR, webhooks, wp-cron)Untouched
2Your own allow_ipsThrough
3Your own deny_ipsRefused
4A trap path (/.env, /.git/…)Refused, and reported if you opted in
5A verified search crawler (forward-confirmed rDNS)Through
6On the blocklist, or score at/above block_scoreBranded 403 page
7Score at/above challenge_score, or a floodPress and hold + proof of work, captcha optional
8Everyone elseThe 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

php ipdb-shield.php --refresh # download/refresh the local list php ipdb-shield.php --status # health, counters, panel URL php ipdb-shield.php --test 1.2.3.4 # what would happen to this address? php ipdb-shield.php --verify # read the whole local list back php ipdb-shield.php --secret # a fresh secret

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 feed mode nothing about a visitor ever leaves your server. In api and both a 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_ADDR is trusted by default. A forwarded header is read when the connection itself came from a proxy you listed in trusted_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.php and cache/ out of reach over HTTP; the kit drops a deny-all .htaccess into cache/ itself, and examples/ 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 with auto_prepend_file, the gate runs before the cache.
  • "blocklist: 0 entries". No API key, no outbound HTTPS, or an unwritable cache directory — --status names 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.

Share

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.

Plain text; links are not accepted. Comments appear at once and may be moderated. Sign in to comment as a member