Blog

  • Lab Test: Bot Traffic & Mitigation Playbook Development (WordPress + Cloudflare)

    Experiment and develop a practical guide for diagnosing and stopping abusive bot traffic that pegs PHP-FPM on WordPress. Example domains used: example.com (primary).

    Date: 04 Sep 2025

    Executive summary

    Our experiment began on , example.com we simulated abusive bot traffic targeting /w/ and /w/index.php (paths used by MediaWiki). Because the site runs WordPress, these requests fell through to PHP, driving PHP-FPM CPU to 100% and slowing responses.

    What we did:

    • Containment: Cloudflare WAF rule that blocks /w, /w/, and anything under /w/….
    • Hardening:
      • Block /xmlrpc.php (safe if Jetpack/remote publishing is not used).
      • Protect /wp-login.php with a Managed Challenge (or IP allow-list).
    • (Optional) Performance: Cloudflare Cache Rule to serve HTML from the edge for anonymous users.

    Result: /w/* no longer reaches origin; PHP-FPM load normalized; key pages serve with CF-Cache-Status: HIT.

    Impact

    • Symptoms: High CPU, pegged PHP-FPM workers for the example.com pool.
    • User experience: Intermittent slowdowns/timeouts during spikes.
    • Blast radius: Mainly example.com. example-two.com also showed noisy login/XML-RPC hits.

    Indicators & evidence

    Hot PHP-FPM pool

    ps -eo pcpu,pmem,args --no-headers \
    | awk '/php-fpm: pool/ {pool=$NF; cpu[pool]+=$1; mem[pool]+=$2; n[pool]++} \
           END {for (p in cpu) printf "%6.1f%% CPU  %6.1f%% MEM  %2d procs  %s\n", cpu[p], mem[p], n[p], p}' \
    | sort -nr

    Access-log hotspots (top endpoints)

    tail -n 20000 /var/www/vhosts/system/example.com/logs/*access* 2>/dev/null \
    | awk '{print $7}' | cut -d? -f1 | sort | uniq -c | sort -nr | head

    Spike fingerprint (time window)

    FROM="04/Sep/2025:16:55"; TO="04/Sep/2025:17:01"
    awk -v f="$FROM" -v t="$TO" '$4~/\[/ {ts=substr($4,2,20); if(ts>=f&&ts<=t) print $7}' \
      /var/www/vhosts/system/example.com/logs/access_log \
      /var/www/vhosts/system/example.com/logs/access_ssl_log \
    | cut -d? -f1 | sort | uniq -c | sort -nr | head

    Finding: Clustered /w/ and /w/index.php requests via Cloudflare edge IPs (bot traffic through the proxy).

    Root cause

    Automated crawl/attack requested MediaWiki edit paths on a WordPress site. Each request invoked PHP (Apache → PHP-FPM), saturating CPU.

    Remediation steps

    1) Containment — Cloudflare WAF block for /w/*

    Cloudflare → Security → WAF → Custom rules → Create rule

    Expression:

    (http.request.uri.path in {"/w", "/w/"} or starts_with(http.request.uri.path, "/w/"))
    • Action: Block
    • Order: Place near the top
    • Why this shape: Matches /w and /w/ exactly, plus /w/index.php, but does not match /wp-login.php or /winner.

    2) Hardening — common WordPress abuse paths

    • Block XML-RPC (safe if Jetpack/remote publishing is not used)
      (http.request.uri.path eq "/xmlrpc.php")
      Action: Block
    • Protect login
      • Low-friction: (http.request.uri.path eq "/wp-login.php") → Managed Challenge
      • Stricter allow-list: (http.request.uri.path eq "/wp-login.php") and not ip.src in {YOUR.PUBLIC.IP} → Block/Challenge

    3) (Optional) Performance — Cache HTML for anonymous users

    Cloudflare → Caching → Cache Rules → Create rule

    Match (AND all):

    • URI Path does not start with /wp-admin
    • URI Path does not equal /wp-login.php
    • URI Path does not equal /xmlrpc.php
    • URI Query String does not contain preview=true
    • Request Header cookie does not contain wordpress_logged_in_
    • Request Header cookie does not contain comment_author_

    Then: Cache eligibility: Eligible for cache; Edge TTL: (use plan minimum, e.g., 2 hours); enable “Serve stale while revalidating”.

    Raw expression (equivalent):

    not starts_with(http.request.uri.path, "/wp-admin")
    and http.request.uri.path ne "/wp-login.php"
    and http.request.uri.path ne "/xmlrpc.php"
    and not (http.request.uri.query contains "preview=true")
    and not any(http.request.headers["cookie"][*] contains "wordpress_logged_in_")
    and not any(http.request.headers["cookie"][*] contains "comment_author_")

    Effect: Anonymous page views come from Cloudflare’s edge (CF-Cache-Status: HIT), dramatically reducing origin PHP load.

    Validation & results

    • /w/* eliminated:
      tail -n 500 /var/www/vhosts/system/example.com/logs/access_* 2>/dev/null \
      | awk '{print $7}' | cut -d? -f1 | grep -E '^/w(/|$)' | wc -l  
    • Lower PHP-FPM CPU:
      ps -eo pcpu,pmem,args --no-headers \
      | awk '/php-fpm: pool/ {p=$NF; c[p]+=$1} END{for(p in c) printf "%5.1f%% CPU  %s\n", c[p], p}' \
      | sort -nr
    • Edge cache hits:
      curl -sI https://example.com/ | egrep -i 'CF-Cache-Status|Age'  

    Ongoing monitoring

    • Top endpoints (recent):
      tail -n 600 /var/www/vhosts/system/example.com/logs/access_* 2>/dev/null \
      | awk '{print $7}' | cut -d? -f1 | sort | uniq -c | sort -nr | head
    • Which site is hot:
      ps -eo pcpu,pmem,args --no-headers \
      | awk '/php-fpm: pool/ {pool=$NF; cpu[pool]+=$1} END {for (p in cpu) printf "%6.1f%% CPU  %s\n", cpu[p], p}' \
      | sort -nr
    • Cloudflare Security → Events: filter by rule names (e.g., “Block /w and /w/*”, “Block xmlrpc”, “Protect wp-login”) to confirm mitigations are firing.

    Recommendations

    1. Keep the /w/* block permanently (zero cost on WordPress).
    2. Leave /xmlrpc.php blocked unless a product explicitly requires it.
    3. Protect /wp-login.php with Managed Challenge or IP allow-list.
    4. (Optional) Keep the HTML cache rule; pair with Cloudflare’s WP plugin for automatic purges on content updates.
    5. Use the quick-response runbook below for future spikes.

    Appendix: Quick-response runbook

    Identify the hot site/pool

    ps -eo pcpu,pmem,args --no-headers \
    | awk '/php-fpm: pool/ {p=$NF; c[p]+=$1} END{for(p in c) printf "%5.1f%% CPU  %s\n", c[p], p}' \
    | sort -nr

    Identify hot URLs

    tail -n 20000 /var/www/vhosts/system/<DOMAIN>/logs/*access* 2>/dev/null \
    | awk '{print $7}' | cut -d? -f1 | sort | uniq -c | sort -nr | head

    Slice by time window

    FROM="dd/Mon/yyyy:HH:MM"; TO="dd/Mon/yyyy:HH:MM"
    awk -v f="$FROM" -v t="$TO" '$4~/\[/ {ts=substr($4,2,20); if(ts>=f&&ts<=t) print $7}' \
      /var/www/vhosts/system/<DOMAIN>/logs/access_log \
      /var/www/vhosts/system/<DOMAIN>/logs/access_ssl_log \
    | cut -d? -f1 | sort | uniq -c | sort -nr | head

    Cloudflare WAF expressions (copyable)

    • Block MediaWiki paths on WordPress:
      (http.request.uri.path in {"/w", "/w/"} or starts_with(http.request.uri.path, "/w/"))
    • Block XML-RPC:
      (http.request.uri.path eq "/xmlrpc.php")
    • Protect Login (allow-list or challenge):
      (http.request.uri.path eq "/wp-login.php") and not ip.src in {YOUR.PUBLIC.IP}
    • Cache HTML for anonymous users (Cache Rule; AND all):
      not starts_with(http.request.uri.path, "/wp-admin")
      and http.request.uri.path ne "/wp-login.php"
      and http.request.uri.path ne "/xmlrpc.php"
      and not (http.request.uri.query contains "preview=true")
      and not any(http.request.headers["cookie"][*] contains "wordpress_logged_in_")
      and not any(http.request.headers["cookie"][*] contains "comment_author_")

    References & further reading

  • National Debt Tracker: American taxpayers (you) are now on the hook for …

    U.S. Total Public Debt Outstanding
    $37,470,833,295,098.68
    As of
    Official source: Treasury FiscalData
    Debt Held by the Public
    $30,156,524,547,639.58
    Marketable & non-marketable debt held outside the U.S. government.


    Intragovernmental Holdings
    $7,314,308,747,459.10
    Holdings within U.S. government accounts (e.g., trust funds).


    Data Source & API
    Updates at end of each business day for the previous business day.
    https://api.fiscaldata.treasury.gov/services/api/fiscal_service/v2/accounting/od/debt_to_penny?fields=record_date,tot_pub_debt_out_amt,debt_held_public_amt,intragov_hold_amt&sort=-record_date&page[size]=1&format=json
    Attribution: U.S. Department of the Treasury, Bureau of the Fiscal Service — FiscalData “Debt to the Penny”. No endorsement is implied.
    Data and API are provided “as is” and “as available” by the Bureau of the Fiscal Service. No warranties are made and availability may change at any time.
    Wordpress Plugin development provided by https://progresstechnologies.com/. No warranties are made and availability may change at any time.
  • How to Log the Real Visitor IP Address When Using Cloudflare

    If you accept connections from websites or API running behind Cloudflare, there’s a hidden “gotcha” you must know about:

    By default, your end-point server will NOT see the real end-user’s IP address.

    Instead, every incoming request appears to come from a Cloudflare data center.
    This impacts:

    • Abuse prevention (rate limiting)
    • Accurate analytics
    • Geo-targeting
    • Any IP-based feature or logging

    Why?

    Cloudflare acts as a reverse proxy, routing all traffic through their global edge network.
    Your server’s REMOTE_ADDR variable now reflects the Cloudflare node—not your actual visitor.


    The Solution: Use the Special HTTP Header

    Cloudflare automatically sends the true client IP in the CF-Connecting-IP HTTP header.

    To reliably log and process the real user’s IP in PHP, use this snippet:

    function getRealIp() {
    // Prefer Cloudflare's header if present
    if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
    return $_SERVER['HTTP_CF_CONNECTING_IP'];
    }
    // Optionally support generic proxies
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
    }
    // Fallback to server-provided address
    return $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN';
    }

    Best Practices:

    • Use this getRealIp() helper everywhere you log, rate-limit, or analyze IP addresses.
    • If you use other CDNs or reverse proxies, check their docs for equivalent headers.
    • Do not trust X-Forwarded-For unless you control all proxy hops—it can be spoofed by end users.

    Why This Matters:

    • Without this fix: All requests appear to originate from Cloudflare, making IP-based abuse detection and analytics impossible.
    • With this fix: You’ll always have the real end-user IP, whether you’re behind Cloudflare or not.

    Resources:


    TL;DR:
    When accounting for Cloudflare, always log and use the IP from CF-Connecting-IP, not just REMOTE_ADDR.
    It’s a simple fix that saves hours of debugging and improves your site’s security and analytics.

  • AAAA record error in Plesk when adding a domain.

    Download the Plesk Cloudflare plugin and import the DNS records to plesk for the domain. Make sure Cloudflare is in Strict SSL Mode when doing this or it won’t work. After this is complete you will get a certificate error. If you wish to add a free certificate then you must put the cert into full mode as opposed to strict to add the free let’s encrypt certificate.