Skip to content
GitHub

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.

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.

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.com or A 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/bpf devices at first. Install Wireshark with its ChmodBPF component once: it gives the devices to an access_bpf group 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 a pcap or wireshark group 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.

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:

Terminal window
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 as node --tls-keylog="$SSLKEYLOGFILE" app.js.
  • Go programs write them only when the program sets KeyLogWriter in 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.

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:

Terminal window
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_PROXY or https_proxy, and trust the bundle through CURL_CA_BUNDLE, REQUESTS_CA_BUNDLE, or SSL_CERT_FILE where they use OpenSSL.
  • Go follows HTTPS_PROXY, except for requests to localhost, and reads SSL_CERT_FILE on 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 send fetch and https through the proxy when you also set NODE_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 folder

The 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 .pcapng file, where Wireshark keeps them too.
  • A key log beside the capture with the same name and the .keylog extension, such as capture-20260101-120000.keylog next to capture-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:

Terminal window
SSLKEYLOGFILE=~/tls.keylog curl https://example.com

Borg 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.

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