{"slug": "blunt-banhammer-blocks-basic-bots-ballistically", "title": "Blunt Banhammer Blocks Basic Bots Ballistically", "summary": "Developer Terence Eden built B6, a honeypot tool that cut bot traffic to his hobby site OpenBenches.org by over 80% after the site transferred more than 2.5TB of data in a month, largely to AI crawlers harvesting its images. B6 hides a link to a /honeypot/ directory disallowed in robots.txt on every page; any client that requests it has its IP address written into the site's .htaccess file, which then returns HTTP 402 Payment Required to that address permanently. The approach works on heavily cached sites, unlike the Blackhole script that loosely inspired it, though Eden notes it may cause collateral problems.", "body_md": "I cut bot traffic to my site by over 80% with a simple trap.\n\nLast month, my hobby website [OpenBenches.org](https://openbenches.org) transferred more than 2.5TB of data to visitors. As much as I'd like to think the site is enormously popular, it looks like it is just AI bots endlessly crawling the data and slurping up the images.\n\nSo I've built B<sup>6</sup> to be an indiscriminate tool to eradicate these unwanted parasites.\n\nIt is *very* loosely inspired by [Jeff Starr's Blackhole](https://perishablepress.com/blackhole-bad-bots/), but works even on sites with heavy caching.\n\nHere's the high level description of how it works.\n\n- Create a directory like \n\t\t\t\t\t\t\t`/honeypot/` on your site\n- Put some code in \n\t\t\t\t\t\t\t`index.php` within the directory\n- Add the honeypot as an explicit \"Disallow\" in \n\t\t\t\t\t\t\t`robots.txt`\n- Include a hidden link to to the honeypot on every page of your site\n- If something accesses that page, write its IP address to your \n\t\t\t\t\t\t\t`.htaccess`\n- The \n\t\t\t\t\t\t\t`.htaccess` bans the IP address from ever accessing your site again.\n\nThis blog post shows you how you can build this yourself and discusses some of the problems it might cause.\n\n## \n[Bad Robots!](#bad-robots)\n\nThe file [robots.txt](https://developers.google.com/search/docs/crawling-indexing/robots/intro) lives at the root of your website. It contains a list of instructions to automated web crawlers. Respectful bots will read it and refuse to look at specific parts of your site.\n\nAdd these two lines to it:\n\n```\nUser-agent: *\nDisallow: /honeypot/\n```\n\nThat says bots shouldn't visit any page in the honeypot directory.\n\nBots generally check for updates every 24 hours, so either wait a day or manually submit it to major crawlers.\n\n## \n[.htaccess](#htaccess)\n\nThe [.htaccess file](https://httpd.apache.org/docs/current/howto/htaccess.html) contains a list of instructions for how your server should process requests.\n\nFind the bit of your `.htaccess` file which looks like this:\n\n```\n<IfModule mod_rewrite.c>\n        RewriteEngine On\n        RewriteBase /\n```\n\nEdit it to look like this:\n\n```\n<IfModule mod_rewrite.c>\n        RewriteEngine On\n        RewriteBase /\n\n### BLOCKED IPs\n    #   TEST-NET-1\n    RewriteCond %{REMOTE_ADDR} \"=192.0.2.1\"\n    RewriteRule ^ - [R=402,L]\n### /BLOCKED IPs\n```\n\nIt is important that these rules come before any other rewrite rules.\n\nChange the end of this `IfModule` to look like:\n\n```\n</IfModule>\n\nErrorDocument 402 \"Payment Required - Access Denied.\"\n```\n\nIf an IP address is in the blocked IP list, the server will send an error code and a readable message. You can add whatever plain text you like here.\n\n## \n[Wipe Them All Out](#wipe-them-all-out)\n\nInside the honeypot directory, create a file called `index.php` (or whatever you like). When this page is visited, it grabs the visitor's IP address and the name of its User Agent. It then places them in the BLOCKED IPs section.\n\n``` php\n//  What's their IP?\n$IP = get_client_ip();\n\n//  User Agent\n$UA = isset( $_SERVER[\"HTTP_USER_AGENT\"] ) ? $_SERVER[\"HTTP_USER_AGENT\"] : \"\";\n\n//  Get the existing .htaccess file.\n$htaccess_file = \"../.htaccess\";\n$htaccess = file_get_contents( $htaccess_file );\n\n//  Split by newline.\n$htaccess_lines = explode( separator: \"\\n\", string: $htaccess );\n\n//  Where's our start block?\n$start = \"###   BLOCKED IPs\";\n$end   = \"###   /BLOCKED IPs\";\n\n//  Exit if the .htaccess doesn't have the block.\nif ( !in_array( needle: $start, haystack: $htaccess_lines ) ) { die(); }\n\n//  This is where to start from.\n$start_block = array_search( needle: $start, haystack: $htaccess_lines );\n\n//  Add the line after the start block.\n//  Include a date.\n$htaccess_lines[ $start_block ] = $start . \"\\n\" . \n    \"\\t#\\t\" . date( \"c\" ) . \"\\t{$UA}\\n\" . \n    \"\\tRewriteCond %{REMOTE_ADDR} \\\"={$IP}\\\" [OR]\";\n\n//  Turn the array back to a string.\n$newHtaccess = implode( separator: \"\\n\", array: $htaccess_lines );\n\n//  Save the new .htaccess file.\nfile_put_contents( filename: $htaccess_file, data: $newHtaccess );\n\nfunction get_client_ip():string\n{\n    //  Adapted from https://github.com/ibericode/koko-analytics/blob/main/src/Resources/functions/collect.php\n\n    //  X-Forwarded-For sometimes contains a comma-separated list of IP addresses\n    //  @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For\n    $ips = empty( $_SERVER[\"HTTP_X_FORWARDED_FOR\"] ) ?\n        [] :\n        \\array_map( \"trim\", \\explode( \",\", $_SERVER[\"HTTP_X_FORWARDED_FOR\"] ) );\n\n    //  Always add REMOTE_ADDR and HTTP_CLIENT_IP to list of ip addresses, if set\n    foreach ( [\"REMOTE_ADDR\", \"HTTP_CLIENT_IP\"] as $key ) {\n        if ( !empty( $_SERVER[$key] ) ) {\n            $ips[] = $_SERVER[$key];\n        }\n    }\n\n    //  Return first valid IP address from list\n    foreach ( $ips as $ip ) {\n        if ( \\filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {\n            return $ip;\n        }\n    }\n\n    return \"\";\n}\n```\n\n## \n[Setting The Trap](#setting-the-trap)\n\nWeb crawlers look at a page, find every link on it, then visit those links. So we need to put a link to the honeypot on every page - but in such a way that normal users will not find it or click it.\n\nThis adds a link, removes it from the accessibility tree, and gives it impossible to click text (a zero width joiner).\n\n```\n<a id=\"honey\" aria-hidden=\"true\" href=\"https://shkspr.mobi/honeypot/index.php\">‍</a>\n```\n\nThis CSS hides it from normal viewers and removes the mouse cursor from view.\n\n```\n#honey {\n    display: none !important;\n    cursor:  none !important;\n}\n```\n\n## \n[Problems This Might Cause](#problems-this-might-cause)\n\nIt's unlikely that a human will find the link, click on it, and be banned. But if they do, consider put a message in the blocking text telling them how they can get unblocked.\n\nSome useful bots ignore robots.txt. If they identify themselves via a User-Agent string (and they should) you can manually remove them from .htaccess. You could also code in an allow list exemption for them. But, of course, many bad bots lie about who they really are.\n\nConversely, not all bots crawl every link. This won't catch bots which respect robots.txt but are nevertheless problematic.\n\nIf you have thousands of different bots blocked, the performance of your server *might* slow down while .htaccess is parsed. There's weak anecdotal evidence that it adds a slight overhead. But, personally, I don't think that's worth worrying about.\n\nBots often use hijacked IP addresses. After the bot has moved, an innocent party might be blocked from your site. Each block has a timestamp associated with it. You could periodically unblock older entries.\n\nBots rotate through hundreds of different IP addresses. So the same bot might get blocked multiple times.\n\nBots might see the CSS marks the link as hidden and refuse to follow it. Instead of CSS, you could use invisible spaces or other characters which would be hard for a human to see but appear visible to a bot.\n\nSimilarly, a smart bot might refuse to visit a path called /honeypot/ so choose a better name for it. A smarter bot would see that touching the honeypot leads to a ban - so perhaps a selection of traps are needed?\n\nAs is implied in the name, this is a *blunt* banhammer. It doesn't give a warning, nor does it look at User-Agent, Country, or any other signal. You touch the honeypot and you receive an instaban. No warning and no automated way to appeal.\n\n## \n[Alternatives](#alternatives)\n\nThere are alternative ways to do this. Some bots don't send language headers, some use older versions of HTTP, they often have severely outdated User Agent strings. Those are all reasonable ways to block something which is *probably* a bot. There are likely to be a few false positives though.\n\nBut B<sup>6</sup> has no false positives. A normal user *can't* follow the link to the trap and a well-behaved bot *won't* follow the link.\n\nThings like Anubis require a lot of work to set up and annoy normal users. B<sup>6</sup> doesn't require a specific hosting environment and is never shown to normal users.\n\n## \n[It Works!](#it-works)\n\nSince running B<sup>6</sup> a week ago, I've blocked over 400 IP addresses and seen a significant fall in traffic. I've not had any complaints yet. Sadly, none of the bots have paid to remove the paywall!\n\nAt peak, my site was seeing 216GB of data transfer per day. That's now dropped to around 25GB.\n\nI don't claim this is the most sophisticated bot blocker in existence - but I enjoyed building it myself. I'll probably add something to records the geolocation of the IP and maybe remove any block older than a certain age.\n\nIf you'd like to use it or improve this code, I'd be grateful if you treated it as [AGPL v3 licensed](https://www.gnu.org/licenses/agpl-3.0.en.html).", "url": "https://wpnews.pro/news/blunt-banhammer-blocks-basic-bots-ballistically", "canonical_source": "https://shkspr.mobi/blog/2026/11/blunt-banhammer-blocks-basic-bots-ballistically/", "published_at": "2026-11-09 12:34:00+00:00", "updated_at": "2026-11-09 12:50:47.851116+00:00", "lang": "en", "topics": ["ai-crawlers", "ai-search"], "entities": ["B6", "OpenBenches.org", "Terence Eden", "Jeff Starr", "Blackhole"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/blunt-banhammer-blocks-basic-bots-ballistically", "markdown": "https://wpnews.pro/news/blunt-banhammer-blocks-basic-bots-ballistically.md", "text": "https://wpnews.pro/news/blunt-banhammer-blocks-basic-bots-ballistically.txt", "jsonld": "https://wpnews.pro/news/blunt-banhammer-blocks-basic-bots-ballistically.jsonld"}}