• Indonesian
  • English
  • How to Read Apache Logs & Detect Multi-Vector Attacks

    Kecepatan:

    Difficulty: Intermediate
    Last Updated: July 20, 2026
    Tested On: Apache 2.4, cPanel/WHM, CloudLinux 7/8/9, LiteSpeed Enterprise

    Introduction

    One afternoon around 4 PM, I was hanging out in the NOC room having some coffee when the Telegram monitoring went off — but this wasn’t a normal error notification. It was more like an alarm: “Server load spiking drastically, response time up 300%”. I immediately opened a terminal and ran tail -f on one of our clients’ access logs.

    What I saw gave me chills. Not because the errors were severe — but because the pattern was too clean. Hundreds of POST requests came from 9 different IPs within seconds, hitting different endpoints: /wp-login.php, /api/comments, /api/subscribe, /api/news. User agents were all over the place — Chrome, Firefox, Safari, even mobile browsers. Referrers from Google, Bing, Yahoo.

    But it was precisely this cleanliness that was suspicious. A real human wouldn’t be sending requests to 4 different endpoints from different IPs this fast. This wasn’t a human — it was a bot designed to attack your server from multiple angles at once.

    Symptoms — What Was Happening?

    The Apache access log showed suspicious activity from 9 different IPs:

    10.0.0.1 - - [20/Jul/2026:16:43:55 +0700] "POST /api/comments HTTP/1.1" 200 0 "https://www.bing.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.1 - - [20/Jul/2026:16:43:55 +0700] "POST /api/subscribe HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.3 - - [20/Jul/2026:16:43:56 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36"
    10.0.0.4 - - [20/Jul/2026:16:43:56 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36 Edg/10.0.0.2"
    10.0.0.5 - - [20/Jul/2026:16:43:56 +0700] "POST /api/comments HTTP/1.1" 500 384 "https://www.yahoo.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.5 - - [20/Jul/2026:16:43:56 +0700] "POST /api/news HTTP/1.1" 200 0 "https://www.yahoo.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
    10.0.0.4 - - [20/Jul/2026:16:43:57 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "-" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.5 - - [20/Jul/2026:16:43:57 +0700] "POST /api/comments HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36"
    10.0.0.4 - - [20/Jul/2026:16:43:57 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.6 - - [20/Jul/2026:16:43:57 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
    10.0.0.1 - - [20/Jul/2026:16:43:58 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.7 Safari/537.36"
    10.0.0.3 - - [20/Jul/2026:16:43:58 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.bing.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.8 - - [20/Jul/2026:16:43:58 +0700] "POST /api/news HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15"
    10.0.0.4 - - [20/Jul/2026:16:43:58 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.3 - - [20/Jul/2026:16:43:58 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
    10.0.0.8 - - [20/Jul/2026:16:43:58 +0700] "POST /api/comments HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.1 - - [20/Jul/2026:16:43:59 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.yahoo.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.7 Safari/537.36"
    10.0.0.5 - - [20/Jul/2026:16:43:59 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.5 - - [20/Jul/2026:16:43:59 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.bing.com/" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.4 - - [20/Jul/2026:16:43:59 +0700] "POST /api/comments HTTP/1.1" 500 384 "https://www.yahoo.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.7 Safari/537.36"
    10.0.0.9 - - [20/Jul/2026:16:43:59 +0700] "POST /api/news HTTP/1.1" 500 384 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.8 - - [20/Jul/2026:16:43:59 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.yahoo.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.9 - - [20/Jul/2026:16:43:59 +0700] "POST /api/comments HTTP/1.1" 500 384 "https://www.yahoo.com/" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.1 - - [20/Jul/2026:16:43:59 +0700] "POST /api/comments HTTP/1.1" 500 384 "https://www.google.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.4 - - [20/Jul/2026:16:43:59 +0700] "POST /api/news HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.4 - - [20/Jul/2026:16:44:03 +0700] "POST /api/news HTTP/1.1" 503 807 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.4 - - [20/Jul/2026:16:44:11 +0700] "POST /api/comments HTTP/1.1" 503 807 "https://www.yahoo.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.5 - - [20/Jul/2026:16:44:13 +0700] "POST /api/news HTTP/1.1" 503 807 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36"
    10.0.0.5 - - [20/Jul/2026:16:44:14 +0700] "POST /api/comments HTTP/1.1" 503 807 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.4 - - [20/Jul/2026:16:44:06 +0700] "POST /api/comments HTTP/1.1" 503 807 "https://www.google.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.7 Safari/537.36"
    10.0.0.4 - - [20/Jul/2026:16:44:09 +0700] "POST /api/comments HTTP/1.1" 503 807 "-" "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.4 - - [20/Jul/2026:16:44:07 +0700] "POST /api/subscribe HTTP/1.1" 503 807 "https://www.yahoo.com/" "Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36"
    10.0.0.5 - - [20/Jul/2026:16:44:11 +0700] "POST /api/subscribe HTTP/1.1" 503 807 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/121.0"
    10.0.0.6 - - [20/Jul/2026:16:44:01 +0700] "POST /api/news HTTP/1.1" 500 384 "https://www.bing.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.2 Safari/537.36 Edg/10.0.0.2"
    10.0.0.5 - - [20/Jul/2026:16:44:09 +0700] "POST /api/comments HTTP/1.1" 503 807 "https://www.bing.com/" "Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36"
    10.0.0.4 - - [20/Jul/2026:16:44:14 +0700] "POST /api/comments HTTP/1.1" 503 807 "https://www.bing.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/10.0.0.7 Safari/537.36"

    The symptoms:

    • 9 different IPs attacking simultaneously (16:43:55 — 16:44:14, just 19 seconds)
    • 4 different endpoints targeted: /wp-login.php, /api/comments, /api/subscribe, /api/news
    • Status codes evolved: from 200 (success) → 500 (internal error) → 503 (server overload) as the attack progressed
    • Diverse user agents: Chrome, Firefox, Safari, Mobile — showing bots simulating various browsers
    • Fake referrers: Google, Bing, Yahoo — used to cover the attack trail

    Root Cause — What Went Wrong?

    1. Distributed Automated Attack

    This attack used 9 different IPs simultaneously. This wasn’t ordinary brute force from a single IP — it was a distributed attack designed to:

    • Avoid per-IP rate limiting (because each IP only sends a few requests)
    • Overload the server from multiple sides at once
    • Make detection harder (each IP looks “normal” individually)

    2. Multi-Vector Attack

    This attack didn’t target just one endpoint. There were 4 different targets:

    • /wp-login.php — WordPress login brute force
    • /api/comments — trying to inject spam comments or find API vulnerabilities
    • /api/subscribe — trying to exploit email subscription (could be used for email bombing)
    • /api/news — trying to exploit the news API (could be used for injection or data theft)

    This is called a multi-vector attack — an assault targeting several weak points simultaneously to maximize impact.

    3. Server Started Overloading (503 Errors)

    Notice the evolution of status codes in the log:

    • 16:43:55 — 16:43:59: All requests return 200 (success). Server still holding up
    • 16:43:59 — 16:44:01: 500 errors (Internal Server Error) start appearing. Server struggling
    • 16:44:03 — 16:44:14: Mostly 503 (Service Unavailable). Server overloaded

    The 503 error means the server can no longer serve requests — Apache or PHP-FPM has hit its maximum process/connection limit. This is a sign that the attack successfully overwhelmed the server.

    4. Fake User Agents & Referrers

    The bots used diverse user agents and referrers to mimic real human traffic. But there are patterns we can detect:

    • User agents too “clean” — no cookies, sessions, or extra headers that real browsers typically include
    • Inconsistent referrers — the same IP switches from Google to Bing to Yahoo within seconds
    • Timing too uniform — requests arrive at very consistent intervals (1-2 seconds), unlike the random patterns of real humans

    How to Read Apache Logs — Step by Step

    Apache Log Format

    The standard Apache Combined Log Format is:

    %h - - %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"
    Field Example Meaning
    %h 10.0.0.1 Source IP address (client)
    - - Remote log name (usually empty)
    - - Remote user (empty for non-authenticated)
    %t [20/Jul/2026:16:43:55 +0700] Access time (CLF format)
    %r "POST /api/comments HTTP/1.1" Request line: Method + Path + Protocol
    %>s 200 HTTP status code
    %b 0 Response size in bytes
    %{Referer}i "https://www.google.com/" Source page (referrer)
    %{User-Agent}i "Mozilla/5.0 ..." Browser/client user agent

    Reading a Specific Request

    Let’s break down one log line in detail:

    10.0.0.3 - - [20/Jul/2026:16:43:56 +0700] "POST /wp-login.php HTTP/1.1" 200 0 "https://www.google.com/" "Mozilla/5.0 (Linux; Android 13; SM-S908B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.144 Mobile Safari/537.36"
    Part Value Meaning
    Source IP 10.0.0.3 IP making the request
    Timestamp [20/Jul/2026:16:43:56 +0700] July 20, 2026, 4:43:56 PM, GMT+7 timezone
    Method POST HTTP method — sending data to the server
    Path /wp-login.php WordPress login endpoint
    Protocol HTTP/1.1 HTTP version used
    Status 200 Successful response (login form shown or attempt recorded)
    Size 0 Response body 0 bytes (redirect or empty)
    Referrer https://www.google.com/ From Google — this is fake, used to cover tracks
    User Agent Chrome/120... Mobile Safari/537.36 Android browser — also fake

    Counting Attacks from Logs

    To see how bad the attack is, run the following commands:

    # Count requests per IP
    awk '{print $1}' /usr/local/apache/logs/domlogs/client-a.com-ssl_log | 
      sort | uniq -c | sort -rn | head -n 10
    
    # Output:
    #  15423 10.0.0.1
    #  12847 10.0.0.2
    #   9834 10.0.0.3
    # Count requests per endpoint
    awk '{print $7}' /usr/local/apache/logs/domlogs/client-a.com-ssl_log | 
      sort | uniq -c | sort -rn | head -n 10
    
    # Output:
    #  12432 /wp-login.php
    #   8721 /api/comments
    #   6543 /api/subscribe
    #   5432 /api/news
    # Count status code distribution
    awk '{print $9}' /usr/local/apache/logs/domlogs/client-a.com-ssl_log | 
      sort | uniq -c | sort -rn
    
    # Output:
    #  34521 200
    #   8721 500
    #   6543 503

    Step-by-Step Solution — Securing a cPanel + CloudLinux Server

    SECURITY WARNING: Before Changing Server Config

    Modifying Apache config, firewall, or CloudLinux settings can cut off website access on the server. Before proceeding:

    1. Back up existing configcp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.bak
    2. Make sure you have console/SSH access — if the config is wrong, websites go down and you need direct access
    3. Test config before restarting — always run apachectl configtest
    4. Don’t permanently block internal IPs — IPs can change

    Step 1: Identify & Block Brute Force IPs

    First, identify the attacking IPs and block them via firewall:

    # Count IPs with the most requests
    awk '{print $1}' /usr/local/apache/logs/domlogs/client-a.com-ssl_log | 
      sort | uniq -c | sort -rn | head -n 20 > /tmp/attacker-ips.txt
    
    cat /tmp/attacker-ips.txt
    
    # Block IPs with CSF (cPanel Firewall)
    csf -d 10.0.0.1 "Brute force attack - multi-vector"
    csf -d 10.0.0.2 "Brute force attack - multi-vector"
    csf -d 10.0.0.3 "Brute force attack - multi-vector"
    csf -d 10.0.0.4 "Brute force attack - multi-vector"
    csf -d 10.0.0.5 "Brute force attack - multi-vector"
    csf -d 10.0.0.6 "Brute force attack - multi-vector"
    csf -d 10.0.0.8 "Brute force attack - multi-vector"
    csf -d 10.0.0.9 "Brute force attack - multi-vector"
    
    # Restart CSF
    csf -r

    Step 2: Rate Limiting in Apache

    Add rate limiting directly in the Apache config. For cPanel, edit /etc/httpd/conf.d/rate-limit.conf:

    # Rate limiting per IP
    <IfModule mod_ratelimit.c>
        <Location "/wp-login.php">
            SetOutputFilter RATE_LIMIT
            SetEnv rate-limit 5
        </Location>
    
        <Location "/api/">
            SetOutputFilter RATE_LIMIT
            SetEnv rate-limit 10
        </Location>
    </IfModule>

    Or use ModSecurity (more effective for cPanel):

    # Add to /etc/httpd/conf.d/mod_security.conf
    SecRule REQUEST_URI "@beginsWith /wp-login.php" 
      "id:1001,phase:1,deny,status:429,
      setenv:wp-login-rate-limit,
      msg:'WP Login Rate Limit'"

    Step 3: Configure CloudLinux Limits

    In CloudLinux, make sure LVE limits aren’t too high to prevent one account from overloading the server:

    # Check LVE limits for all users
    lveinfo --period=1M
    
    # Set limits for the affected user
    lvectl set-user client-a --maxmem=512M --maxcpu=50 --maxio=50000
    
    # Check current usage
    lveinfo --period=1h --sort=memusage | head -20

    Make sure MySQL/MariaDB isn’t running out of connections:

    # Check active connections
    mysql -e "SHOW STATUS LIKE 'Threads_connected';"
    
    # Check max connections
    mysql -e "SHOW VARIABLES LIKE 'max_connections';"
    
    # If too high, reduce in /etc/my.cnf
    # max_connections = 100
    # wait_timeout = 60
    # interactive_timeout = 60

    Step 4: Install & Configure ModSecurity Rules

    ModSecurity is a Web Application Firewall (WAF) that can detect and block attacks at the HTTP level:

    # Check if ModSecurity is installed
    apachectl -M | grep security
    
    # If not, install via EasyApache (cPanel)
    # WHM → EasyApache → Apache → Include → mod_security
    
    # Enable OWASP ModSecurity Core Rule Set
    # WHM → ModSecurity → Configure → Enable OWASP CRS

    OWASP CRS rules will automatically detect:

    • Brute force on login pages
    • SQL injection via API
    • Cross-site scripting (XSS)
    • Path traversal attempts

    Step 5: Configure CloudLinux PHP Selector

    Make sure PHP execution limits aren’t too high:

    # Check PHP limits for user
    cat /home/client-a/public_html/.user.ini 2>/dev/null
    
    # Set recommended limits in /opt/cpanel/ea-php*/root/etc/php.ini
    max_execution_time = 30
    max_input_time = 60
    memory_limit = 256M
    post_max_size = 8M
    upload_max_filesize = 5M

    Step 6: Monitor & Verify

    After all configurations are applied, monitor the logs in real time:

    # Monitor ongoing attacks
    tail -f /usr/local/apache/logs/domlogs/client-a.com-ssl_log | 
      grep -E '429|503|rate.limit'
    
    # Check blocked IPs
    csf -g 10.0.0.1
    
    # Check requests per minute (should be decreasing)
    watch -n 5 'tail -1000 /usr/local/apache/logs/domlogs/client-a.com-ssl_log | 
      awk "{print $4}" | cut -d: -f1-3 | uniq -c | tail -5'

    Pro Tips & Warnings

    • Use CSF (ConfigServer Security & Firewall) — CSF is easier to use in cPanel than raw iptables. Install via WHM → Plugin
    • Enable ModSecurity — a WAF can detect attacks that don’t show up in regular logs (SQL injection, XSS, etc.)
    • Use CloudLinux CageFS — CageFS isolates each user so an attack from one account can’t affect others
    • Monitor LVE statistics — CloudLinux LVE shows resource usage per user. If a user is consuming excessive resources, it could be a sign of a compromised account
    • Use Imunify360 — a paid solution but very effective at protecting servers from brute force, malware, and other attacks
    • Back up config before editing — always backup /etc/httpd/conf/httpd.conf and other configs

    FAQ

    Q: Why did the status codes change from 200 to 500 to 503?

    The status code evolution shows the server overloading: 200 = server still handling requests, 500 = PHP/MySQL starting to struggle (internal errors), 503 = Apache hit its maximum process limit and is rejecting new requests. If you see lots of 503, immediately block attacker IPs because the server can no longer handle the load.

    Q: How do you distinguish bot attacks from real human traffic?

    Look at the patterns: (1) Bots use diverse but inconsistent user agents, (2) Referrers switch too quickly, (3) Request timing is too uniform (1-2 second intervals), (4) Requests hit many endpoints simultaneously, (5) No cookies or sessions are cached. Real humans are usually consistent in their user agent and referrer.

    Q: Are CSF IP blocks permanent?

    No. CSF auto-unblocks after the timeout (default 3600 seconds = 1 hour). You can check blocked IPs with csf -g [IP] and unblock manually with csf -dr [IP]. For permanent blocks, use csf -d [IP] "reason" — the IP stays blocked even after server restart.

    Q: Can CloudLinux protect against this type of attack?

    CloudLinux helps with LVE (Lightweight Virtual Environment) which limits resources per user. But CloudLinux doesn’t directly block attacks — it ensures one user can’t consume all server resources. To actually block attacks, you still need a firewall (CSF), WAF (ModSecurity), and rate limiting.

    Conclusion

    Apache logs are your first source of information when a server feels slow or errors appear. By understanding the log format — from source IP and timestamp to request method, status code, and user agent — you can spot attack patterns early and take preventive action before the server goes down completely.

    From the logs I analyzed, this attack used a multi-vector approach: targeting 4 different endpoints from 9 IPs simultaneously. The bots tried to evade detection with diverse user agents and referrers. But the uniform timing pattern and status code evolution from 200 → 500 → 503 told us this was a coordinated attack that successfully overwhelmed the server.

    The biggest lesson: don’t just look at a single log line. Look at the pattern. One POST request to /wp-login.php might look normal. But 9 IPs attacking 4 endpoints in 19 seconds with evolving status codes — that’s a pattern that demands immediate action.

    Author: NOC Engineer — written based on experience handling multi-vector attacks on cPanel + CloudLinux production servers.