Open-source project
elesiuta/picosnitch avatar
elesiuta/picosnitch

Picosnitch: per executable bandwidth monitoring on Linux with BPF and fanotify

Monitor network traffic per executable

1,016 stars45 forksCGPL-3.0

At a glance

What is it?
Picosnitch is a Linux daemon that logs network connections attributed to the executable that made them, using BPF for traffic accounting and fanotify to notice when a program on disk is modified, with web and terminal interfaces and optional VirusTotal checks. It is a Python project that ships its own C components, requires root, and keeps a local SQLite database of connection logs.
Who is it for?
Picosnitch fits a Linux administrator or privacy conscious developer who wants to know which programs on a machine are talking to the network, and who is willing to run a root daemon with BPF loaded, a fanotify watch on the filesystem and a persistent database of connection logs.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: knowing that a program is on the network, not just that the machine is

A host level bandwidth graph tells you that 400 MB went out last night. It does not tell you which of the forty things running on that host sent it, which is the question most people actually have.

Picosnitch answers that by attributing each connection to the executable responsible for it. The README lists the feature set plainly. It receives notifications whenever a new program connects to the network, or when it is modified. It monitors bandwidth, breaking traffic down by executable, hash, parent, domain, port or user over time. It offers web and terminal interfaces with GeoIP lookups for each connection, using IP Geolocation by DB-IP. It can optionally check hashes or executables using VirusTotal. Executable hashes are cached based on device and inode for improved performance, and it detects applications running inside containers, with multiple versions of the same app differentiated based on their hash.

Two of those features are unusual enough to be the reason to use this rather than a generic traffic monitor.

The first is the parent chain. The README explains that since applications can call others to send or receive data for them, the parent and grandparent executables, with hashes, are also logged for each connection. That closes the most common blind spot in attribution, where a small helper binary makes the socket and the interesting program is two levels up in the process tree.

The second is the modification watch. The project uses fanotify to watch executables for modification, so a program that is replaced on disk while running is noticed. Combined with the hash based cache, which is keyed on device plus inode, that gives a defensible answer to the question of whether the binary on disk is still the binary that opened the connection.

The design goal is stated in the README as pragmatic and minimalist design focusing on accurate detection with clear and reliable error reporting when it isn't possible, and the documentation links that phrase to a limitations section. That is a good framing for a tool whose whole value is attribution, and it tells you the project would rather report a gap than paper over it.

BPF for the traffic, fanotify for the executables

The measurement side is a Linux kernel facility rather than a userspace netfilter hook, and the choice shows in the accuracy claim.

The README says Picosnitch uses BPF for accurate, low overhead bandwidth monitoring, and fanotify to watch executables for modification. The libbpf CO-RE angle matters: the install section says the tool works on every Linux distribution with Python 3.12 or newer and a kernel new enough to run modern libbpf CO-RE programs. CO-RE, which stands for compile once run everywhere, means the BPF program is compiled against a kernel's BTF type information and relocated at load time, so one binary works across kernel versions instead of needing a rebuild per kernel.

The project is primarily Python, and the primary language in the repository metadata is C, which reflects the BPF and native components. The repository layout shows the split. There is a src/ directory holding the Python package, a bench/ directory which is the natural place for BPF benchmark harnesses, a debian/ directory for the Debian package, an rpm/ directory for the RPM, a docs/ directory, and a tests/ directory.

The build configuration confirms the C part. The pyproject.toml excludes src/picosnitch/bpf/vmlinux*.h from the wheel, which are the generated kernel type headers, so they are a build time artefact rather than something shipped. There is a custom hatch build hook at hatch_build.py, a file also included in the sdist alongside the Debian service unit, and the version is dynamic, read from src/picosnitch/__init__.py through the hatch version path.

The static web assets are force-included rather than discovered. The wheel target force-includes app.js, favicon.svg, index.html and style.css from src/picosnitch/ui/static/, which is how a Python packaging tool ends up carrying a small single page application for the web interface without a bundler step in the wheel build.

The repository also has a tach.toml. Having that in the tree suggests the codebase has enough internal structure that keeping the layers honest is an ongoing problem rather than a one-off.

Installing with pipx, and the systemd unit it writes for you

The recommended install is a system-wide pipx install, and the three commands are:

sh
sudo pipx install picosnitch --global
sudo picosnitch systemd
sudo systemctl enable --now picosnitch

The --global flag is what makes this work, and the README is explicit that pipx version 1.5.0 or newer is required, because that is when the --global flag was added. A system-wide pipx install puts the picosnitch command on the PATH for every user, which is what a monitoring daemon needs to be, and the pipx isolation keeps the Python dependencies out of the system site-packages.

