Every day, automated bots are scanning your website. Not just yours — everyone's. They probe for exposed .env files, old WordPress admin panels, SQL injection points, and known CVEs. Some of them send thousands of requests per minute, not because they're targeting you specifically, but because scanning the entire internet is cheap and easy.
This article is about what's actually happening out there, why it matters even for small projects, and how a lightweight PHP library can help you deal with it.
What Are These Scanners, Exactly?
Web scanners are automated programs that crawl the internet looking for security vulnerabilities. Some are legitimate security tools — like Shodan or Censys — that map the internet for research purposes. But many others are operated by attackers who are looking for:
-
Exposed configuration files —
.env,web.config,.git/config -
Unprotected admin panels —
/wp-admin,/phpmyadmin,/adminer -
Known vulnerable endpoints — like
xmlrpc.phpin WordPress orsetup.phpleft behind after an install - SQL injection and XSS vectors — via query strings, POST bodies, and cookies
-
Path traversal attacks — trying
../../etc/passwdvariations
The tools they use are well-known: sqlmap, nikto, nmap, dirbuster, gobuster, masscan, and many more. These aren't obscure hacking tools — they're freely available, widely documented, and actively maintained.
Even if your app has no actual vulnerabilities, these scanners generate noise, consume server resources, pollute your logs, and can slow down real users during a burst of requests.
Why Server-Level Solutions Aren't Always Enough
You might wonder: "Can't Nginx or Apache handle this?"
Sort of. You can write firewall rules, block known bad IPs, or configure rate limiting in your server config. But these approaches have limitations:
- They require system-level access (not always available on shared hosting)
- They need manual maintenance as attack patterns evolve
- They can't easily inspect request payloads for things like SQL injection signatures
- They don't integrate naturally with your application logic
A PHP-level firewall runs inside your application, which means it can inspect everything: headers, query strings, POST bodies, cookies, request paths, and user agents — and react accordingly.
Introducing xZeroProtect
xZeroProtect is a lightweight, file-based PHP 8 firewall with zero external dependencies. No Redis, no MySQL, no external services — everything is stored on disk.
Install it via Composer:
composer require webrium/xzeroprotect
The simplest possible integration — two lines at the top of your index.php:
<?php
require 'vendor/autoload.php';
use Webrium\XZeroProtect\XZeroProtect;
XZeroProtect::init()->run();
That's it. Default rules activate immediately.
What It Actually Does
1. Blocks Known Scanner User Agents
The library maintains a list of User-Agent signatures associated with scanning tools and attack frameworks: sqlmap, nikto, nessus, acunetix, masscan, dirbuster, gobuster, feroxbuster, wfuzz, ffuf, hydra, metasploit, and many others. Requests from these tools are blocked before they get anywhere near your application logic.
You can extend or trim this list:
$firewall->patterns->addAgent('custom-bad-bot');
$firewall->patterns->removeAgent('curl'); // if your API clients use curl legitimately
2. Blocks Sensitive Path Probes
Scanners routinely probe paths that don't exist in modern PHP apps — and shouldn't. xZeroProtect blocks requests targeting:
- CMS admin panels:
/wp-admin,/wp-login,/administrator - Config files:
.env,.git,.htpasswd,web.config - Database tools:
/phpmyadmin,/adminer,/dbadmin - Dangerous files:
.sql,.bak,.backup,dump.sql - Web shells:
shell.php,c99.php,r57.php - Path traversal:
../,..%2f,%2e%2e
If you're running a fully-routed PHP app with no .php files in URLs, you can add that as a rule too:
$firewall->patterns->addPath('.php');
3. Scans Payloads for Attack Signatures
The payload scanner inspects GET parameters, POST body, raw input, and cookies using compiled regular expressions. It detects:
- SQL injection variants:
UNION SELECT,DROP TABLE,SLEEP(),BENCHMARK() - XSS:
<scripttags, inline event handlers,javascript:protocol - Path traversal:
../ - PHP code injection:
system(),exec(),eval(base64_decode(...)) - LFI:
/etc/passwd,/etc/shadow - Remote file inclusion
- Command injection via shell metacharacters
4. Rate Limiting Without Redis
The rate limiter uses a sliding-window counter stored per-IP on disk. No Redis or Memcached required.
$firewall = XZeroProtect::init([
'rate_limit' => [
'max_requests' => 60,
'per_seconds' => 60,
],
]);
When a client exceeds the limit, they accumulate violations. After enough violations, they get temporarily banned. After enough bans, permanently.
5. Doesn't Block Legitimate Crawlers
This is important: Google, Bing, and other legitimate search engines should not get caught in your firewall. xZeroProtect handles this with double-DNS verification for major crawlers:
- Resolve the visitor's IP to a hostname via reverse DNS
- Confirm the hostname ends with the expected suffix (e.g.
.googlebot.com) - Re-resolve that hostname back to an IP
- Confirm the re-resolved IP matches the original
Anyone can fake a User-Agent string. DNS cannot be faked. This is the same verification method recommended by Google and Bing in their official documentation.
Learning Mode: Test Before You Block
Before switching to production, you can run in learning mode. All threats are detected and logged, but nothing is blocked. This lets you review what would have been caught and tune your configuration accordingly.
// During tuning
XZeroProtect::init(['mode' => 'learning'])->run();
// Once satisfied
XZeroProtect::init(['mode' => 'production'])->run();
This is genuinely useful. Running learning mode for a few days on a real server will show you the volume and variety of automated probes hitting your application — and help you avoid accidentally blocking legitimate traffic.
Apache Integration for Zero-PHP-Overhead Blocking
For permanently banned IPs, you can optionally write them into .htaccess so Apache rejects the connection before PHP even starts:
$firewall = XZeroProtect::init([
'apache_blocking' => true,
'htaccess_path' => __DIR__ . '/.htaccess',
]);
This is particularly useful for confirmed attackers who you never want to reach your application layer again.
Custom Rules
If the built-in modules don't cover a specific case, you can register your own:
use Webrium\XZeroProtect\RuleResult;
$firewall->rules->add('no-php-extension', function ($request) {
if (str_ends_with($request->path(), '.php')) {
return RuleResult::block('PHP extension not valid on this server');
}
return RuleResult::pass();
});
Rules can block, log without blocking, or pass. They run in priority order and can be enabled, disabled, or removed at runtime.
What xZeroProtect Is Not
To be clear about expectations: this is not a commercial WAF. It won't stop a sophisticated, targeted attack from a determined adversary. It doesn't do deep packet inspection, it doesn't have a threat intelligence feed, and it doesn't update its rule set automatically.
What it does is significantly reduce the automated noise that hits most PHP applications — the opportunistic scanners, the script-kiddie tools, the mass-probing bots — with a setup that takes minutes and has no infrastructure dependencies.
For many projects, especially those on shared hosting or small VPS instances where you don't have the option to configure Nginx rate limiting or cloud WAF rules, this is a practical and useful layer of defense.
Getting Started
composer require webrium/xzeroprotect
Full documentation and source: github.com/webrium/xzeroprotect
Start in learning mode, review your logs, tune your configuration, then flip to production.
Have feedback or found an edge case? Issues and PRs are welcome on GitHub.

Top comments (0)