A real-time NetFlow visualization tool that displays network traffic on an interactive world map. Built with Go and designed for use with firewalls and routers that support NetFlow v9.
NetFlowMap was developed entirely with AI as an exploratory project. There might be dragons. 🐉
- Real-time Visualization: Watch network flows appear on a world map as they happen via WebSocket
- Multiple Sources: Configure and monitor multiple NetFlow sources simultaneously
- GeoIP Enrichment: Automatic IP geolocation using DB-IP Lite database
- Traffic Analysis:
- Top 100 flows sorted by bandwidth
- Aggregated traffic per remote IP
- Min./Max. Traffic filter slider to focus on large/small transfers
- Sampling Support: Automatic detection of NetFlow sampling with optional extrapolation
- FortiGate Integration: Optional integration to resolve IP addresses to FortiGate address objects
- Traffic Filtering: Filter by direction (inbound/outbound), IP addresses, countries, or text search
- Authentication: Optional authentication with local users or OpenID Connect (OIDC)
- Role-based Access: Admin, User, and Anonymous roles with different visibility levels
- Admin Health Dashboard: Real-time system statistics including memory usage, goroutines, and flow metrics
- Responsive UI: Works on desktop and mobile browsers
- Docker (recommended) or Go 1.21+
- A NetFlow v9 source (e.g., FortiGate, OPNsense, Cisco router, etc.)
The easiest way to run NetFlowMap:
-
Download example configuration
curl -O https://raw.githubusercontent.com/RiskIdent/NetFlowMap/main/configs/config.example.yml mv config.example.yml config.yml # Edit config.yml with your settings -
Run the container
docker run -d \ -p 8080:8080 \ -p 2055:2055/udp \ -v $(pwd)/config.yml:/app/config.yml:ro \ ghcr.io/riskident/netflowmap:latest -
Access the web interface
Open http://localhost:8080 in your browser.
-
Download the required files
curl -O https://raw.githubusercontent.com/RiskIdent/NetFlowMap/main/docker-compose.yml curl -O https://raw.githubusercontent.com/RiskIdent/NetFlowMap/main/configs/config.example.yml mv config.example.yml config.yml # Edit config.yml with your settings -
Start with Docker Compose
docker compose up -d
-
Access the web interface
Open http://localhost:8080 in your browser.
| Port | Protocol | Description |
|---|---|---|
| 8080 | TCP | Web UI |
| 2055 | UDP | NetFlow collector |
| Path | Description |
|---|---|
config.yml |
Your configuration (required) |
users.yml |
Local user credentials (optional) |
netflowmap-data |
GeoIP database storage (persistent) |
To change ports, edit docker-compose.yml:
ports:
- "8080:8080" # Change left side for different host port
- "9995:2055/udp" # Example: Use port 9995 for NetFlow-
Clone the repository
git clone https://github.com/RiskIdent/NetFlowMap.git cd NetFlowMap -
Create configuration
cp configs/config.example.yml config.yml
-
Build and run
go build -o netflowmap ./cmd/netflowmap ./netflowmap
-
Access the web interface
Open http://localhost:8080 in your browser.
See configs/config.example.yml for a complete configuration example.
Each source represents a device sending NetFlow data to NetFlowMap:
sources:
- id: "fw-main"
name: "Main Firewall"
source_ip: "192.168.1.1" # IP address of the NetFlow exporter
latitude: 52.52 # Geographic location for map display
longitude: 13.405flows:
# How long flows remain visible after last update (seconds)
display_timeout_seconds: 60
# Maximum number of flows to display (sorted by bandwidth)
max_display_flows: 100Configure the log level in config.yml:
log_level: info # Options: trace, debug, info, warning, error| Level | Description |
|---|---|
trace |
Very verbose, includes per-flow details |
debug |
Debug information, filter operations |
info |
Normal operation messages (default) |
warning |
Warnings only |
error |
Errors only |
If your NetFlow exporter uses packet sampling (e.g., 1:100), NetFlowMap can detect this automatically via NetFlow Options Templates. As a fallback, you can configure it manually:
sources:
- id: "fw-main"
name: "Main Firewall"
source_ip: "192.168.1.1"
latitude: 52.52
longitude: 13.405
sampling_interval: 100 # 1:100 samplingWhen sampling is detected, an "Extrapolate sampled data" toggle appears in the UI to estimate real traffic values.
Resolve remote IP addresses to FortiGate address object names:
sources:
- id: "fw-main"
name: "Main Firewall"
source_ip: "192.168.1.1"
latitude: 52.52
longitude: 13.405
fortigate:
host: "https://192.168.1.1"
token: "your-api-token"
verify_ssl: falseNetFlowMap supports optional authentication with role-based access control.
| Role | Access Level |
|---|---|
| Admin | Full access to all data including private IPs |
| User | Access to public IPs only, private IPs are masked |
| Anonymous | Can view the map but all IP addresses are hidden |
-
Generate a password hash
With Docker Compose:
docker compose run --rm netflowmap --hash-password
With local binary:
./netflowmap --hash-password
You will be prompted to enter and confirm your password (input is hidden).
-
Create users.yml
cp configs/users.example.yml users.yml
-
Add users to users.yml
users: - username: admin role: admin password_hash: "$2a$10$..." - username: viewer role: user password_hash: "$2a$10$..."
-
Enable authentication in config.yml
auth: enabled: true session_secret: "your-secret-key-min-16-chars" session_duration: 12h local: enabled: true users_file: "users.yml"
Connect to an OpenID Connect provider (Keycloak, Authentik, etc.):
auth:
enabled: true
session_secret: "your-secret-key-min-16-chars"
session_duration: 12h
oidc:
enabled: true
issuer_url: "https://auth.example.com/realms/main"
client_id: "netflowmap"
client_secret: "your-client-secret"
redirect_url: "http://localhost:8080/auth/callback"
admin_users:
- "admin@example.com"
- "kai"- All OIDC-authenticated users receive the User role by default
- Users listed in
admin_usersreceive the Admin role
Use the "Min. Traffic" slider to filter out small connections and focus on large data transfers. The slider supports values from 0 (disabled) up to 1 GB.
- All: Show both inbound and outbound flows
- Inbound: Show only traffic coming into your network (red lines)
- Outbound: Show only traffic leaving your network (blue lines)
Search for flows by:
- IP address
- Country or city name
- Protocol (TCP, UDP, ICMP)
- FortiGate address object name
Click on a remote IP marker (green dot) to see:
- Traffic summary (total bytes, inbound/outbound split)
- Organization and ASN information
- All active connections to that IP
Click the "Health" button in the header to view:
- NetFlowMap version and uptime
- Connected clients and flow statistics
- Per-source flow counts and address objects
- Go runtime metrics (memory, goroutines, GC)
- System CPU and memory usage
The dashboard auto-refreshes every 10 seconds.
NetFlowMap/
├── cmd/netflowmap/ # Application entry point
├── internal/
│ ├── auth/ # Authentication (local + OIDC)
│ ├── config/ # Configuration parsing
│ ├── flowstore/ # In-memory flow storage
│ ├── fortigate/ # FortiGate API client
│ ├── geoip/ # GeoIP database management
│ ├── logging/ # Structured logging
│ ├── netflow/ # NetFlow v9 collector & parser
│ └── web/ # HTTP server, handlers, WebSocket
├── web/
│ ├── static/ # CSS, JavaScript
│ └── templates/ # HTML templates
├── configs/ # Example configurations
└── data/ # GeoIP database files
- Check that your firewall is sending NetFlow to the correct IP and port (default: 2055/UDP)
- Verify the
source_ipin your config matches the IP your firewall uses to send NetFlow - Check the logs:
./netflowmapordocker compose logs -f
- Only flows between public and private IPs are displayed
- Private-to-private or public-to-public flows are not shown on the map
- Check the "flows" counter in the header to see if flows are being received
- NetFlowMap displays the top 100 flows sorted by bandwidth
- Use the "Min. Traffic" slider to filter out small connections
- Ensure your NetFlow active/inactive timeouts are configured appropriately
MIT License - see LICENSE file for details.
For information about third-party components and their licenses, see LicenseInfo.md.
Contributions are welcome! Please open an issue or submit a pull request.
