A real-time router / firewall traffic visualizer for the terminal, inspired by btop.
Where a host monitor shows your machine's own traffic, fwtop is built for the box that sits between networks — an edge router or firewall. It reads kernel state directly (near-zero overhead, no packet capture) to show per-interface throughput (split into WAN/LAN zones), the live connection-tracking table (including NAT), and firewall rule counters with a smoke→fire drops heatmap that surfaces spikes at a glance.
The one exception is the opt-in Capture tab, an interactive tcpdump: enter a port/protocol filter and watch just that traffic's WAN/LAN throughput and per-interface breakdown, with a live packet stream. Capture only runs while that tab is open, so the rest of the app keeps its zero-capture footprint.
Built with Textual, Plotext, and psutil.
The Overview tab — WAN/LAN throughput charts and the zone-grouped Interfaces table (shown with --demo data):
The Drops tab — a smoke→fire heatmap, one ticking row per firewall drop/reject rule:
The Connections tab — WAN and LAN sub-tabs, with both endpoints reverse-DNS resolved:
- Per-interface flows — RX/TX rates, cumulative totals, and error/drop counters for every interface, so forwarded (through) traffic is visible per link
- Conntrack view — live netfilter connection table: protocol & TCP-state breakdown, NAT translations, and table-capacity gauge
- Firewall drops — nftables (or iptables) rule counters with per-rule packet/byte rates; drops and rejects sorted to the top
- Chain drill-down — pick any
table/chainand inspect every rule's counter in ruleset order, likenft list chain/iptables -L <chain> - Interactive capture — a tcpdump-style tab: filter by port(s) and protocol, then watch that traffic's WAN/LAN throughput charts, per-interface breakdown, and a live packet stream (opt-in; runs only while the tab is open)
- Drops heatmap — a ticking grid, one row per drop/reject rule, colored smoke→fire (gray low, red high) so spikes light up at a glance
- Zone categorization — label each interface WAN / WAN-Tunnel / LAN / LAN-Tunnel / other; saved to a config file and editable at runtime
- Side-by-side rolling 60-second throughput charts — WAN in red shades, LAN in green shades, each overlaying its physical link and its tunnel (WireGuard/GRE/IPsec) traffic, braille-dot plotted
- Connections view — WAN and LAN sub-tabs splitting NAT'd/public flows from internal ones; both endpoints reverse-DNS resolved
- CPU usage panel (user / system / total / fwtop process)
- Optional reverse-DNS resolution of IPs, toggleable at runtime
--demomode with synthetic data — runs anywhere, no kernel access or root needed- btop-inspired dark theme with rounded borders and color-coded panels
- Reads kernel counters only: no
libpcap, no per-packet overhead on busy routers
All sources are read-only and Linux-native. Each degrades gracefully: if a facility is missing, its panel shows why instead of crashing.
| View | Source | Notes |
|---|---|---|
| Interfaces | /proc/net/dev |
Always present on Linux |
| Conntrack | /proc/net/nf_conntrack |
Requires the nf_conntrack module (loaded on any NAT/stateful box). Per-flow byte/packet counts need nf_conntrack_acct |
| Firewall | nft -j list ruleset, fallback iptables -L -v -n -x |
Per-rule counters require a counter statement on the rule (nftables) |
| Capture | tcpdump (one process per interface) |
Opt-in; runs only while the Capture tab is open. Needs the tcpdump binary. The only source that captures packets |
Download the latest binary for your platform from GitHub Releases:
| Platform | Architecture | Download |
|---|---|---|
| Linux | x86_64 | fwtop-linux-x86_64 |
| Linux | ARM64 | fwtop-linux-arm64 |
| macOS | ARM64 | fwtop-macos-arm64 (demo/dev only) |
chmod +x fwtop-*
sudo ./fwtop-linux-x86_64git clone https://github.com/ibehren1/fwtop.git
cd fwtop
uv sync
sudo uv run fwtop # live
uv run fwtop --demo # synthetic data, no rootusage: fwtop [-h] [-v] [-n INTERVAL] [-r] [--demo] [-c PATH] [--dns SERVER]
options:
-h, --help show this help message and exit
-v, --version Show the version and exit
-n, --interval Screen refresh interval in seconds (default: 1.0)
-r, --resolve Reverse-DNS resolve IPs, showing hostnames when available
--demo Run with synthetic data (no kernel access / root needed)
-c, --config PATH Config file for interface zone assignments
(default: $XDG_CONFIG_HOME/fwtop/config.json)
--dns SERVER DNS server for reverse lookups (repeatable); overrides
the servers auto-discovered from the host
# Live monitoring on a Linux router (requires root for conntrack & nft)
sudo fwtop
# Try it anywhere with synthetic data
fwtop --demo
# Resolve IPs to hostnames, refresh twice a second
sudo fwtop -r -n 0.5| Key | Action |
|---|---|
q |
Quit |
p |
Pause / resume display |
r |
Toggle reverse-DNS resolution of IPs |
n |
Cycle refresh interval (0.5 → 1 → 2 → 5s) |
z |
Assign interface zones (WAN / WAN-Tunnel / LAN / LAN-Tunnel / other) |
1 |
Overview tab |
2 |
Connections tab |
3 |
Firewall tab |
4 |
Chains tab |
5 |
Capture tab |
6 |
Drops tab |
w |
Connections tab, WAN sub-tab |
l |
Connections tab, LAN sub-tab |
f |
Set capture filter (opens the Capture tab and prompts for ports/protocol) |
s |
Stop the running capture |
A two-column layout. The left column stacks Summary (aggregate throughput, tracked connections, drop rate, uptime), CPU, and a Conntrack breakdown (capacity bar, per-protocol bars, per-state bars). The right column shows two side-by-side rolling throughput charts — WAN (red shades) and LAN (green shades) — above the Interfaces table.
The throughput charts each plot up to four lines: the physical link's down/up plus its tunnel's down/up.
The Interfaces table groups rows into zone sections (WAN → WAN-Tunnel → LAN → LAN-Tunnel → other), each under a heading; rows keep a stable order and update their values in place rather than re-sorting each tick. The table expands and contracts with the window.
The heaviest conntrack flows, one row per connection: protocol, original source → destination, the NAT reply address when translation is in effect, TCP state, and per-flow packet/byte volume.
Flows are split into two sub-tabs — WAN and LAN (keys w / l).
Conntrack entries carry no interface label, so the split is inferred: the
WAN sub-tab holds flows that were NAT'd out or touch a public address (WAN
and WAN-Tunnel traffic), and the LAN sub-tab holds purely internal flows
(private endpoint to private endpoint, no NAT). Each sub-tab label shows its
live flow count.
With reverse-DNS resolution enabled (the -r flag or the r key), both the
source and destination addresses — and the NAT reply address — are resolved to
hostnames when a PTR record exists, falling back to the raw IP otherwise.
All resolution is done in-process with dnspython,
querying the host's upstream nameservers directly — the system resolver
(getaddrinfo/NSS//etc/hosts) is never consulted. The upstream servers are
auto-discovered, preferring /run/systemd/resolve/resolv.conf (the real
upstreams) over /etc/resolv.conf (which on systemd-resolved hosts is just
the 127.0.0.53 stub); loopback/stub addresses are dropped. This is why
private (RFC1918) addresses resolve here when the host's own reverse lookups
would not. Override the servers with --dns SERVER (repeatable).
Results are cached in-process. A successful name is kept for the session; an authoritative "no PTR" is cached for a few minutes; a transient failure (timeout, unreachable server) is retried shortly after, so a momentary glitch never pins a resolvable address to its raw IP.
Every firewall rule counter, with drops and rejects sorted to the top and color-coded by verdict. Shows per-rule packet/byte rates so a spike in dropped traffic (a scan, a brute-force attempt, a misconfig) jumps out.
A chain-by-chain drill-down. A picker on the left lists every table/chain
seen in the ruleset (with its rule count, and highlighted red when the chain is
actively dropping); selecting one shows that chain's rules in ruleset order
on the right — rule position, verdict, live packet/byte rates, and cumulative
totals. Where the Firewall tab flattens everything drops-first, this reads like
nft list chain / iptables -L <chain> for inspecting one chain end to end.
An interactive tcpdump. Press f to enter a filter — a list of ports
(e.g. 80, 443, 53; blank for all), a protocol (tcp / udp / icmp /
any), source and destination addresses (blank for any), and an interface
(a specific link, or all interfaces — the default) — and fwtop starts
capturing just that traffic. The filter is remembered, so reopening it with
f (even after leaving and returning to the tab) shows your last values.
Laid out like the
Overview tab, three stacked panels update together: two rolling charts split
the matched flow's throughput into WAN (red) and LAN (green); a table
breaks the same traffic down per interface (packet/byte rates and totals);
and a live packet list scrolls the matching packets in tcpdump's own
format (HH:MM:SS.ffffff iface IP src.port > dst.port: …). s stops the
capture.
This is the only part of fwtop that captures packets, so it is strictly opt-in:
it shells out to the system tcpdump (one process per interface) and runs
only while the Capture tab is on screen — leaving the tab stops it, and no
tcpdump lingers. It therefore needs the tcpdump binary installed and root
(as the rest of fwtop already requires). Throughput here is derived from
captured frame lengths, so it approximates rather than exactly matches the
Overview tab's kernel interface counters. With --demo the tab is fully
exercisable with synthetic packets, no capture privileges needed.
A full-screen ticking heatmap with one row per firewall drop/reject rule and one column per refresh tick, scrolling left as time advances. Each cell's color runs smoke→fire — faint gray at low drop rates, warming through embers to bright red at the peak — scaled to the busiest cell on screen, so a spike in dropped traffic (a scan, a brute-force burst) lights up at a glance. The newest tick is the right-hand column; a footer shows the smoke→fire legend and the current peak rate the color scale maps to. The grid expands and contracts with the window.
Each interface is categorized into one of five zones:
| Zone | Chart | Typical interfaces |
|---|---|---|
| WAN | red (bright) | uplink: eth0, ppp0, *wan* |
| WAN-Tunnel | red (orange) | WireGuard/IPsec/GRE over the WAN: wg0, tun0, ipsec0 |
| LAN | green (bright) | local: br-lan, vlan*, *lan* |
| LAN-Tunnel | green (lime) | tunnels bridged into the LAN: gre-lan, wg-lan |
| other | neither | loopback, management, unclassified |
WAN and WAN-Tunnel feed the red chart; LAN and LAN-Tunnel feed the green
chart. other is excluded from both.
On first run, zones are guessed from interface names (tunnel hints like wg,
tun, tap, gre, ipsec take priority and resolve to a *-Tunnel zone —
WAN-side unless the name also looks LAN-side). Press z to open the
assignment screen and cycle any interface through WAN → WAN-Tunnel → LAN →
LAN-Tunnel → other; the choice is saved immediately to the config file and
applied on the next refresh.
The config is a small JSON file (default
$XDG_CONFIG_HOME/fwtop/config.json, override with -c):
{
"zones": {
"eth0": "wan",
"wg0": "wan_tunnel",
"br-lan": "lan",
"gre-lan": "lan_tunnel"
}
}uv sync --dev
uv run pyinstaller fwtop.spec --clean --noconfirm
# Binary at dist/fwtopThe GitHub Actions workflow (.github/workflows/build.yml) builds binaries
for Linux x86_64, Linux ARM64, and macOS ARM64. Push a tag to cut a release:
make release VERSION=0.1.0
# or: git tag v0.1.0 && git push origin v0.1.0This builds all binaries, generates SHA256 checksums, and creates a GitHub Release with the artifacts attached.
- Python >= 3.10
- Linux (for live data); any platform for
--demo - Root privileges to read conntrack and the firewall ruleset
- The
tcpdumpbinary, only if you use the Capture tab (optional) - A terminal emulator with 256-color support
Works out of the box with sudo. The conntrack panel needs the
nf_conntrack module loaded (automatic on any box doing NAT or stateful
filtering). For per-flow byte/packet accounting:
sysctl -w net.netfilter.nf_conntrack_acct=1For per-rule firewall counters under nftables, add a counter statement to
the rules you want to track.
The kernel sources are Linux-specific, so live mode has nothing to read. Use
fwtop --demo to explore the interface with synthetic data — this is the
intended path for development on non-Linux machines.
MIT — Copyright © 2026 Isaac B. Behrens. All rights reserved.