Regular review of Apache logs exposes reconnaissance, brute-force probing, and exploit attempts before they become outages or data loss. The access log captures request paths, status codes, and traffic patterns that often reveal malicious intent long before an alerting system notices.

In most deployments, the Apache access log uses a combined format that includes the client address, timestamp, request line, HTTP status, response size, referrer, and user-agent. Those fields enable fast pivots: aggregate by IP to find “top talkers,” aggregate by URI to identify scanning targets, and search for payload patterns like traversal strings or encoded script tags.

Correct interpretation depends on deployment details such as logrotate, time zone, and reverse proxies or load balancers. When a proxy sits in front of Apache, the “client IP” in the first field can be the proxy unless mod_remoteip (or an equivalent mechanism) is configured. Pattern searches also produce false positives, so matches work best as leads to validate by reviewing full log lines and correlating with server errors.

Steps to perform threat analysis on Apache log:

  1. List available Apache access logs, including rotated files.
    $ sudo ls -lh /var/log/apache2/access.log*
    -rw-r----- 1 root adm  92M Dec 13 10:30 /var/log/apache2/access.log
    -rw-r----- 1 root adm 184M Dec 13 00:00 /var/log/apache2/access.log.1
    -rw-r----- 1 root adm  14M Dec  6 00:00 /var/log/apache2/access.log.2.gz

    Common paths include /var/log/apache2/access.log on Ubuntu and Debian, and /var/log/httpd/access_log on Red Hat-based systems.

    Compressed rotations (for example, access.log.2.gz) are readable with zless and searchable with zgrep.

  2. Preview recent entries to confirm the log format and field order.
    $ sudo tail -n 3 /var/log/apache2/access.log
    203.0.113.45 - - [13/Dec/2025:10:29:58 +0000] "GET /wp-login.php HTTP/1.1" 404 512 "-" "Mozilla/5.0 (X11; Linux x86_64)"
    198.51.100.23 - - [13/Dec/2025:10:29:59 +0000] "POST /api/login HTTP/1.1" 401 233 "https://example.com/login" "curl/8.4.0"
    192.0.2.17 - - [13/Dec/2025:10:30:01 +0000] "GET / HTTP/1.1" 200 10432 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"

    The request line appears inside quotes as METHOD URI PROTOCOL, and status code and byte count follow.

  3. Identify the highest-volume source IPs.
    $ sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -10
      18452 203.0.113.45
       9211 198.51.100.23
       4120 192.0.2.17
       1325 66.249.66.1
        984 52.167.144.1
    ##### snipped #####

    Reverse proxies can mask client IPs unless mod_remoteip (or an equivalent) is configured, so validate what the first field represents before acting on it.

  4. Count HTTP methods to spot unexpected verbs.
    $ sudo awk '{print $6}' /var/log/apache2/access.log | tr -d '"' | sort | uniq -c | sort -nr
      98765 GET
      12450 POST
         84 HEAD
          7 OPTIONS
          1 PROPFIND

    Rare methods like PROPFIND, PUT, DELETE, or TRACE often indicate scanning or misconfiguration.

  5. Search for uncommon HTTP methods in request lines.
    $ sudo grep -E '"(PUT|DELETE|TRACE|OPTIONS|CONNECT|PROPFIND) ' /var/log/apache2/access.log | head -5
    203.0.113.45 - - [13/Dec/2025:10:21:04 +0000] "PROPFIND / HTTP/1.1" 405 235 "-" "Mozilla/5.0"
    198.51.100.23 - - [13/Dec/2025:10:22:19 +0000] "OPTIONS /api/login HTTP/1.1" 204 0 "https://example.com/login" "Mozilla/5.0"
    ##### snipped #####

    TRACE is commonly disabled for security reasons, and sustained PUT or DELETE requests can indicate an attempt to modify server content.

  6. Pivot into a single source IP by listing its most-requested URIs.
    $ sudo grep '^203.0.113.45 ' /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -nr | head -15
       4210 /wp-login.php
       3184 /xmlrpc.php
       2100 /.env
       1987 /phpmyadmin/
        944 /admin/
    ##### snipped #####

    Replacing 203.0.113.45 with an IP from the “top talkers” output keeps investigation focused.

  7. Count requests per minute for a single IP to spot bursts.
    $ sudo grep '^203.0.113.45 ' /var/log/apache2/access.log | cut -d[ -f2 | cut -d] -f1 | awk -F: '{print $1":"$2":"$3}' | sort | uniq -c | sort -nr | head -10
       612 13/Dec/2025:10:15
       598 13/Dec/2025:10:16
       577 13/Dec/2025:10:17
    ##### snipped #####

    Sustained high counts across many consecutive minutes are typical of automated scanning or brute-force activity.

  8. Identify scanning behavior by listing the most-requested missing pages (404).
    $ sudo awk '$9 == 404 {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -20
       5201 /wp-login.php
       3179 /xmlrpc.php
       1440 /.env
       1022 /phpmyadmin/
        611 /.git/config
    ##### snipped #####

    Repeated 404s clustered around well-known admin or framework paths usually indicate probing for vulnerable endpoints.

  9. Review authentication and authorization probing via 401 and 403 responses.
    $ sudo awk '$9 == 401 || $9 == 403 {print $1, $7, $9}' /var/log/apache2/access.log | head -10
    198.51.100.23 /api/login 401
    198.51.100.23 /api/login 401
    203.0.113.45 /admin/ 403
    203.0.113.45 /admin/ 403
    ##### snipped #####

    High volumes of 401s against a login endpoint can indicate credential stuffing, and repeated 403s against admin paths can indicate permission probing.

  10. Search for common exploit targets and misconfiguration leaks.
    $ sudo grep -Ei '"(GET|POST) /(wp-login\.php|xmlrpc\.php|\.env|phpmyadmin|\.git|admin|setup|config)' /var/log/apache2/access.log | head -8
    203.0.113.45 - - [13/Dec/2025:10:15:02 +0000] "GET /.env HTTP/1.1" 404 512 "-" "Mozilla/5.0"
    203.0.113.45 - - [13/Dec/2025:10:15:09 +0000] "GET /.git/config HTTP/1.1" 404 512 "-" "Mozilla/5.0"
    203.0.113.45 - - [13/Dec/2025:10:15:21 +0000] "GET /wp-login.php HTTP/1.1" 404 512 "-" "Mozilla/5.0"
    ##### snipped #####

    Paths like /.env and /.git/config should never be publicly accessible; any 200 responses for these require immediate remediation.

  11. Spot directory traversal attempts, including URL-encoded variants.
    $ sudo grep -Ei '(\.\./|%2e%2e%2f|%2fetc%2fpasswd|%2fproc%2fself%2fenviron)' /var/log/apache2/access.log | head -8
    203.0.113.45 - - [13/Dec/2025:10:17:32 +0000] "GET /../../../../etc/passwd HTTP/1.1" 400 222 "-" "Mozilla/5.0"
    203.0.113.45 - - [13/Dec/2025:10:17:40 +0000] "GET /%2e%2e%2f%2e%2e%2fetc%2fpasswd HTTP/1.1" 400 222 "-" "Mozilla/5.0"
    ##### snipped #####

    Traversal attempts can indicate active exploitation, especially when paired with successful 200/206 responses.

  12. Search for XSS-style payload indicators in URLs and parameters.
    $ sudo grep -Ei '(<script|%3cscript%3e|onerror=|javascript:|%22%3e%3c)' /var/log/apache2/access.log | head -8
    198.51.100.23 - - [13/Dec/2025:10:19:11 +0000] "GET /search?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1" 400 512 "-" "Mozilla/5.0"
    ##### snipped #####

    Encoding often hides payloads, so searches should include both raw and -encoded forms.</WRAP> - Inspect for SQL injection patterns in request parameters. <code>$ sudo grep -Ei 'union(\+|%20|[[:space:]])+select|or(\+|%20|[[:space:]])+1=1|information_schema|sleep(\(|%28)|benchmark(\(|%28)|%27|%22' /var/log/apache2/access.log | head -8 203.0.113.45 - - [13/Dec/2025:10:20:03 +0000] "GET /product?id=1%20union%20select%201,2,3 HTTP/1.1" 404 512 "-" "Mozilla/5.0" 203.0.113.45 - - [13/Dec/2025:10:20:07 +0000] "GET /search?q=%27%20or%201%3D1%23 HTTP/1.1" 400 512 "-" "Mozilla/5.0" ##### snipped #####</code> <WRAP alert>Any signs of SQL injection warrant immediate validation of input handling and parameterized queries in the application layer.</WRAP> - Identify command-injection style separators and suspicious execution hints. <code>$ sudo grep -Ei '(;|%3b|\||%7c|`|%60|\$\(|%24%28|cmd=|exec=|/bin/(sh|bash))' /var/log/apache2/access.log | head -8 203.0.113.45 - - [13/Dec/2025:10:21:44 +0000] "GET /cgi-bin/status?cmd=id%3Bwhoami HTTP/1.1" 404 512 "-" "Mozilla/5.0" ##### snipped #####</code> <WRAP alert>Command injection attempts can be destructive when an endpoint passes parameters into shell execution, so prioritize investigation when 200/302 responses appear.</WRAP> - Spot requests for high-risk file extensions commonly used in web shells or executables. <code>$ sudo grep -Ei '"(GET|POST) [^ ]*\.(php|phtml|phar|jsp|asp|aspx|exe|sh|py)(\?| |")' /var/log/apache2/access.log | head -8 203.0.113.45 - - [13/Dec/2025:10:22:58 +0000] "GET /shell.php HTTP/1.1" 404 512 "-" "Mozilla/5.0" ##### snipped #####</code> <WRAP info>Extension scans often accompany directory traversal and exploit-kit probing.</WRAP> - Find anomalous or automation-heavy user agents by frequency. <code>$ sudo awk -F'"' '{print $6}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -15 14220 Mozilla/5.0 (X11; Linux x86_64) 8112 Mozilla/5.0 (Windows NT 10.0; Win64; x64) 944 curl/8.4.0 233 python-requests/2.31.0 77 Go-http-client/1.1 ##### snipped #####</code> <WRAP tip>Legitimate monitoring can also appear as //curl// or //python-requests//, so correlate user-agent findings with URI targets and status codes.</WRAP> - Review 5xx responses to locate endpoints under stress or possible exploitation. <code>$ sudo awk '$9 ~ /^5/ {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -20 144 /api/login 62 /search 11 /checkout ##### snipped #####</code> <WRAP alert>Spikes in 500-series responses during probing can indicate crashes, unhandled exceptions, or resource exhaustion.</WRAP> - Correlate suspicious windows with the //Apache// error log to confirm server-side failures. <code>$ sudo tail -n 20 /var/log/apache2/error.log [Sat Dec 13 10:15:07.123456 2025] [php:error] [pid 3124] [client 203.0.113.45:53122] PHP Fatal error: Uncaught Exception in /var/www/html/app.php on line 88 [Sat Dec 13 10:15:07.123501 2025] [core:error] [pid 3124] [client 203.0.113.45:53122] End of script output before headers: app.php ##### snipped #####</code> <WRAP important>Error logs can contain stack traces and sensitive paths, so handle exports with the same care as production data.</WRAP> - Filter the access log to a specific incident window using an exact timestamp prefix. <code>$ sudo grep '\[13/Dec/2025:10:15:' /var/log/apache2/access.log | head -3 203.0.113.45 - - [13/Dec/2025:10:15:02 +0000] "GET /.env HTTP/1.1" 404 512 "-" "Mozilla/5.0" 203.0.113.45 - - [13/Dec/2025:10:15:09 +0000] "GET /.git/config HTTP/1.1" 404 512 "-" "Mozilla/5.0" 203.0.113.45 - - [13/Dec/2025:10:15:21 +0000] "GET /wp-login.php HTTP/1.1" 404 512 "-" "Mozilla/5.0"</code> <WRAP tip>Matching on [DD/Mon/YYYY:HH:MM:%% keeps searches aligned to the Apache timestamp format.

  13. Build a quick suspect list by counting IPs that hit high-risk exploit patterns.
    $ sudo grep -Ei '(\.\./|%2e%2e%2f|%2fetc%2fpasswd|%3cscript%3e|<script|union(\+|%20|[[:space:]])+select|or(\+|%20|[[:space:]])+1=1|sleep(\(|%28)|benchmark(\(|%28)|;|%3b|\||%7c|`|%60|\$\(|%24%28)' /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -20
       982 203.0.113.45
        14 198.51.100.23
    ##### snipped #####

    Matches and counts indicate likelihood, not certainty, so confirm intent by reviewing full request context before applying permanent blocks.

Discuss the article:

Comment anonymously. Login not required.