The second command is the one that surprises people. sudo picosnitch systemd writes /usr/lib/systemd/system/picosnitch.service, and only then does systemctl enable --now have a unit to enable. The README lists this as a bullet under the install steps, which is a nice touch, because a subcommand whose only effect is to write a file is easy to miss and then wonder why the service does not exist.

The third install variant is for remote logging. If you want the connection logs written to an external SQL server as well as the local one, you install with sudo pipx install 'picosnitch[sql]' --global for the optional MariaDB, MySQL or PostgreSQL drivers. The pyproject.toml confirms the extras: sql pulls psycopg[binary] and pymysql, and all pulls the sql extra.

Everything runs as root, and that is not incidental. The daemon reads process information and file metadata for other users' executables, attaches BPF programs to the kernel, and reads its own event socket. The README says the top command requires root to read the daemon's event socket, and it starts and stops its own daemon if one is not running.

The tool also has a packaging story beyond pipx. The pyproject.toml classifiers list POSIX Linux only, and the sdist includes debian/picosnitch.service, and the repository carries both debian/ and rpm/ directories. The README links a repology packaging status badge, which is how you would check whether your distribution ships it.

The daemon, the three front ends and the config file

The usage model is one daemon with several front ends against it.

The daemon lifecycle is managed through systemd with sudo systemctl enable, disable, start, stop or restart picosnitch, or without systemd with sudo picosnitch start, stop or restart, or kept in the foreground with sudo picosnitch start-no-daemon. That last mode is the one you want when running it in a container or under a supervisor.

There are three ways to look at the data. picosnitch webui serves a web interface for browsing past connections at http://localhost:5100, overridable with the PICOSNITCH_HOST and PICOSNITCH_PORT environment variables. picosnitch tui is the terminal interface for the same job. And sudo picosnitch top is a live event feed, which the screenshots show sorted by bytes received. picosnitch status shows the daemon pid and the systemd service state, and picosnitch help gives the full usage.

The config lives at /etc/picosnitch/config.toml and is created with defaults on first run, so there is no mandatory configuration step. The file is TOML, and the shipped excerpt shows the shape:

toml
[database]
enabled = true                # write connection logs to /var/lib/picosnitch/picosnitch.db (SQLite)
retention_days = 30           # how many days to keep connection logs in the local database
                              # (the remote database is append-only; see [database.remote])
write_limit_seconds = 10      # minimum time between connection log entries
                              # increasing it groups traffic into larger time windows, decreasing
                              # disk writes, time precision, and database size
text_log = false              # also write a CSV connection log to /var/log/picosnitch/conn.log

The write_limit_seconds knob is the one to think about. It is a minimum time between connection log entries, and raising it groups traffic into larger windows at the cost of disk writes, time precision and database size. That is a direct trade of resolution against storage, and for a tool that logs every connection, a chatty process can produce a lot of rows.

The [database.remote] section is the interesting one for a security audience. It is described as optional, and used for off-system, tamper-evident logs. It mirrors the local SQLite schema with connections, executables, domains and addresses, the client is set to mariadb, psycopg, psycopg2 or pymysql, and you can optionally set connections_table to override the default. The comment explains the purpose: this lets multiple hosts share one server with a connections table each while reusing the shared executables, domains and addresses tables. So a fleet of machines can write into one database and still be separated by table.

The local file permissions are deliberate too. The [data] section sets owner, group and mode for the files in /var/lib/picosnitch, /var/log/picosnitch and /var/cache/picosnitch, and the comment states that config.toml stays root:root 0600 because it can contain credentials.

What gets logged, and the settings that decide what stays out

The [log] section of the config file is a privacy control surface, not just a verbosity switch. The shipped excerpt shows the first entries:

toml
[log]
addresses = true              # log remote addresses for each connection
commands = true               # log command line args for each executable
ports = true                  # log local and remote ports for each connection
ignore_ports = []             # list of ints; matching connections are omitted from the log

Each of the three booleans is a category of personal or sensitive data. Addresses are the remote endpoints, which combined with GeoIP is enough to say where you were. Commands are the command line arguments of the executables, which routinely contain credentials, file paths and identifiers. Ports are the local and remote ports, which is how you identify a service even when the address is a CDN address.

The two ignore lists are the escape valve, and they work at different granularities. ignore_ports takes a list of ints and omits matching connections. ignore_domains takes a list of strings in reverse-dns notation, so you can exclude an entire host by name regardless of which address it currently resolves to. That distinction matters, since a domain can rotate addresses and a port can be reused by an unrelated service.

