A lightweight Proof-of-Work (PoW) gateway for PHP sites that reduces abusive traffic without CAPTCHAs.
It issues a signed cookie (abp) after a browser completes a SHA-256 work check, then allows normal access.
This repository includes:
- β
PoW challenge page:
__ab/pow.php - β
PoW verifier + signed cookie:
__ab/pow-verify.php - β
ModSecurity rate limits for PoW endpoints:
modsecurity/ab_pow_ratelimit.conf - β
Apache vhost examples (sanitized to
example.com) with PoW "skip" rules + clean URL option - β Cloudflare compatibility notes (cache bypass + real client IP restore)
- β Secret rotation script + systemd service/timer examples
This repository intentionally excludes:
- β TLS certificates / private keys
- β secrets (your
AB_POW_SECRET) - β server logs / user data
- A client requests a protected URL and does not have cookie
abp - Apache rewrites/redirects them to:
/__ab/pow.php?next=/original/path&qs=original=query pow.phpruns PoW in the browser:- compute
sha256(TOKEN + "." + counter)until it has enough leading zero bits
- compute
- Browser submits the solution to:
/__ab/pow-verify.php - Server verifies:
- token integrity (HMAC)
- user-agent binding (light)
- PoW difficulty (leading zero bits)
- Server sets cookie:
abp=<signed value>(Secure, HttpOnly, SameSite=Lax)
- Browser is redirected back to the original URL
Goal: make abusive traffic expensive while normal visitors pass quickly.
A live deployment of pow-shield-php is running in production here:
This site uses:
- Proof-of-Work (PoW) gateway for unauthenticated traffic
- ModSecurity rate limiting on PoW endpoints
- Apache connection-level protections (Slowloris / low-and-slow mitigation)
- Cloudflare as CDN + TLS terminator (no bot challenges, no CAPTCHA)
β οΈ Note: Configuration values, secrets, and thresholds used on the live site are intentionally not published in this repository.
The installer handles all dependencies automatically. Simply run:
sudo ./install.shIf installing manually, you need:
- PHP 8+
- HTTPS (required for Secure cookie + WebCrypto)
- Apache 2.4+
- ModSecurity (Apache connector + CRS optional) for rate-limiting
/__ab/* - If behind Cloudflare: Apache
mod_remoteipconfigured to restore the real client IP
AB_POW_SECRETmust be set in the environment- Minimum: 48 characters
- Recommended: 64+ characters
pow-shield-php/
ββ __ab/
β ββ pow.php
β ββ pow-verify.php
ββ modsecurity/
β ββ ab_pow_ratelimit.conf
ββ apache/
β ββ sites-available/
β ββ example.com-redirect.conf.example
β ββ example.com.conf.example
ββ scripts/
β ββ rotate-pow-secret.sh.example
ββ systemd/
β ββ rotate-pow-secret.service.example
β ββ rotate-pow-secret.timer.example
ββ assets/img/
β ββ README.md
β ββ .gitkeep
ββ docs/
β ββ cloudflare-notes.md
β ββ installation-checklist.md
β ββ modsecurity-global-notes.md
ββ install.sh # π Automated installer
ββ uninstall.sh # π Automated uninstaller
ββ README.md
We provide automated installation scripts for easy setup:
# Clone the repository
git clone https://github.com/AfterPacket/pow-shield-php.git
cd pow-shield-php
# Make scripts executable
chmod +x install.sh uninstall.sh
# Run interactive installer
sudo ./install.shπ Full Installation Guide: See INSTALL.md for detailed instructions, troubleshooting, and advanced configuration options.
The installer will:
- β Install all required dependencies (Apache, PHP, OpenSSL)
- β Generate secure PoW secret automatically
- β Deploy PoW endpoints and assets
- β Configure Apache virtual hosts
- β Set up ModSecurity rate limiting (optional)
- β Configure Let's Encrypt SSL (optional)
- β Set up automatic secret rotation
Interactive Mode (Default)
sudo ./install.shFollow the prompts to configure your installation.
Non-Interactive with Let's Encrypt
sudo ./install.sh -d example.com -w /var/www/html -l admin@example.com -eNon-Interactive with Existing SSL
sudo ./install.sh -d example.com -w /var/www/html \
-c /etc/ssl/certs/cert.pem -k /etc/ssl/private/key.pem -eSkip ModSecurity
sudo ./install.sh -d example.com -w /var/www/html -s| Flag | Description |
|---|---|
-d, --domain |
Domain name (e.g., example.com) |
-w, --webroot |
Web root directory path |
-c, --cert |
SSL certificate path (optional) |
-k, --key |
SSL key path (optional) |
-l, --letsencrypt |
Use Let's Encrypt with email |
-e, --enable |
Enable site with a2ensite after install |
-s, --skip-modsec |
Skip ModSecurity installation |
-n, --non-interactive |
Run without prompts |
-h, --help |
Show help message |
To completely remove pow-shield-php:
# Interactive uninstaller
sudo ./uninstall.sh
# Force removal without prompts
sudo ./uninstall.sh -d example.com -w /var/www/html -f
# Keep the PoW secret file
sudo ./uninstall.sh -d example.com -w /var/www/html -k
# Also remove ModSecurity rules
sudo ./uninstall.sh -d example.com -w /var/www/html -mThe uninstaller will:
- β Backup all files before removal
- β Disable and remove virtual hosts
- β Remove PoW endpoints
- β Remove systemd rotation (optional)
- β Remove ModSecurity rules (optional)
- β Test Apache config before reload
If you prefer manual installation:
Copy the following files into your site webroot:
__ab/pow.php__ab/pow-verify.php
They must resolve at:
https://example.com/__ab/pow.phphttps://example.com/__ab/pow-verify.php
β Tip: keep
/__ab/excluded from caching and from other WAF rules that might block POST.
Your pow.php references:
/assets/img/clank.jpg
To keep this path:
- place the image at
assets/img/clank.jpgin your webroot
Or update $MEME_SRC inside __ab/pow.php.
Instead of embedding secrets in vhost configs, load them from a root-owned include file:
/etc/apache2/pow.env(root-owned, mode600)- included in your HTTPS vhost via:
IncludeOptional /etc/apache2/pow.env
sudo install -d -m 0755 /etc/apache2
sudo bash -c 'umask 077; SECRET="$(openssl rand -base64 64 | tr -d "\n")"; \
printf "%s\n" "# Managed by pow-shield-php" "SetEnv AB_POW_SECRET \"$SECRET\"" > /etc/apache2/pow.env'
sudo chown root:root /etc/apache2/pow.env
sudo chmod 600 /etc/apache2/pow.env
sudo apachectl -t
sudo systemctl reload apache2
β οΈ Never commit secrets to git.
Rotating the PoW secret reduces replay value if a cookie/token leaks. To avoid breaking in-flight challenges, rotate with overlap:
- New secret stored as
AB_POW_SECRET - Old secret preserved as
AB_POW_SECRET_PREV
β
For this to work, your pow-verify.php should accept either secret when validating.
Save as:
/usr/local/sbin/rotate-pow-secret.sh
#!/bin/bash
set -euo pipefail
OUT="/etc/apache2/pow.env"
TMP="$(mktemp)"
umask 077
# Pull current secret (if any) from existing file
CURRENT=""
if [[ -f "$OUT" ]]; then
CURRENT="$(awk -F'"' '/SetEnv[[:space:]]+AB_POW_SECRET[[:space:]]+"/ {print $2; exit}' "$OUT" || true)"
fi
NEW="$(openssl rand -base64 64 | tr -d '\n')"
{
echo '# Managed by rotate-pow-secret.sh'
echo "SetEnv AB_POW_SECRET \"$NEW\""
if [[ -n "${CURRENT}" ]]; then
echo "SetEnv AB_POW_SECRET_PREV \"$CURRENT\""
fi
} > "$TMP"
chown root:root "$TMP"
chmod 600 "$TMP"
mv -f "$TMP" "$OUT"
# Safety: verify Apache config first
apachectl -t
# Reload, not restart (keeps connections)
systemctl reload apache2Install + test:
sudo install -m 0755 /usr/local/sbin/rotate-pow-secret.sh /usr/local/sbin/rotate-pow-secret.sh
sudo /usr/local/sbin/rotate-pow-secret.shCreate:
/etc/systemd/system/rotate-pow-secret.service
[Unit]
Description=Rotate AB_POW_SECRET for pow-shield-php and reload Apache
Wants=apache2.service
After=apache2.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rotate-pow-secret.sh
User=root
Group=root
# Hardening (safe defaults)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/etc/apache2/pow.envCreate:
/etc/systemd/system/rotate-pow-secret.timer
[Unit]
Description=Hourly rotation for AB_POW_SECRET (pow-shield-php)
[Timer]
OnCalendar=hourly
Persistent=true
RandomizedDelaySec=120
Unit=rotate-pow-secret.service
[Install]
WantedBy=timers.targetEnable:
sudo systemctl daemon-reload
sudo systemctl enable --now rotate-pow-secret.timer
sudo systemctl list-timers --all | grep rotate-pow-secretManual trigger:
sudo systemctl start rotate-pow-secret.service
sudo systemctl status rotate-pow-secret.service --no-pagerUse the sanitized examples in apache/sites-available/.
Two common patterns:
Option A β Redirect to /__ab/pow.php (visible PoW URL)
- simplest
- user sees
/__ab/pow.php?...
Option B β Internal rewrite (clean URL)
- keeps the original URL in the address bar
- uses
[PT]internally to servepow.php
In both options, always skip:
/__ab/*(prevents loops)/status/*(your private panels/JSON)- static assets
- non-GET/HEAD methods
Rules are provided in:
modsecurity/ab_pow_ratelimit.conf
sudo apt update
sudo apt install -y libapache2-mod-security2
sudo a2enmod security2
sudo systemctl reload apache2Confirm:
apachectl -M | grep -i securityIn /etc/modsecurity/modsecurity.conf:
SecRuleEngine On
SecRequestBodyAccess OnReload:
sudo systemctl reload apache2Copy:
sudo mkdir -p /etc/modsecurity
sudo cp modsecurity/ab_pow_ratelimit.conf /etc/modsecurity/ab_pow_ratelimit.confThen include it in your vhost or global security2 config:
IncludeOptional /etc/modsecurity/ab_pow_ratelimit.conf
Header always set Retry-After "30" env=AB_RLfor i in $(seq 1 80); do
curl -sk https://example.com/__ab/pow.php?next=/ >/dev/null -w "%{http_code}\n"
doneYou should see 429 once the limit triggers.
PoW is application-layer cost. It helps with:
- Basic bot spam
- Naive request floods
- Large-scale scraping (makes it expensive per request)
It does not stop all L7 attacks by itself. Pair it with:
- ModSecurity rate limiting (especially on
/__ab/pow-verify.php) mod_reqtimeout(Slowloris mitigation)- Connection limits / MPM tuning
- Correct real-IP restoration when behind Cloudflare
π Note: Pattern matters more than specific values; deploy thresholds appropriate to your traffic.
See docs/cloudflare-notes.md.
Important settings:
- β Bot Fight Mode / "Stop Bot Attack": OFF (can interfere with PoW)
- π« Cache bypass for:
/__ab/pow.php/__ab/pow-verify.php
- π Restore real client IP at the origin using
mod_remoteip
Common causes:
- Cloudflare caching PoW endpoints
- Cloudflare bot challenges enabled
- Cookies blocked by browser
- WAF blocking
/__ab/pow-verify.php - Using PoW as an
ErrorDocument 403(can recurse)
Fix:
- Disable Bot Fight / Stop Bot Attack
- Bypass cache for PoW endpoints
- Confirm
Set-Cookie: abp=...is issued over HTTPS - Don't use PoW as 403 handler; use a static error page instead
- Lower difficulty for hardened UAs (or remove the "hard fail")
- Extend TTL for challenge tokens
- Ensure cookies aren't blocked for the site
- ModSecurity limits are working as intended
- Wait for the window to expire (often 60 seconds)
AB_POW_SECRETmust be long and random (>= 48 chars; 64+ recommended)- Never commit secrets to git
- Consider rotating the secret with overlap (
AB_POW_SECRET_PREV) to reduce replay value - Keep PoW endpoints uncached and allow POST to
/__ab/pow-verify.php - If behind Cloudflare, configure real IP restoration before using per-IP rate limits
Contributions are welcome! To participate:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-enhancement - Commit your changes:
git commit -m "Add: your feature" - Push to your fork:
git push origin feature/your-enhancement - Open a Pull Request
Found a bug or have a feature request? Please open an issue with:
- Steps to reproduce
- Expected vs actual behavior
- PHP and Apache versions
- Operating system
This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for full details.
Enjoy Fighting BOTS π€π‘οΈ