Network
Alt-N or Command → Network shows what this machine does on the network: the ports that are open and who can reach them, the processes that are talking and how fast, every connection, and the network interfaces. It comes from the bundled System plugin, like the activity monitor. The list takes the active pane, and the other pane shows the selected row in detail with charts of the last 60 seconds. Borg samples once a second while the screen is open and stops when you close it. Rates appear from the second sample on, and each one is the mean of the last 3 seconds, so a list sorted by traffic holds still long enough to read.
Press 1 to 4, or click a tab, to switch pages:
- 1 Ports lists every port a process listens on, sorted by port. Reach says who can connect: All networks for a port open on every address, the address for a port bound to specific ones, and This machine for a port only programs on this machine can reach. Ports open to all networks stand out, since other machines can find them. Conns counts the connections that came in through the port, and the details list them with their traffic.
- 2 Processes lists the processes that use the network, busiest first, with their download and upload rates and connection counts. The details add what the process moved since the screen opened and every connection it holds.
- 3 Connections lists every connection with its process, the other end, and its rates. A wide pane adds the protocol and the TCP state. The details show both ends, the host name, the service, and the bytes received and sent.
- 4 Interfaces lists the network interfaces with their kind (Wi-Fi, Ethernet, Loopback, Tunnel, Bridge or Virtual), first address, and rates. ● marks an interface that is up. The details add every address, the MTU, the bytes and packets the interface received and sent so far, and its errors.
The footer shows the machine’s total download and upload rate over every interface except loopback. Borg looks up the names of remote addresses in the background and shows a name in place of the address once it is known.
| Key | Action |
|---|---|
| 1 / 2 / 3 / 4 | Show Ports / Processes / Connections / Interfaces |
/ |
Filter the rows; ↑↓ still move, Enter keeps the filter, Esc clears it |
| T / N / P / C | Sort by traffic / name / port / connections; press again to reverse |
| Enter | Show the selected row’s process in the activity monitor |
| K | Show the process with Signal or priority open |
| R | Capture the selected row’s traffic, or stop the capture that runs |
| Ctrl-C | Copy the port’s address, the PID, the other end, or the interface’s address |
| Tab | Move between the list and the details |
| Space | Pause or resume sampling |
| Alt-W | Maximize the focused pane |
| Esc | Go back to the list, then close the screen or return to the activity monitor |
P sorts the pages that have a port, and C the pages that count connections. Click a column heading to sort by it, and double-click a row to show its process. Enter on a row without a process moves into the details. A row names a process and no program file, so Alt-O here tells you to press Enter and then Alt-O in the activity monitor. The activity monitor that Enter and K open has the process selected, so its memory, signals and priority are one key away (see Signals and priority). Esc there brings you back to the network screen on the row you left, with its charts.
It works the other way too. Alt-N in the activity monitor opens this screen on the selected process’s row of 2 Processes, with its traffic and connections in the details, and Esc goes back to the monitor. A process without connections or open ports gets a notice that says so.
Filtering
Section titled “Filtering”The filter keeps the rows that match every word you type. A plain word matches the process, PID,
port, service, address, host name, state, protocol, or a label the row shows, so postgres,
5432, https and example.com all work. A word with a field name looks in that field only:
| Word | Keeps |
|---|---|
port:443, port:ssh |
Rows on that port number or service |
proc:node, process:node |
Rows whose process name contains the text |
pid:4100 |
Rows of that PID |
host:example |
Rows whose address or host name contains the text; a whole address keeps that address alone |
state:listen, state:time |
TCP rows in a state that contains the text |
proto:udp, proto:tcp |
Rows of that protocol |
if:en0, interface:en0 |
Interfaces whose name contains the text |
Put ! before a word to drop the rows it matches: !state:time_wait hides closing connections,
and !proc:browser hides one program. The filter applies to every page, and while you type it
says how many of the page’s rows it keeps.
Capturing traffic
Section titled “Capturing traffic”Press R to record the packets behind the selected row with tcpdump, which macOS ships and
Linux installs from the tcpdump package. The Capture traffic dialog starts from that row:
- Traffic is what tcpdump keeps. On 1 Ports that is the port and the connections that came in through it, and on 3 Connections the one connection in both directions. On 2 Processes it is All traffic, shown for that process: tcpdump cannot pick packets by process, so Borg records the whole interface and opens the result on the process. All traffic on the interface is always there as the second choice.
- Interface starts on the one that carries the row’s address, or else the busiest one.
- Duration is 5, 10, 30 or 60 seconds, or Until stopped.
- Keep starts on First 1 MiB of each connection: each conversation’s packets stay whole until they carried 1 MiB of data, and after that Borg saves only their headers. Short requests stay complete while a long download or a video call leaves the file small. Whole packets saves everything, and Headers, first 128 bytes of each packet saves the start of every packet for the smallest files. Borg trims the packets as it writes the file, which stays a normal pcap or pcapng, and each trimmed packet still records its length on the wire.
- TLS keys is Off, Set SSLKEYLOGFILE in Borg’s shell to record the TLS keys of the programs you start while the capture runs, or Proxy HTTPS through Borg with a temporary CA for programs that do not write their keys (see Decrypting TLS).
- Run as shows where Borg can ask the system for an administrator’s password. You runs
tcpdump as you, and Administrator runs it as root once you give your password in the
system’s dialog. On macOS the choice reads Administrator, with each packet’s process:
tcpdump then listens through macOS’s
pktap, which tags every packet with the process that sent or received it.
↑↓ pick a field, ←→ or Space change it, Enter starts the capture and Esc cancels. With the mouse,
click a field to pick it and click again to change it. While the capture runs, the footer counts its
packets and bytes and the seconds left, and R stops it early. A capture also stops at 200,000
packets or 256 MiB, and when the disk fills up, keeping what it saved so far. To leave room for the
rest of the system, set keep-free-mib in the [network] section of the config file: a capture
then does not start when the disk that holds the folder has less than that many MiB free, and it
stops once the free space drops below that. After its first 3 seconds, the footer says how soon the
first of these limits stops the capture at its rate so far, such as about 30 s to 256 MiB or
about 2 min to a full disk, when that comes before the capture’s own end and within the hour.
When a limit stopped the capture, a notice says which one. If you close the screen first, the
capture stops, and a notice says where Borg saved it.
Borg saves each capture as capture-<date>-<time>.pcap in the folder of the panel the network
screen covers, readable only by you, so Wireshark and other tools can open it later. A capture
that tags each packet with its process is saved as capture-<date>-<time>.pcapng, the format
that can carry the tags, and so is a capture that recorded TLS keys once Borg puts them inside. A
capture holds whatever the packets carried, so keep it as safe as that data. Each capture also
goes on the list in Borg’s files.
An agent connected through borg mcp can ask for a capture too, and it waits for your approval
(see What the agent can do).
When the capture ends, the traffic viewer takes the place of the live pages, with a page for each way to look at it:
- 1 Processes shows each process with the hosts it talked to, its packets and its bytes. When the packets carry their process, each packet belongs to the process that sent or received it. A process that lived for less than a second still gets its name then, and a process’s filter keeps exactly its packets. Otherwise a conversation belongs to the process whose socket held it while the capture ran, and the rest goes to Unknown process.
- 2 Hosts shows the other machines with the protocols seen. Names come from the capture’s own DNS answers and the server names TLS connections asked for, so most hosts have one.
- 3 Connections shows each conversation with its process, other end, protocol, packets and bytes. The details show both ends, the server name, the service, the bytes sent and received, and the conversation’s stream: what each side sent, in order and with retransmitted bytes counted once, as a hex dump with the bytes that print as text beside them. The stream comes in turns, each with an arrow for its direction, the bytes it holds and when it came. When the capture lacks bytes before a turn, the turn says how many are missing. When it kept only the start of the connection, the stream’s heading says how many bytes it left out after that, as the packets’ headers count them. ] and [ jump to the next and previous turn. F3 opens the whole stream in the viewer as hex rows, with its turns one after another, so you can page through it and search it there. Enter shows the conversation’s packets one by one. When Borg has the connection’s TLS keys, the stream shows what its encrypted records carried (see Decrypting TLS).
- 4 Packets shows every packet in order with its time from the start, protocol, length and a
summary such as
ClientHello example.comorA example.com → 203.0.113.10. The details show its layers (Ethernet, IP, TCP or UDP, and DNS, TLS or HTTP when Borg reads them) and its bytes as a hex dump. A dump lays its bytes out as the viewer’s hex rows do, with as many on each line as fit the pane. F3 opens the packet’s bytes in the viewer as hex rows.
| Key | Action |
|---|---|
| 1 / 2 / 3 / 4 | Show Processes / Hosts / Connections / Packets |
| Enter | Go down a level: from a process or host to its connections, from a connection to its packets, from a packet into its details |
| ] / [ | In a connection’s details, jump to the next or previous turn of its stream |
/ |
Filter the rows, as on the live pages |
| Ctrl-C | Copy the PID, the address, the other end, or the packet’s bytes in hex |
| F3 | Open the connection’s stream or the packet’s bytes in the viewer as hex rows |
| Tab | Move between the list and the details, where ↑↓ scroll |
| Esc | Go back to the list, then up a level, then clear the filter, then return to the live screen |
The filter takes the same words as the live pages, and proto: also matches what Borg recognized
in the packets: proto:dns, proto:tls, proto:http and proto:quic. A filter applies to every
page, and Enter sets one to go down a level, which is why Esc can step back out.
F3 on a .pcap, .pcapng or .cap file in a panel opens it in the same viewer, starting on
2 Hosts. A file recorded elsewhere has no sockets to match, so its conversations show
Unknown process. A .pcapng whose packets carry their process, as Borg and
tcpdump -i pktap save them on macOS, still names every process. The viewer reads files up to
256 MiB and their first 200,000 packets.
When Borg cannot find tcpdump, the capture offers to install it. The Install tcpdump dialog
shows the exact command for the package manager Borg found: apt-get, dnf, pacman, zypper or
apk on Linux, or Homebrew’s brew on macOS and on a Linux machine with none of those. The
system’s package managers run under sudo, or doas where there is no sudo, unless Borg runs as
root. Enter types the command into Borg’s shell, where you see what it does and type your password
yourself, and Esc cancels. When the command ends, a notice says whether it worked, and Ctrl-O shows
its output. Borg looks for tcpdump again each time a capture starts, on PATH and in /usr/sbin,
/usr/bin, /sbin, /bin and Homebrew’s sbin folders, so R works as soon as the install
finishes. Without a package manager Borg knows, the capture ends with Capturing needs tcpdump,
and Borg cannot find it.
tcpdump needs access to the capture device. Borg runs it as you, so give yourself access once:
- On macOS, only root can open the
/dev/bpfdevices at first. Install Wireshark with its ChmodBPF component once: it gives the devices to anaccess_bpfgroup and adds you to it, and from then on you can capture on every interface. - On Linux, give tcpdump the capture capabilities once with
sudo setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump. Some distributions let the members of apcaporwiresharkgroup capture instead.
Without access, Borg asks Capture as administrator where the system can ask for your
password: on macOS in its own password dialog, and on Linux through pkexec, whose dialog comes
from your desktop. Choose Yes and type the password in the system’s dialog, which Borg never
sees. While the dialog is up, the footer reads Waiting for your password to capture on and
the interface’s name. Borg then runs a copy of itself as root, which checks what it was asked to
capture and starts tcpdump from the system’s own folders. The packets stream back to Borg, which
saves the file as you. R and the duration stop the capture as before, and if Borg exits, the
capture stops with it. Cancel the system’s dialog and the capture ends without a notice. To skip
the question, pick Administrator under Run as before you start.
On Linux without pkexec, Borg uses sudo when it may run tcpdump without a password. Over SSH
there is no desktop to show the dialog, so the capture ends with Borg cannot capture as
administrator and the system’s reason. When Borg itself runs as root, it never asks, and on
macOS its captures always carry each packet’s process. Where Borg cannot ask at all, the capture
ends with No permission to capture on and the interface’s name.
Decrypting TLS
Section titled “Decrypting TLS”When Borg has the keys of a TLS connection, its stream on 3 Connections shows what the encrypted records carried, such as an HTTP request and its answer, and the stream’s heading ends in decrypted.
The keys come from a TLS key log, the file that browsers, curl, Python and many other programs
write when the SSLKEYLOGFILE environment variable names one. Nothing sits between the program
and the server then, so this works for programs that pin their certificates too. For programs that
never write a key log, Borg can stand in the middle instead (see
Through Borg’s proxy).
To record them with a capture, set TLS keys to Set SSLKEYLOGFILE in Borg’s shell in the
Capture traffic dialog. When the capture starts, Borg creates capture-<date>-<time>.keylog
next to the capture, readable only by you, and types a line like this at its shell’s prompt, where
you see it run and find it later in the shell’s history:
export SSLKEYLOGFILE='/home/me/projects/capture-20260101-120000.keylog'In fish the line starts with set -gx instead. Borg then comes back to the network screen, where
the footer says TLS keys while the capture runs.
Now start the program from Borg’s command line, or press Ctrl-O and start it in the shell. It
inherits the variable and writes its keys to the file. For a program in another terminal, press
L on the network screen to copy SSLKEYLOGFILE='…' with the key log’s path, paste it there and
type the program after it. A program that was already running keeps the setting it started with,
so quit it and start it again. That goes for browsers in particular, which hand a second launch
over to the window already open. On macOS, open starts apps without the shell’s variables, so
start the app’s own program, such as /Applications/Firefox.app/Contents/MacOS/firefox.
If Borg’s shell is busy, or the command line belongs to a container or another machine, Borg types nothing and a notice says so. L still copies the line.
When the capture ends, Borg types unset SSLKEYLOGFILE at the prompt (set -e SSLKEYLOGFILE in
fish), or puts back the value Borg itself started with, and a notice names the line. If a program
you started is still running in the shell, the line waits until it ends. Borg then puts the keys
of the capture’s connections into the capture as a decryption secrets block, so a .pcap becomes
a .pcapng with the same name, which Wireshark opens decrypted. The key log stays next to it,
because programs started during the capture keep writing to it until they exit. Captures an agent
asks for do not record keys.
Programs that read SSLKEYLOGFILE include Firefox, Chrome and other Chromium browsers, most curl
builds, and Python programs that use the ssl module’s default context. Others need their own
switch:
- Node.js writes its keys with
--tls-keylog, so start it asnode --tls-keylog="$SSLKEYLOGFILE" app.js. - Go programs write them only when the program sets
KeyLogWriterin its TLS configuration. - Rust programs on rustls write them only when they use its
KeyLogFile. - Java needs an agent that hooks into its TLS code.
Safari and other apps built on Apple’s own TLS libraries never write a key log, and their traffic stays encrypted.
Through Borg’s proxy
Section titled “Through Borg’s proxy”Set TLS keys to Proxy HTTPS through Borg with a temporary CA to read the HTTPS of programs
that never write a key log, such as Go and Rust programs or a client you cannot change. When the
capture starts, Borg runs a proxy on 127.0.0.1 and makes a certificate authority for this
capture alone. Its key lives in Borg’s memory and is gone when the capture ends. Borg types one
line at its shell’s prompt that sends HTTPS through the proxy and has programs trust the CA:
export HTTPS_PROXY='http://127.0.0.1:54321' https_proxy='http://127.0.0.1:54321' SSL_CERT_FILE='…' CURL_CA_BUNDLE='…' REQUESTS_CA_BUNDLE='…' NODE_EXTRA_CA_CERTS='…'The four certificate variables name one file in Borg’s runtime folder, readable only by you, that
holds the CA’s certificate followed by the system’s own, so the programs still trust every other
host. While the capture runs, the footer says Proxy on and the proxy’s address, and
L Copy proxy settings copies the same variables for a program in another terminal. When the
capture ends, Borg puts back what its shell had, as it does for SSLKEYLOGFILE, and removes the
file. Borg never adds its CA to the system’s trust store.
A program that goes through the proxy asks it for a tunnel to the host. The proxy connects to the host and checks its certificate as the system does, then shows the program a certificate for the same host that the CA signed. The program and the host still talk HTTP/2 or HTTP/1.1, whichever the host picked. The proxy writes the keys of both TLS sessions to the capture’s key log, so the capture’s copy of the connection to the host opens decrypted, and the viewer gives it the process that asked for it. Anything in a tunnel that is not TLS passes through as it is.
Programs follow the proxy when they read both kinds of variables:
- curl, git and Python with requests or urllib follow
HTTPS_PROXYorhttps_proxy, and trust the bundle throughCURL_CA_BUNDLE,REQUESTS_CA_BUNDLE, orSSL_CERT_FILEwhere they use OpenSSL. - Go follows
HTTPS_PROXY, except for requests tolocalhost, and readsSSL_CERT_FILEon Linux. On macOS it trusts the system’s certificates only, so its connections fail there. - Node.js trusts the CA through
NODE_EXTRA_CA_CERTS. Recent versions sendfetchandhttpsthrough the proxy when you also setNODE_USE_ENV_PROXY=1. - Programs with their own certificate list, those that pin certificates, and apps that take the proxy from the system settings, such as Safari, refuse or bypass the proxy. A refusal shows as A program did not trust the proxy’s certificate for and the host.
When the host’s certificate does not check out, the proxy closes the connection and a notice says
Borg could not verify and the host. That is what happens with your own services when their
certificates come from a development CA the system does not trust, such as the one
mkcert makes. Point proxy-ca in the [network] section
of config.toml at that CA’s folder:
[network]proxy-ca = "~/Library/Application Support/mkcert" # mkcert -CAROOT prints the folderThe folder must hold the certificate and its key as rootCA.pem and rootCA-key.pem, as mkcert
names them, or as ca.pem and ca-key.pem. The choice in the dialog then reads Proxy HTTPS
through Borg with the CA in and the folder. The proxy signs with your CA, which your programs
already trust, and it trusts the CA too when it checks your services. Use a CA that exists for
development only, because whoever has its key can read the traffic of everything that trusts it.
F3 finds keys in two places:
- A decryption secrets block inside a
.pcapngfile, where Wireshark keeps them too. - A key log beside the capture with the same name and the
.keylogextension, such ascapture-20260101-120000.keylognext tocapture-20260101-120000.pcap.
To decrypt a capture you made another way, run the program with the variable set during the capture, and once the capture is saved, rename the key log so its name matches the capture’s:
SSLKEYLOGFILE=~/tls.keylog curl https://example.comBorg opens TLS 1.3 and TLS 1.2 records sealed with AES-128-GCM, AES-256-GCM or ChaCha20-Poly1305, the ciphers browsers and current servers agree on. A record that no key opens keeps its encrypted bytes, and its turn says still encrypted. That happens when the key log lacks some of the connection’s keys, and after bytes the capture lacks, because Borg cannot tell where the next record starts. QUIC traffic stays encrypted.
A key log decrypts every connection it has keys for, passwords, cookies and tokens included, and
so does a capture with the keys inside. Borg makes both readable only by you. Do the same for a key
log you write yourself with chmod 600, keep key logs as safe as the captures, and delete both when
you are done.
What the system reports
Section titled “What the system reports”On macOS, Borg reads every socket with its byte counts and process from netstat, so traffic
appears for TCP and UDP alike. Sockets of other users’ processes are listed too.
On Linux, Borg reads the sockets from /proc/net and finds each socket’s process through the
processes’ open files. Without administrator rights it can only look into your own processes, so
other users’ sockets show Unknown process. TCP byte counts come from ss when it is
installed; UDP sockets and machines without ss show — for rates, and the details say
The system reports no byte counts here. Interface totals and rates always work.
This guide follows the main branch · Borg 0.11.0 · 1f6bce6
The latest release may not have everything described here. Release notes