Nginx Log Attack Tracing: Finding Intrusion Traces in access.log
Reconstruct an attack chain from Nginx access.log. Covers an enhanced log format, detection commands for SQL injection, path traversal, and brute force, plus a one-shot tracing script.
Scope
For security ops engineers, incident responders, and DevOps teams. Covers tracing attacks in Nginx logs to find intrusion evidence. When a server looks compromised or a site behaves abnormally, Nginx access.log is the first source to analyze. This article gives a full method from log collection to attack reconstruction, plus runnable commands.
Prerequisites
- Linux server with Nginx 1.10+ and access.log enabled (default path
/var/log/nginx/access.log) - Basic Linux commands: awk, grep, sort, uniq
- GoAccess or ELK Stack recommended for visualization (optional)
- Custom Nginx log format recommended so more attack features are captured
How it works
Nginx access.log records metadata for every HTTP request: source IP, request time, method, URL path, HTTP status, User-Agent, Referer, and more. Attack tracing correlates these fields to reconstruct the attacker's kill chain: recon → scan → exploit → privilege escalation → lateral movement. Common cases include WebShell access, scanner probes, brute-force attempts, SQL injection payloads, and file-inclusion attempts.
Procedure
Step 1: Configure an enhanced log format
The default combined format is missing key fields. Enable a custom format in /etc/nginx/nginx.conf:
http {
# Enhanced log format — request body size, upstream response time, TLS version
log_format attack_trace '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$ssl_protocol $ssl_cipher';
access_log /var/log/nginx/access.log attack_trace;
}
Reload Nginx: nginx -t && systemctl reload nginx
Step 2: Attack-signature command cheat sheet
Copy these commands and run them on the server:
1. Find the most common attacking IPs:
# Top 20 IPs by request volume
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# IPs with the most 404s (scanner signature)
awk '$9 == 404 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
2. Detect malicious scanning:
# Likely SQL injection requests
grep -iE "(union.*select|select.*from|or+1=1|--|%27)" /var/log/nginx/access.log | head -50
# Path traversal attempts
grep -iE "(\.\./|\.\.\|/etc/passwd|/proc/self)" /var/log/nginx/access.log | head -50
# WebShell access attempts
grep -iE "(cmd|exec|shell|eval|passthru|system)\.(php|asp|aspx|jsp)" /var/log/nginx/access.log | head -50
3. Analyze HTTP status code anomalies:
# Status code distribution
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# IPs with the most 403s (WAF blocks)
awk '$9 == 403 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
4. Detect brute force:
# Frequent hits on the login endpoint (wp-login.php)
grep "wp-login" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# IPs with the most POST requests (form submissions)
awk '$6 == "\"POST"' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
5. Analyze User-Agent anomalies:
# User-Agent distribution
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -30
# Known malicious tool signatures
grep -iE "(sqlmap|nikto|nmap|masscan|acunetix|nessus|openvas|python-requests|curl|wget)" \
/var/log/nginx/access.log | awk '{print $1}' | sort -u
Step 3: Reconstruct the attack timeline
Use time as the axis:
# Attack frequency by hour
awk '{print $4}' /var/log/nginx/access.log | cut -d: -f1-2 | sort | uniq -c | sort -rn
# Full request sequence for a specific IP (attack timeline)
grep "203.0.113.42" /var/log/nginx/access.log | awk '{print $4, $6, $7, $9}' | head -100
Step 4: Automated tracing script
#!/bin/bash
# /usr/local/bin/attack_trace.sh — one-shot attack tracing report
LOG="/var/log/nginx/access.log"
REPORT="/tmp/attack_report_$(date +%Y%m%d).txt"
echo "=== Attack tracing report $(date) ===" > $REPORT
echo "" >> $REPORT
echo "--- Anomalous IP Top 10 (by 404) ---" >> $REPORT
awk '$9 == 404 {print $1}' $LOG | sort | uniq -c | sort -rn | head -10 >> $REPORT
echo "" >> $REPORT
echo "--- Suspected SQL injection requests ---" >> $REPORT
grep -ciE "(union.*select|or+1=1|%27)" $LOG >> $REPORT
echo " SQL injection signature requests logged" >> $REPORT
echo "" >> $REPORT
echo "--- Scanner User-Agents ---" >> $REPORT
grep -ciE "(sqlmap|nikto|acunetix|nessus)" $LOG >> $REPORT
echo " scanner requests" >> $REPORT
echo "" >> $REPORT
echo "--- Brute-force attempt stats ---" >> $REPORT
grep -c "wp-login" $LOG >> $REPORT
echo " wp-login.php hits" >> $REPORT
echo "Report written to: $REPORT"
Add execute permission and a cron job:
chmod +x /usr/local/bin/attack_trace.sh
# Generate a report at 02:00 daily
echo "0 2 * * * root /usr/local/bin/attack_trace.sh" >> /etc/crontab
Verify the config
# 1. Confirm the Nginx log format is in effect
tail -1 /var/log/nginx/access.log | awk '{print NF}'
# Output should have 9+ fields (including ssl_protocol and other extras)
# 2. Run the one-shot tracing report
bash /usr/local/bin/attack_trace.sh
cat /tmp/attack_report_$(date +%Y%m%d).txt
# 3. Simulate an attack signature and verify detection
curl -v "http://yourdomain.com/user?id=1%27%20OR%201=1"
# Run the report again and confirm the SQL injection count increased
FAQ
Q1: The log file is huge (GB-scale). grep is too slow. What then?
Three options: (1) use ngxtop for real-time analysis; (2) deploy GoAccess for fast binary-level parsing: goaccess /var/log/nginx/access.log -o report.html; (3) ship logs to ELK or Loki for full-text indexing. For incident response only, slice the last 100k lines with tail -n 100000 first.
Q2: The attacker used a proxy/CDN. access.log only shows CDN node IPs. What then?
Enable the realip module to get the real client IP: (1) compile Nginx with --with-http_realip_module; (2) configure set_real_ip_from CDN_CIDR; real_ip_header X-Forwarded-For;; (3) if the CDN does not pass X-Forwarded-For, ask the CDN to enable XFF passthrough or read the origin IP from CDN logs.
Summary
Nginx log attack tracing is basic incident-response work. An enhanced log format, a standard command library, and automated reports cut hours of investigation to minutes. Key points: a log format that meets security needs; a standard analysis flow (anomalous IPs → anomalous status codes → anomalous request patterns → timeline reconstruction); regular tracing reports. Put these commands and scripts in the ops incident-response SOP so they become routine security checks.