A real-time security and traffic dashboard for Nginx Proxy Manager. Reads NPM access logs, geolocates source IPs, classifies threats, and streams everything live to a modern dark-mode dashboard.
- Live Traffic Feed — scrolling table of every request with IP, method, status code, path, and threat level
- Security Alerts — dedicated panel that isolates high-threat requests (XSS, SQLi, path traversal, Log4Shell,
.envprobes, and more) - Geo Intelligence Map — world map with glowing dots plotted from real IP geolocation data
- 13 built-in threat rules covering the most common attack patterns
- WebSocket streaming — sub-second latency from log write to dashboard update
- Docker-first — single
docker compose up -d --builddeployment
| Layer | Technology |
|---|---|
| Backend | Python 3.12, FastAPI, Uvicorn, aiofiles |
| Geolocation | MaxMind GeoLite2-City (free) |
| Frontend | React 19, Vite, Tailwind CSS v4 |
| Map | d3-geo + world-atlas |
| Transport | WebSockets |
| Serving | nginx (Alpine) |
| Deployment | Docker Compose |
- Docker + Docker Compose
- A running Nginx Proxy Manager instance
- A free MaxMind account to download
GeoLite2-City.mmdb
git clone https://github.com/SpoonerBoy/Proxy-Traffic-Visualizer.git
cd Proxy-Traffic-Visualizercp .env.example .envEdit .env and set:
| Variable | Description | Example |
|---|---|---|
NPM_LOG_DIR |
Host path to NPM logs directory | /home/pi/nginx-proxy-manager/data/logs |
NPM_LOG_FILE |
Active access log filename | default-host_access.log |
GEOIP_DB_PATH |
Host path to GeoLite2-City.mmdb |
/opt/geoip/GeoLite2-City.mmdb |
FRONTEND_PORT |
Dashboard port | 3002 |
DEMO_MODE |
Use synthetic traffic (no NPM needed) | false |
Finding your active log file:
ls -lht /path/to/nginx-proxy-manager/data/logs/The file with the most recent timestamp and non-zero size is your active log.
docker compose up -d --buildOpen http://<your-host>:3002 — the dashboard connects automatically.
# Check both containers are running
docker compose ps
# Stream backend logs
docker logs proxy-visualizer-backend -fYou should see:
GeoIP database loaded from '/geoip/GeoLite2-City.mmdb'.
Ingest loop started (demo_mode=False).
Tailing log file '/npm-logs/default-host_access.log'.
Application startup complete.
To run without a real NPM instance (useful for testing):
DEMO_MODE=trueThis generates synthetic traffic with realistic IPs, paths, and ~15% threat events.
| Rule | Pattern | Level |
|---|---|---|
env_file_leak |
/.env, /.env.backup etc. |
HIGH |
git_exposure |
/.git/, /.svn/ |
HIGH |
wp_admin_probe |
/wp-admin, /wp-login.php |
HIGH |
directory_traversal |
../, %2f.. |
HIGH |
sql_injection |
UNION SELECT, OR 1=1 |
HIGH |
xss_probe |
<script>, javascript: |
HIGH |
log4shell |
${jndi:ldap://} |
HIGH |
shell_injection |
;, |, `, && in URL |
HIGH |
aws_metadata |
169.254.169.254 |
HIGH |
config_file_probe |
phpinfo.php, web.config |
HIGH |
remote_file_include |
?file=http:// |
HIGH |
common_shell_backdoor |
c99.php, webshell |
HIGH |
admin_panel_probe |
/admin, /phpmyadmin |
MEDIUM |
scanner_ua |
Nikto, sqlmap, Nmap UA | MEDIUM |
backup_file_probe |
.bak, .old, .swp |
MEDIUM |
excessive_404 |
HTTP 404 responses | MEDIUM |
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Health check and server stats |
/api/history |
GET | Last N buffered log entries |
/ws/traffic |
WebSocket | Live stream of enriched log events |
{
"id": "monotonic-ns-timestamp",
"ip": "185.220.101.47",
"timestamp": "2026-05-23T19:15:30+00:00",
"method": "GET",
"path": "/.env.backup",
"status": 404,
"bytes_sent": 150,
"user_agent": "curl/7.88",
"lat": 52.6171,
"lon": 13.1207,
"country": "DE",
"country_name": "Germany",
"city": "Brandenburg an der Havel",
"threat_level": "high",
"matched_rule": "env_file_leak",
"threat_description": "Attempt to read .env or environment config",
"tags": ["env_file_leak"]
}Proxy-Traffic-Visualizer/
├── backend/
│ ├── config.py # Pydantic-Settings configuration
│ ├── log_parser.py # Nginx combined-log-format regex parser
│ ├── geoip_lookup.py # MaxMind GeoIP2 wrapper
│ ├── threat_analyzer.py # Rule-based security classification engine
│ ├── log_reader.py # Async file tailer + demo traffic generator
│ └── main.py # FastAPI app, WebSocket fan-out, REST endpoints
├── frontend/
│ └── src/
│ ├── components/
│ │ ├── Header.jsx # Stats bar + connection indicator
│ │ ├── TrafficFeed.jsx # Live scrolling request table
│ │ ├── SecurityAlerts.jsx # High-threat event panel
│ │ ├── GeoMap.jsx # d3-geo world map with live dots
│ │ └── StatusBadge.jsx # Threat / method / status badges
│ ├── hooks/
│ │ └── useTrafficStream.js # WebSocket client + state management
│ └── App.jsx # Root layout
├── Dockerfile.backend
├── Dockerfile.frontend
├── docker-compose.yml
├── nginx.conf # nginx reverse proxy config (serves + proxies /ws/)
├── requirements.txt
└── .env.example
After pulling new code, rebuild both images:
git pull
docker compose up -d --buildMIT
