8.6 KiB
NPM Logging & Audit Guide
Centralized logging for all external access Created: 2025-11-11
Why NPM is the Logging Hub
All public service access routes through NPM, which means:
- Every external request is logged
- Failed authentication attempts tracked
- Rate limiting violations recorded
- SSL certificate renewals logged
- Configuration changes audited
Accessing NPM Logs
Via NPM UI
Access NPM admin panel:
- VPN: http://10.99.0.1:81
- Local: http://192.168.86.149:81
View logs:
- Navigate to each Proxy Host
- Click "View Logs" button
- See real-time access logs
- Filter by status code, IP, user agent
Via Docker Logs
Real-time monitoring:
# All NPM logs
docker logs -f nginx-proxy-manager
# Filter for access logs only
docker logs -f nginx-proxy-manager 2>&1 | grep -i "access"
# Filter for errors
docker logs -f nginx-proxy-manager 2>&1 | grep -i "error"
# Filter for specific service (e.g., Jellyfin)
docker logs -f nginx-proxy-manager 2>&1 | grep "media.schweitz.net"
Via Log Files
Log location:
# Access logs stored in container volume
ls -lh ~/docker-data/nginx-proxy-manager/data/logs/
# View access logs
tail -f ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
# View error logs
tail -f ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*_error.log
Log Format
Standard NPM access log:
192.0.2.1 - - [11/Nov/2025:21:30:15 +0100] "GET /api/users HTTP/2.0" 200 1234 "https://media.schweitz.net" "Mozilla/5.0..."
Fields:
- IP address: Client IP (or Cloudflare IP if proxied)
- Timestamp: When request occurred
- HTTP method: GET, POST, etc.
- Request path: /api/users
- Protocol: HTTP/2.0
- Status code: 200 (success), 404 (not found), 403 (forbidden), etc.
- Bytes sent: Response size
- Referer: Previous page
- User agent: Browser/client info
Useful Log Queries
Find Failed Login Attempts
# Status codes 401 (unauthorized) or 403 (forbidden)
grep -E " (401|403) " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
# With IP addresses
grep -E " (401|403) " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | awk '{print $1}' | sort | uniq -c | sort -nr
Monitor Specific Service Access
# Jellyfin access
grep "media.schweitz.net" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | tail -20
# Nextcloud uploads (POST requests)
grep "cloud.schweitz.net" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | grep "POST"
Identify High-Traffic IPs
# Top 10 IP addresses by request count
awk '{print $1}' ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | sort | uniq -c | sort -nr | head -10
Monitor SSL Certificate Activity
# Certificate renewal attempts
docker logs nginx-proxy-manager 2>&1 | grep -i "letsencrypt"
# Certificate errors
docker logs nginx-proxy-manager 2>&1 | grep -i "certificate" | grep -i "error"
Track API Usage
# API endpoint access
grep "/api/" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
# Specific API endpoint
grep "/api/login" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log
Security Monitoring
Suspicious Activity Patterns
Brute force attempts:
# Multiple 401s from same IP (potential brute force)
grep " 401 " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
awk '{print $1}' | sort | uniq -c | sort -nr | \
awk '$1 > 10 {print "Potential brute force from " $2 " (" $1 " attempts)"}'
Directory scanning:
# Looking for 404s (scanning for vulnerabilities)
grep " 404 " ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
grep -E "(wp-admin|phpmyadmin|admin|login\.php)"
Unusual user agents:
# Non-browser requests (potential bots/scrapers)
grep -v "Mozilla" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
grep -v "curl" | tail -20
Log Rotation
Automatic rotation configuration:
NPM handles basic rotation, but for long-term storage:
# Create logrotate config
sudo tee /etc/logrotate.d/nginx-proxy-manager <<EOF
/home/jpmschweitzer/docker-data/nginx-proxy-manager/data/logs/*.log {
daily
rotate 30
compress
delaycompress
notifempty
missingok
create 0644 root root
postrotate
docker exec nginx-proxy-manager nginx -s reload > /dev/null 2>&1 || true
endscript
}
EOF
# Test logrotate config
sudo logrotate -d /etc/logrotate.d/nginx-proxy-manager
Manual log cleanup:
# Archive old logs
cd ~/docker-data/nginx-proxy-manager/data/logs/
tar -czf logs-archive-$(date +%Y%m%d).tar.gz *.log
mv logs-archive-*.tar.gz ~/backups/
# Clean logs older than 30 days
find ~/docker-data/nginx-proxy-manager/data/logs/ -name "*.log" -mtime +30 -delete
Centralized Logging (Future Enhancement)
Option 1: Ship logs to external service
Use a log aggregator like:
- Loki + Grafana (self-hosted)
- Elasticsearch + Kibana
- Splunk
- Cloud services (Datadog, Loggly)
Option 2: Syslog forwarding
Configure NPM to forward to syslog:
# Add to NPM custom nginx config
access_log syslog:server=10.99.0.1:514,tag=nginx combined;
Option 3: Promtail + Loki (Recommended)
Deploy Promtail container to tail NPM logs and send to Loki:
# Future: stacks/promtail.yml
services:
promtail:
image: grafana/promtail:latest
volumes:
- /home/jpmschweitzer/docker-data/nginx-proxy-manager/data/logs:/var/log/nginx
- ./promtail-config.yml:/etc/promtail/config.yml
command: -config.file=/etc/promtail/config.yml
Compliance Logging
For audit trails, log these events:
Access Events
- ✅ All successful logins (200 responses to /login endpoints)
- ✅ Failed login attempts (401/403 responses)
- ✅ File downloads (GET requests with large response sizes)
- ✅ File uploads (POST/PUT requests)
- ✅ API calls (requests to /api/ paths)
Security Events
- ✅ SSL certificate renewals
- ✅ Configuration changes in NPM
- ✅ Rate limit violations
- ✅ Blocked IPs (403 responses)
Monitoring Events
- ✅ Service downtime (502/503 responses)
- ✅ Slow responses (response time tracking)
- ✅ High traffic patterns
Alerting Setup
Create alerts for critical events:
Using Uptime Kuma
Configure HTTP(s) monitors in Uptime Kuma:
- Monitor each public service
- Alert on downtime
- Track response times
Using Custom Scripts
Example: Alert on failed logins:
#!/bin/bash
# /home/jpmschweitzer/scripts/monitor-failed-logins.sh
THRESHOLD=10
LOG_FILE="/home/jpmschweitzer/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log"
# Count 401s in last 5 minutes
RECENT_FAILURES=$(find ~/docker-data/nginx-proxy-manager/data/logs/ -name "proxy-host-*.log" -mmin -5 -exec grep -c " 401 " {} + | awk '{s+=$1} END {print s}')
if [ "$RECENT_FAILURES" -gt "$THRESHOLD" ]; then
echo "ALERT: $RECENT_FAILURES failed login attempts in last 5 minutes" | \
mail -s "Security Alert: High Failed Login Rate" admin@schweitz.net
fi
Run via cron:
*/5 * * * * /home/jpmschweitzer/scripts/monitor-failed-logins.sh
Performance Monitoring
Track service performance via logs:
Response Time Analysis
# Extract response times (if configured in NPM)
grep "upstream_response_time" ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
awk '{print $NF}' | sort -n | tail -20
Bandwidth Usage
# Sum bytes sent per service
awk '{sum+=$10} END {print "Total bytes: " sum " (" sum/1024/1024 " MB)"}' \
~/docker-data/nginx-proxy-manager/data/logs/proxy-host-media*.log
Most Accessed Endpoints
# Top 10 requested paths
awk '{print $7}' ~/docker-data/nginx-proxy-manager/data/logs/proxy-host-*.log | \
sort | uniq -c | sort -nr | head -10
Best Practices
Regular Log Review
- ✅ Check NPM logs weekly for suspicious activity
- ✅ Review SSL certificate status monthly
- ✅ Archive logs older than 30 days
- ✅ Monitor for unusual traffic patterns
Retention Policy
- Active logs: 30 days (on SSD)
- Compressed archives: 1 year (on HDD /mnt/media/backups/logs/)
- Long-term storage: Ship to external service if needed
Privacy Considerations
- ⚠️ Logs contain IP addresses (PII in EU)
- ⚠️ Don't log full request bodies (may contain passwords)
- ⚠️ Rotate/delete old logs per privacy policy
- ⚠️ Secure log access (only admins via VPN)
Summary:
- All external access logged in NPM
- Logs accessible via UI, Docker, or files
- Use for security monitoring and audit trails
- Set up alerts for critical events
- Regular log review and rotation