Apache and Nginx Logs: Analysis and Rotation
Web server logs are invaluable sources of information for monitoring application health, diagnosing issues, analyzing traffic patterns, detecting security threats, and understanding user behavior. Both Apache and Nginx generate detailed access and error logs that, when properly managed and analyzed, provide critical insights into your web infrastructure. This comprehensive guide covers log configuration, analysis techniques, rotation strategies, and best practices for both Apache and Nginx web servers.
Effective log management is essential for maintaining operational visibility, troubleshooting performance issues, ensuring security compliance, and making data-driven decisions about your infrastructure. Whether you're tracking down a mysterious 500 error, analyzing traffic patterns for capacity planning, or investigating potential security incidents, mastering web server log analysis and management is a fundamental skill for system administrators and DevOps engineers.
Table of Contents
- Prerequisites
- Understanding Web Server Logs
- Apache Log Configuration
- Nginx Log Configuration
- Log Analysis Techniques
- Log Rotation
- Centralized Logging
- Security and Compliance
- Troubleshooting
- Best Practices
- Conclusion
Prerequisites
Before working with web server logs, ensure you have:
- Operating System: Ubuntu 20.04/22.04, Debian 10/11, CentOS 8/Rocky Linux 8, or similar
- Web Server: Apache 2.4+ or Nginx 1.18+ installed and running
- Root or sudo access: Required for configuring log settings and rotation
- Basic knowledge: Understanding of HTTP, web servers, and command-line tools
- Disk space: Adequate storage for log retention (logs can grow quickly)
- Text editor: nano, vim, or similar for editing configuration files
- Log analysis tools: Basic familiarity with grep, awk, sed recommended
Understanding Web Server Logs
Types of Web Server Logs
Access Logs: Record all requests processed by the web server
- Client IP addresses
- Request timestamps
- HTTP methods and URLs
- Response status codes
- Bytes transferred
- User agents and referrers
Error Logs: Record server errors, warnings, and diagnostic information
- Application errors (500 errors)
- Configuration issues
- Missing files (404 errors)
- Permission problems
- Module warnings
Log Formats
Combined Log Format (most common):
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
Example entry:
192.168.1.100 - - [10/Jan/2026:10:30:45 +0000] "GET /index.html HTTP/1.1" 200 2326 "https://google.com" "Mozilla/5.0..."
Fields explained:
%h: Client IP address (192.168.1.100)%l: Identity (usually -)%u: Authenticated user (or -)%t: Timestamp%r: Request line%>s: Status code (200)%b: Size in bytes (2326)Referer: Where visitor came fromUser-Agent: Browser information
Log Locations
Apache:
- Ubuntu/Debian:
/var/log/apache2/- access.log
- error.log
- CentOS/Rocky:
/var/log/httpd/- access_log
- error_log
Nginx:
- All distributions:
/var/log/nginx/- access.log
- error.log
Apache Log Configuration
Custom Log Formats
Define custom log formats in Apache configuration:
sudo nano /etc/apache2/apache2.conf
# Standard Combined format
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
# Common format
LogFormat "%h %l %u %t \"%r\" %>s %O" common
# Virtual host combined format
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
# Custom format with response time
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" custom_timing
# JSON format for easy parsing
LogFormat "{ \"time\":\"%t\", \"remote_addr\":\"%a\", \"status\":\"%>s\", \"request\":\"%U%q\", \"method\":\"%m\", \"bytes\":\"%B\", \"referer\":\"%{Referer}i\", \"agent\":\"%{User-Agent}i\", \"duration\":\"%D\" }" json_format
# Detailed format with SSL information
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{SSL_PROTOCOL}x %{SSL_CIPHER}x" ssl_combined
Virtual Host Log Configuration
Configure per-site logging:
sudo nano /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com
# Separate access log for this site
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
# Separate error log for this site
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
# Set error log level (debug, info, notice, warn, error, crit, alert, emerg)
LogLevel warn
# Conditional logging (skip logging for specific paths)
SetEnvIf Request_URI "^/health-check$" dontlog
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined env=!dontlog
</VirtualHost>
Error Log Levels
Configure Apache error log verbosity:
# Global log level
LogLevel warn
# Module-specific log levels
LogLevel core:info ssl:warn rewrite:trace3
# Per-directory log level
<Directory /var/www/html/api>
LogLevel debug
</Directory>
Log levels (most to least verbose):
trace8-trace1: Trace messagesdebug: Debugging messagesinfo: Informational messagesnotice: Normal but significant conditionswarn: Warning conditionserror: Error conditionscrit: Critical conditionsalert: Action must be taken immediatelyemerg: System is unusable
Conditional Logging
Skip logging for specific requests:
# Don't log health checks and monitoring
SetEnvIf Request_URI "^/health$" dontlog
SetEnvIf Request_URI "^/status$" dontlog
SetEnvIf Request_URI "^/ping$" dontlog
# Don't log static assets (optional)
SetEnvIf Request_URI "\.(jpg|gif|png|css|js|ico)$" dontlog
# Apply conditional logging
CustomLog ${APACHE_LOG_DIR}/access.log combined env=!dontlog
# Log only errors for specific IPs
SetEnvIf Remote_Addr "192\.168\.1\." internalip
CustomLog ${APACHE_LOG_DIR}/internal-access.log combined env=internalip
Nginx Log Configuration
Custom Log Formats
Define custom log formats in Nginx:
sudo nano /etc/nginx/nginx.conf
http {
# Default combined format
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
# Extended format with timing information
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
# JSON format for structured logging
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request":"$request",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"http_referrer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for"'
'}';
# Performance monitoring format
log_format performance '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'rt=$request_time '
'uct=$upstream_connect_time '
'uht=$upstream_header_time '
'urt=$upstream_response_time '
'cs=$upstream_cache_status';
# SSL/TLS format
log_format ssl_combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$ssl_protocol $ssl_cipher';
# Default access log
access_log /var/log/nginx/access.log combined;
# Error log with level
error_log /var/log/nginx/error.log warn;
}
Server Block Log Configuration
Configure per-site logging:
sudo nano /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
root /var/www/html;
# Access log with custom format
access_log /var/log/nginx/example.com-access.log detailed;
# Error log with level
error_log /var/log/nginx/example.com-error.log warn;
# Conditional logging - skip health checks
location = /health {
access_log off;
return 200 "OK\n";
}
# Disable logging for static files (optional)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
access_log off;
expires 30d;
}
# Main location
location / {
try_files $uri $uri/ =404;
}
}
Error Log Levels
Configure Nginx error log verbosity:
# Available levels: debug, info, notice, warn, error, crit, alert, emerg
# Global error log
error_log /var/log/nginx/error.log warn;
# Server-specific error log
server {
error_log /var/log/nginx/example.com-error.log error;
}
# Debug logging for specific location
location /api {
error_log /var/log/nginx/api-debug.log debug;
}
Buffered Logging
Improve I/O performance with buffered logging:
# Buffer logs to reduce disk I/O
access_log /var/log/nginx/access.log combined buffer=32k flush=5s;
# Disable access logging for high-performance scenarios
access_log off;
Log Analysis Techniques
Basic Log Analysis Commands
View recent log entries:
# Apache
sudo tail -f /var/log/apache2/access.log
sudo tail -f /var/log/apache2/error.log
# Nginx
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
# View last 100 lines
sudo tail -100 /var/log/apache2/access.log
# View logs from specific time
sudo grep "10/Jan/2026" /var/log/nginx/access.log
Analyze Status Codes
# Count status codes
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr
# Count 404 errors
grep " 404 " /var/log/apache2/access.log | wc -l
# List unique 404 URLs
grep " 404 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -nr | head -20
# Count 500 errors by hour
grep " 500 " /var/log/apache2/access.log | cut -d[ -f2 | cut -d] -f1 | awk -F: '{print $2":00"}' | sort -n | uniq -c
Analyze Traffic Sources
# Top IP addresses
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -20
# Top user agents
awk -F'"' '{print $6}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -10
# Top referrers
awk -F'"' '{print $4}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -20
# Requests by country (requires GeoIP)
# Install: sudo apt install geoip-bin geoip-database
Analyze Request Patterns
# Most requested URLs
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -20
# Requests by HTTP method
awk '{print $6}' /var/log/apache2/access.log | sort | uniq -c | sort -nr
# Requests per hour
cut -d[ -f2 /var/log/nginx/access.log | cut -d] -f1 | awk -F: '{print $2":00"}' | sort -n | uniq -c
# Bandwidth usage by IP
awk '{bytes+=$10} END {print bytes/1024/1024 " MB"}' /var/log/nginx/access.log
Detect Security Issues
# Detect SQL injection attempts
grep -i "select.*from\|union.*select\|insert.*into\|delete.*from" /var/log/apache2/access.log
# Detect path traversal attempts
grep -E "\.\./|\.\.%2F" /var/log/nginx/access.log
# Detect brute force login attempts
grep "POST /login" /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -nr
# Detect suspicious user agents
grep -i "sqlmap\|nikto\|nessus\|nmap" /var/log/nginx/access.log
# Find requests from specific IP
grep "192.168.1.100" /var/log/apache2/access.log
Performance Analysis
# Slowest requests (Nginx with timing in log format)
awk '{print $NF, $7}' /var/log/nginx/access.log | sort -nr | head -20
# Average response time
awk '{sum+=$NF; count++} END {print sum/count}' /var/log/nginx/access.log
# Requests taking longer than 1 second
awk '$NF > 1.0 {print $7, $NF}' /var/log/nginx/access.log
# Traffic by hour
awk '{print $4}' /var/log/apache2/access.log | cut -d: -f2 | sort -n | uniq -c
Using GoAccess for Real-Time Analysis
Install and use GoAccess for interactive log analysis:
# Install GoAccess
sudo apt install goaccess -y
# Real-time Apache analysis
sudo goaccess /var/log/apache2/access.log --log-format=COMBINED -o /var/www/html/report.html --real-time-html
# Real-time Nginx analysis
sudo goaccess /var/log/nginx/access.log --log-format=COMBINED -o /var/www/html/report.html --real-time-html
# Terminal-based interface
sudo goaccess /var/log/nginx/access.log --log-format=COMBINED
# Generate static HTML report
sudo goaccess /var/log/apache2/access.log --log-format=COMBINED -o /tmp/report.html
Log Rotation
Logrotate Configuration
Logrotate automatically rotates, compresses, and manages logs:
Apache logrotate configuration:
sudo nano /etc/logrotate.d/apache2
/var/log/apache2/*.log {
# Rotate daily
daily
# Keep 14 days of logs
rotate 14
# Compress old logs
compress
# Delay compression until next rotation
delaycompress
# Don't error if log file is missing
missingok
# Don't rotate if log is empty
notifempty
# Create new log with specific permissions
create 640 root adm
# Share log files between multiple processes
sharedscripts
# Reload Apache after rotation
postrotate
if /etc/init.d/apache2 status > /dev/null 2>&1; then \
/etc/init.d/apache2 reload > /dev/null 2>&1; \
fi;
endscript
# Alternatively, use systemd
# postrotate
# systemctl reload apache2 > /dev/null 2>&1 || true
# endscript
}
Nginx logrotate configuration:
sudo nano /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
# Signal Nginx to reopen log files
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}
Custom Rotation Schedules
Weekly rotation with 8 weeks retention:
/var/log/nginx/example.com-access.log {
weekly
rotate 8
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
Size-based rotation:
/var/log/apache2/high-traffic-access.log {
# Rotate when log reaches 100MB
size 100M
# Keep 50 rotated logs
rotate 50
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
postrotate
systemctl reload apache2
endscript
}
Manual Log Rotation
# Force log rotation
sudo logrotate -f /etc/logrotate.d/nginx
# Test logrotate configuration
sudo logrotate -d /etc/logrotate.d/apache2
# Check logrotate status
cat /var/lib/logrotate/status
Alternative: Rotation with Systemd Timer
Create custom rotation with systemd:
# Create rotation script
sudo nano /usr/local/bin/rotate-nginx-logs.sh
#!/bin/bash
LOG_DIR="/var/log/nginx"
ARCHIVE_DIR="/var/log/nginx/archive"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "$ARCHIVE_DIR"
# Move current log
mv "$LOG_DIR/access.log" "$ARCHIVE_DIR/access-$DATE.log"
mv "$LOG_DIR/error.log" "$ARCHIVE_DIR/error-$DATE.log"
# Signal Nginx to create new logs
kill -USR1 $(cat /var/run/nginx.pid)
# Compress old logs
gzip "$ARCHIVE_DIR/access-$DATE.log"
gzip "$ARCHIVE_DIR/error-$DATE.log"
# Delete logs older than 30 days
find "$ARCHIVE_DIR" -name "*.log.gz" -mtime +30 -delete
# Make executable
sudo chmod +x /usr/local/bin/rotate-nginx-logs.sh
# Create systemd service
sudo nano /etc/systemd/system/nginx-log-rotation.service
[Unit]
Description=Nginx Log Rotation
[Service]
Type=oneshot
ExecStart=/usr/local/bin/rotate-nginx-logs.sh
# Create systemd timer
sudo nano /etc/systemd/system/nginx-log-rotation.timer
[Unit]
Description=Nginx Log Rotation Timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
# Enable and start timer
sudo systemctl enable nginx-log-rotation.timer
sudo systemctl start nginx-log-rotation.timer
# Check timer status
sudo systemctl list-timers
Centralized Logging
rsyslog Configuration
Send logs to centralized syslog server:
Configure Apache to use syslog:
# Add to Apache configuration
CustomLog "|/usr/bin/logger -t apache -p local6.info" combined
ErrorLog syslog:local6
Configure Nginx to use syslog:
access_log syslog:server=192.168.1.100:514,facility=local6,tag=nginx,severity=info combined;
error_log syslog:server=192.168.1.100:514,facility=local6,tag=nginx,severity=error;
ELK Stack Integration
Send logs to Elasticsearch/Logstash/Kibana:
Install Filebeat:
# Add Elastic repository
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
echo "deb https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
# Install Filebeat
sudo apt update
sudo apt install filebeat -y
# Configure Filebeat
sudo nano /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/access.log
fields:
log_type: nginx_access
- type: log
enabled: true
paths:
- /var/log/nginx/error.log
fields:
log_type: nginx_error
output.elasticsearch:
hosts: ["localhost:9200"]
setup.kibana:
host: "localhost:5601"
# Enable and start Filebeat
sudo systemctl enable filebeat
sudo systemctl start filebeat
Security and Compliance
Secure Log Files
# Set appropriate permissions
sudo chmod 640 /var/log/nginx/access.log
sudo chmod 640 /var/log/apache2/error.log
# Set ownership
sudo chown root:adm /var/log/nginx/*.log
sudo chown root:adm /var/log/apache2/*.log
# Restrict directory access
sudo chmod 750 /var/log/nginx
sudo chmod 750 /var/log/apache2
Log Anonymization
Remove sensitive data from logs:
Apache anonymization:
# Hash IP addresses
LogFormat "%{c}a %l %u %t \"%r\" %>s %b" anonymized
SetEnvIf Remote_Addr "(\d+\.\d+\.\d+)\.\d+" ANONYMIZED_IP=$1.0
CustomLog ${APACHE_LOG_DIR}/access.log "%{ANONYMIZED_IP}e %l %u %t \"%r\" %>s %b"
Nginx anonymization:
# Hash or truncate IP addresses
map $remote_addr $anonymized_ip {
~(?P<ip>\d+\.\d+\.\d+)\.\d+ $ip.0;
~(?P<ip>[^:]+:[^:]+): $ip::;
default 0.0.0.0;
}
log_format anonymized '$anonymized_ip - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log anonymized;
Compliance Considerations
GDPR compliance:
- Anonymize or pseudonymize IP addresses
- Implement data retention policies
- Secure log storage and access
- Document log processing activities
PCI DSS compliance:
- Log all access to cardholder data
- Implement log retention (minimum 1 year)
- Secure log storage with access controls
- Regular log reviews and monitoring
Troubleshooting
Logs Not Being Created
Check permissions:
# Verify log directory permissions
ls -la /var/log/nginx
ls -la /var/log/apache2
# Fix permissions
sudo mkdir -p /var/log/nginx
sudo chown www-data:adm /var/log/nginx
sudo chmod 755 /var/log/nginx
Check configuration:
# Test Apache configuration
sudo apache2ctl configtest
# Test Nginx configuration
sudo nginx -t
# Check if logs are defined
sudo apache2ctl -S | grep -i log
sudo nginx -T | grep -i log
Disk Space Issues
Monitor disk usage:
# Check disk usage
df -h
# Check log directory size
du -sh /var/log/nginx
du -sh /var/log/apache2
# Find largest log files
find /var/log -type f -size +100M -exec ls -lh {} \;
Clean up old logs:
# Manually delete old compressed logs
sudo find /var/log/nginx -name "*.gz" -mtime +30 -delete
sudo find /var/log/apache2 -name "*.gz" -mtime +30 -delete
# Truncate current log (use with caution)
sudo truncate -s 0 /var/log/nginx/access.log
Log Rotation Not Working
Debug logrotate:
# Run logrotate in debug mode
sudo logrotate -d /etc/logrotate.d/nginx
# Force rotation
sudo logrotate -f /etc/logrotate.d/nginx
# Check logrotate status
cat /var/lib/logrotate/status
# Check for errors
sudo journalctl -u logrotate
Best Practices
- Implement log rotation: Prevent disk space exhaustion
- Monitor log sizes: Set up alerts for unusual growth
- Use structured logging: JSON format for easier parsing
- Centralize logs: Send to dedicated log management system
- Set appropriate retention: Balance compliance and storage costs
- Secure log access: Restrict permissions and implement access controls
- Regular analysis: Review logs for security and performance issues
- Automate alerting: Set up notifications for critical errors
- Document log formats: Maintain documentation of custom formats
- Test log configuration: Verify logging after configuration changes
Conclusion
Effective web server log management is crucial for maintaining operational visibility, troubleshooting issues, ensuring security, and meeting compliance requirements. By properly configuring Apache and Nginx logging, implementing robust log rotation strategies, and utilizing powerful analysis tools, you can extract valuable insights from your web server logs while managing storage efficiently.
Key takeaways:
- Configure appropriately: Customize log formats for your specific needs
- Rotate regularly: Implement automated log rotation to manage disk space
- Analyze systematically: Use command-line tools and specialized software for log analysis
- Centralize when possible: Send logs to centralized systems for better visibility
- Secure logs: Implement proper permissions and anonymization where required
- Monitor continuously: Set up automated monitoring and alerting
Logs are your primary source of truth for understanding web server behavior, diagnosing issues, and detecting security threats. Invest time in proper log configuration and analysis—it will pay dividends when troubleshooting production issues or investigating security incidents.


