There are two usual ways to make one application use a proxy. You can change the machine, or you can intercept the machine. Both are a single switch, and both become difficult to explain the moment they fail.

Two switches, both machine-wide

A system HTTP proxy is the tidier option. Applications that read system settings pick it up automatically, which is most of the ones you care about. The cost is scope: every other application on the machine now goes through a path you configured for one of them. Local tools that expect a direct connection start behaving strangely, and the setting outlives the session that needed it.

A TUN interface is the more forceful option. It takes over routing below the application layer, so it works whether or not the application cooperates. The cost is observability. Nothing above that layer can say which requests used the tunnel, because the tunnel does not have an opinion about applications — it has an opinion about packets.

Neither design is a mistake. Both answer the question “how do I send traffic through a proxy?”. I had a smaller question.

The smallest scope that answers the question

What I actually wanted was: launch this one application, with this one setting, for this run. Chromium accepts a proxy server as a launch argument, and reads proxy environment variables for its child processes. Pass both at launch and the setting belongs to that process tree rather than to the machine.

That is a genuinely smaller change, and it stays smaller in ways that matter later. Nothing is written to system configuration, so there is nothing to clean up if the experiment ends badly. The setting does not survive the process tree it was created for. And because there is no global switch, there is no state where half the machine is routed and nobody remembers why.

The scope of a change is not a detail of its implementation. It decides what you are able to say when something goes wrong.

A narrower scope moves the work, it does not delete it

The first thing you lose is the launch path. Once settings are applied at launch, opening the application from the Dock or the Start menu quietly bypasses them. Users will do exactly that, because that is how they have always opened it. The only honest response is to say it in the interface, next to the buttons, rather than in a document nobody reads.

The second thing you lose is coverage. A launch argument is a request, not a policy. Child processes that ignore environment variables will not follow it, and traffic that does not go through Chromium’s network stack — voice, WebRTC, UDP — may behave differently or not at all. A tool that promises “all traffic” would be lying. This one promises a launch.

The third thing you take on is maintenance against someone else’s release cycle. The behaviour depends on how a specific desktop application is built, and that application updates on its own schedule. This is not a stable contract, and designing as if it were would make the next Electron update look like a bug in the launcher.

Proof has a resolution

The part I did not expect to matter as much as it did: a diagnostic is only as useful as its resolution. “Connected” is a low-resolution answer. It collapses several different facts — a tunnel opened, a TLS handshake completed, an application process carried the right arguments, sockets were observed — into one word, and that word is wrong for at least one of them some of the time.

So the checks are reported separately, and each one states what it did not establish:

  • A tunnel reached a TLS endpoint through the proxy. That is not the same as a working session; an HTTP 403 at that stage is still a response.
  • The running main process carries proxy arguments matching the saved settings. This is the one check that answers the question directly, and it is also the one that can be true while traffic still goes elsewhere.
  • A TCP snapshot showed connections from the process family to the proxy. A snapshot is one moment. Its absence proves nothing, and a hostname cannot be matched against a numeric address inside it.

Putting the limitation beside the tick is a design decision, not legal caution. A green mark that overstates its evidence ends the investigation at exactly the point where the investigation should start.

Three questions I use to judge a scope

This is the reusable part, and it applies well beyond proxies.

  1. Does the change touch anything that is not part of the question? If the answer is yes, the extra reach will need its own explanation later, and nobody will remember to give it.
  2. When it fails, can I tell which piece failed? A change that fails as a single undifferentiated symptom is a change you cannot debug, only reinstall.
  3. Can it be undone without a reboot, a reinstall, or a support conversation? Reversibility is what makes it safe to try things in the first place.

By those three, scoping the proxy to a launch is the right call here. It touches one process, it fails in separable pieces, and quitting the application undoes it.

Narrow is not automatically better

It would be easy to turn this into a rule and be wrong. Narrower scope is not a virtue on its own — it moves work onto the person using the tool, and sometimes onto the person maintaining it. The system proxy exists because most of the time “make everything use the proxy” is the actual requirement.

What narrow scope buys is explainability. It lets the tool say what it did, in a sentence, without hedging. For a utility whose only job is to answer a question about a connection, that turned out to be the whole product.


Written from the project’s design notes and documentation. No usage data, accounts or metrics appear on this page; the application collects none.