How to install Proxy Traffic Visualizer (Nginx Proxy Manager)
Audience: homelab users running Nginx Proxy Manager (NPM)
Time: ~30–45 minutes (first time, including MaxMind signup)
Goal: run Proxy Traffic Visualizer in Docker so you get a live dashboard of NPM access logs, geolocation, and threat alerts.
What you’ll build
- A Docker Compose stack (backend + frontend) that tails your NPM access log
- A dark-mode dashboard on port
3002(default) with live traffic, security alerts, and a geo map - WebSocket streaming from log line → enriched event on screen (sub-second when logs are active)
Prerequisites
- Docker and Docker Compose on the same host that can read NPM log files (see Portainer install guide or Docker on Ubuntu if you still need those)
- A running Nginx Proxy Manager instance writing access logs to disk
- A free MaxMind account to download GeoLite2-City (
GeoLite2-City.mmdb) - Network: dashboard reachable only on your LAN/VLAN unless you deliberately expose it (see security notes)
Conventions used in this guide
- Commands assume a Linux shell on the Docker host.
- Replace values in
<ANGLE_BRACKETS>. - Paths differ per install — always confirm NPM’s log directory on your system.
Step 1 — Clone the repository
git clone https://github.com/SpoonerBoy/Proxy-Traffic-Visualizer.git
cd Proxy-Traffic-Visualizer
Verify
ls -la docker-compose.yml .env.example
Expected: both files exist in the project root.
Step 2 — Download the GeoIP database (MaxMind)
- Create a free account: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
- Download GeoLite2 City in MaxMind DB binary format (
GeoLite2-City.mmdb). - Place it on the Docker host, for example:
sudo mkdir -p /opt/geoip
sudo mv ~/Downloads/GeoLite2-City.mmdb /opt/geoip/GeoLite2-City.mmdb
sudo chmod 644 /opt/geoip/GeoLite2-City.mmdb
Verify
file /opt/geoip/GeoLite2-City.mmdb
Expected: output mentions MaxMind or data (not “ASCII text”).
MaxMind updates this file periodically — plan to refresh it every few months (see Keep it up to date).
Step 3 — Find your NPM access log path and active file
NPM usually stores logs under its data directory. Common layouts:
- Docker volume bind: something like
.../nginx-proxy-manager/data/logs - Host path you configured when you deployed NPM
List logs by recency:
ls -lht <PATH_TO_NPM>/data/logs/
Pick the access log with recent timestamps and non-zero size. The default name is often:
default-host_access.log
Note both:
- Directory →
NPM_LOG_DIR - Filename →
NPM_LOG_FILE
Verify
Generate a request through NPM (open any proxied site), then:
tail -n 3 "<PATH_TO_NPM>/data/logs/<YOUR_LOG_FILE>"
Expected: new lines in combined log format (IP - - [date] "METHOD path HTTP/x.x" status ...).
Step 4 — Configure environment
cp .env.example .env
nano .env
Set at minimum:
| Variable | What to set | 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 |
Port for the dashboard UI | 3002 |
DEMO_MODE |
false for real NPM logs |
false |
Optional tuning (defaults are fine to start):
| Variable | Purpose |
|---|---|
LOG_POLL_INTERVAL |
Seconds between polls when idle (0.25 default) |
HISTORY_BUFFER_SIZE |
Events replayed to new WebSocket clients (200 default) |
Verify
grep -E '^(NPM_LOG_DIR|NPM_LOG_FILE|GEOIP_DB_PATH|FRONTEND_PORT|DEMO_MODE)=' .env
test -r "$(grep '^GEOIP_DB_PATH=' .env | cut -d= -f2-)" && echo "GeoIP file readable"
test -d "$(grep '^NPM_LOG_DIR=' .env | cut -d= -f2-)" && echo "NPM log dir exists"
Step 5 — Build and start with Docker Compose
docker compose up -d --build
Verify containers
docker compose ps
Expected: proxy-visualizer-backend and proxy-visualizer-frontend Up.
Verify backend is tailing logs
docker logs proxy-visualizer-backend --tail 30
Expected lines similar to:
GeoIP database loaded from '/geoip/GeoLite2-City.mmdb'.
Ingest loop started (demo_mode=False).
Tailing log file '/npm-logs/<your-log-file>'.
Application startup complete.
Step 6 — Open the dashboard
In a browser on your LAN:
http://<docker-host-ip>:3002
Generate traffic through NPM (visit a proxied hostname). You should see:
- Rows in the live traffic feed (IP, method, status, path)
- Threat level badges when paths match built-in rules (e.g.
/.env, scanners) - Map dots when GeoIP resolves (some IPs may have no city data)
The UI connects over WebSocket automatically; no separate API key is required on the default install.
Optional — Demo mode (no NPM required)
Useful to preview the UI before wiring logs:
# In .env
DEMO_MODE=true
Then rebuild:
docker compose up -d --build
Demo mode generates synthetic traffic (~15% flagged as threats). Switch back to DEMO_MODE=false for production.
What the app detects (summary)
Proxy Traffic Visualizer applies rule-based classification on each log line (not a WAF). Examples:
| Level | Examples |
|---|---|
| High | .env probes, path traversal, SQLi/XSS patterns, Log4Shell (${jndi:), shell injection in URLs |
| Medium | Admin panel probes, scanner user-agents, backup file probes, excessive 404s |
Full rule list and API details: project README.
Official resources (verify upstream changes)
- Project repo: https://github.com/SpoonerBoy/Proxy-Traffic-Visualizer
- Nginx Proxy Manager: https://nginxproxymanager.com/
- MaxMind GeoLite2: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
Keep it up to date
Application (after git pull in the repo):
git pull
docker compose up -d --build
GeoLite2 database (periodic):
- Download the newest
GeoLite2-City.mmdbfrom MaxMind. - Replace the file at
GEOIP_DB_PATH. - Restart the backend:
docker compose restart backend
Security notes (recommended)
- Treat this as observability, not a replacement for a WAF or IDS.
- Do not publish port
3002to the public Internet without TLS, authentication, and rate limiting (put it behind NPM or your reverse proxy on a management VLAN). - Mount NPM logs read-only (Compose already uses
:roon the log volume). - Restrict who can reach the Docker host’s published port (firewall / VLAN).
Troubleshooting
-
Dashboard loads but stays empty
- Confirm
NPM_LOG_FILEis the active log (re-runls -lhton the logs folder). - Hit a proxied site, then
tailthe log file on the host — if nothing new appears, NPM may be logging elsewhere. - Check
docker logs proxy-visualizer-backendfor “Tailing log file” path.
- Confirm
-
GeoIP / map always empty
- Verify
GEOIP_DB_PATHon the host and that the container mount succeeded. - Some IPs have no city record; country-level may still apply.
- Verify
-
Permission denied on log directory
- Ensure the user running Docker can read
NPM_LOG_DIR(add todockergroup or adjust directory permissions carefully).
- Ensure the user running Docker can read
-
Port 3002 already in use
- Change
FRONTEND_PORTin.envand rundocker compose up -d.
- Change
-
Want to test without NPM
- Set
DEMO_MODE=true, rebuild, confirm synthetic events appear.
- Set
Next steps
- Put the dashboard behind Nginx Proxy Manager with SSO or basic auth on an internal hostname.
- Pair with Pi-hole or firewall logs for broader visibility (Pi-hole Docker guide).
- Open an issue or PR on GitHub if you deploy on plain Nginx (non-NPM) — the parser targets standard combined access logs.