Linux and Bash course menu
Bash Project: Analyse a Web Server Log
Build a Bash report for web server logs: top pages, top IP addresses, status codes, errors by hour and total traffic, using grep, awk, sort and uniq.
On this page
Every web server records each request. In this project, you will turn a raw log into a report that answers real questions: which pages are popular, who is hitting the site hardest, and what is failing?
The log format#
The file access.log is in the common format used by Nginx and Apache. The addresses are from ranges reserved for documentation:
head -n 2 access.log
wc -l access.log192.0.2.44 - - [05/Nov/2026:09:23:00 +0530] "GET /products HTTP/1.1" 200 4252 "-" "Mozilla/5.0"
192.0.2.10 - - [05/Nov/2026:13:44:00 +0530] "GET /login HTTP/1.1" 200 1718 "-" "Mozilla/5.0"
400 access.logSplitting on spaces gives these awk fields:
Step 1: Try each question#
Most popular pages:
awk '{print $7}' access.log | sort | uniq -c | sort -nr | head -n 3 98 /products
87 /login
70 /missingStatus codes:
awk '{print $9}' access.log | sort | uniq -c | sort -nr 177 200
96 404
57 403
35 500
35 301Pages that fail with a server error:
awk '$9 >= 500 {print $7}' access.log | sort | uniq -c | sort -nr | head -n 3 14 /products
12 /login
6 /cartRequests per hour:
awk '{print substr($4, 14, 2)}' access.log | sort | uniq -c 58 08
65 09
69 10
67 11
69 12
72 13Total data sent:
awk '{total += $10} END {printf "%.1f MB\n", total / 1024 / 1024}' access.log0.9 MBStep 2: The script#
Now combine them in report.sh:
cat > report.sh <<'EOF'
#!/usr/bin/env bash
# Usage: report.sh <access log> [top N]
set -euo pipefail
log_file="${1:?Usage: report.sh <access log> [top N]}"
top="${2:-3}"
[[ -r "$log_file" ]] || { echo "Cannot read $log_file" >&2; exit 1; }
heading() {
printf '\n== %s ==\n' "$1"
}
top_values() {
awk "{print \$$1}" "$log_file" | sort | uniq -c | sort -nr | head -n "$top"
}
total=$(wc -l < "$log_file")
errors=$(awk '$9 >= 400' "$log_file" | wc -l)
echo "Report for $log_file"
echo "Requests: $total"
awk -v e="$errors" -v t="$total" 'BEGIN {printf "Errors: %d (%.1f%%)\n", e, e * 100 / t}'
heading "Top pages"
top_values 7
heading "Top clients"
top_values 1
heading "Status codes"
top_values 9
heading "Pages with server errors"
awk '$9 >= 500 {print $7}' "$log_file" | sort | uniq -c | sort -nr | head -n "$top"
EOF
chmod +x report.sh
./report.sh access.logReport for access.log
Requests: 400
Errors: 188 (47.0%)
== Top pages ==
98 /products
87 /login
70 /missing
== Top clients ==
142 192.0.2.10
96 198.51.100.7
65 203.0.113.9
== Status codes ==
177 200
96 404
57 403
== Pages with server errors ==
14 /products
12 /login
6 /cartHow it works#
${1:?message}stops with a message if there is no argument.top_valuesis a reusable function that takes a field number. Double quotes let$1become the number, and\$keeps a literal$forawk.awk -vpasses shell values in safely.
Step 3: Save and compare#
Keep a daily report by redirecting the output:
./report.sh access.log 5 > "report-$(date +%F).txt"
ls report-*.txt | wc -l
./report.sh missing.log
echo "Exit code: $?"1
Cannot read missing.log
Exit code: 1Step 4: Automate it#
Run it every morning with cron:
0 8 * * * /home/asha/report.sh /var/log/nginx/access.log 10 > /home/asha/reports/daily.txt 2>&1Extend the project#
- Accept several log files, including
.gzfiles, usingzcat. - Show the top IP addresses that receive
404errors, which often indicates scanners. - Print a warning, and exit with code 1, if errors go over a limit, so that cron or CI can react.
- Produce the report as an HTML page.
- Load the log into a database, and answer the same questions with SQL.
Key takeaway#
Next: Linux interview questions.
Finished this lesson?