This matters more than usual for a tool whose stated topics include privacy, and it is the honest counterweight to the feature list. A monitoring tool that logs every connection with command line arguments is a rich target and a rich dataset. The project makes the data categories individually switchable and lets you exclude by port or domain, and it gives the local database a retention period of 30 days by default, so there are three separate controls over how long the data lives.

The remaining features interact with this. The VirusTotal check is optional, and it is the one feature that sends data off the machine, since a hash lookup tells the service which file you are running. The GeoIP lookups are similarly a lookup against a database provider, IP Geolocation by DB-IP. Both are opt in by nature of being listed as optional, but a reader deciding whether to run this on a work laptop should know that enabling them means file hashes and connection endpoints are visible to third parties.

The container detection is worth one more note, because it changes how you read the output. Multiple versions of the same app are differentiated based on their hash, so two processes with the same name running different builds are not merged in the accounting, which is exactly the case where a naive per executable breakdown would mislead you.

Versioning, packaging and where the project stands

The repository is not archived and the last push was on 2026-10-08, so the project is being worked on. The three recent releases are v2.2.1 on 2026-07-11, v2.2.0 on 2026-07-11 and v2.1.2 on 2026-07-05, so two of the three landed on the same day, which is a project still finding its shape around the 2.x API.

The version number comes from src/picosnitch/__init__.py through the hatch version configuration, which means the version lives in the Python source rather than in the packaging metadata, a normal pattern for hatchling projects and the reason the version is declared dynamic in pyproject.toml.

The project description in the packaging metadata is the whole proposition in a few words: Monitor network traffic per executable using BPF. The classifiers scope it to Linux and to networking and system monitoring, and the licence is GPL-3.0-or-later, which is worth noting for anyone considering the remote logging mode, since the SQL server holds a long term record of what ran on your machines and that data is generated by the tool rather than covered by its licence.

The development tooling is modest and worth noting for the contributor. Ruff is the linter with import sorting enabled and a line length of 200, which is a deliberate choice for a project with long configuration strings. The dev dependency group includes pillow, playwright and pyan3, which is the classic trio for a project that renders its own terminal and web interfaces and needs a screenshot test, and pytest. Playwright in a dev dependency of a network monitor usually means the web UI is tested end to end against a real browser, which is the right way to test an interface that charts traffic.

The documentation is a published site at elesiuta.github.io/picosnitch, and the README points to a screenshots gallery there with short demo videos. The screenshots are generated by the pages workflow on every release, and the README has a comment explaining why: the image URLs point at the published docs site, which bundles the screenshots and videos generated on every release, because releases carry no attached assets so they stay immutable. That is a thoughtful detail for a project whose README is mostly screenshots.

Editorial conclusion

Picosnitch fits a Linux administrator or privacy conscious developer who wants to know which programs on a machine are talking to the network, and who is willing to run a root daemon with BPF loaded, a fanotify watch on the filesystem and a persistent database of connection logs. It is the wrong tool if you want fleet wide flow data, since it is a per host daemon rather than a collector, and the wrong tool if your kernel is too old for CO-RE BPF or your distribution ships an older Python. Before adopting, read the limitations section in the documentation site, because the project states plainly that it favours accurate detection with clear error reporting when detection is not possible, and that means it will refuse to attribute a connection rather than guess.

Frequently asked questions

How do I install Picosnitch on Linux?

Use a system-wide pipx install with sudo pipx install picosnitch --global, which requires pipx 1.5.0 or newer for the --global flag. Then run sudo picosnitch systemd to write /usr/lib/systemd/system/picosnitch.service, and sudo systemctl enable --now picosnitch to start it.

What kernel and Python versions does Picosnitch need?

It works on every Linux distribution with Python 3.12 or newer and a kernel new enough to run modern libbpf CO-RE programs. The Python requirement is also declared in the packaging metadata as requires-python >=3.12.

Where is the Picosnitch config file and what is in it?

Config is stored at /etc/picosnitch/config.toml and is created with defaults on first run. It is TOML, with a database section for the local SQLite log and retention, an optional database.remote section for an external SQL server, a data section for file ownership and mode, and a log section controlling whether addresses, command line arguments and ports are logged plus ignore lists for ports and domains.

How do I log Picosnitch data to a remote database?

Install the sql extra with sudo pipx install 'picosnitch[sql]' --global for the MariaDB, MySQL or PostgreSQL drivers, then set client to mariadb, psycopg, psycopg2 or pymysql in the database.remote section. The remote schema mirrors the local one, and connections_table can be overridden so several hosts share one server.

Official sources

  1. elesiuta/picosnitch on GitHub
  2. License: GPL-3.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/elesiuta-picosnitch.svg)](https://hysenlabs.com/projects/elesiuta-picosnitch)

Was this useful?