Your software in your customers' networks

Your product has a piece that runs at the customer's site: a sensor, a collector, a gateway or an agent. It has to reach your cloud, and sometimes your cloud or your support engineers have to reach it. This page shows what that usually takes, the same fleet on nfltr, measured, and where nfltr does not help.


The problem

Vendors with a component inside customer networks tend to fall into a few shapes. We read the public deployment, firewall and network-requirements guides of a dozen such vendors; these are the shapes they describe:

Across these shapes, the data path to the cloud is already outbound only; nobody asks a customer to open an inbound port for telemetry. What each vendor builds and runs on top of it is:

The demo: one fleet, before and after

The fleet is a control plane and three edge agents in .NET 8 with gRPC, written twice. It is an example in nfltr's own repository, which is not public, and an end-to-end test runs both versions in containers and checks every claim below by exit codes.

What the fleet's machines need, read from the test's runs:

what the fleet's machines needbeforeafter
listeners reachable from the network (each one an inbound firewall, NAT or ingress rule and a public DNS name)1 (the control plane's public TLS port)0
a private CA to run, and the certificates it issues and renews1 CA, 1 certificatenone
self-signed certificates whose SHA-256 the peers pin (end-to-end encryption, per service)03
an account key file on every machine (mode 0600)noyes
processes on the 4 machines4 programs4 programs + 4 sidecars
what the proxied site must allow: CONNECT tothe vendor's own endpoint (controlplane.fleet.example:8443)the relay's two addresses (in the test relay:8443, relay:8444; hosted: grpc.nfltr.xyz:443, nfltr.xyz:443)
proxy code in the agenta hand-written CONNECT (the gRPC library used neither HTTPS_PROXY nor its handler's proxy setting)none: the sidecar reads HTTPS_PROXY
the control plane calls one agent by nameno (refused, checked)yes

Evidence: what each machine runs and listens on

Setup
The example's end-to-end test, both versions in containers on one Mac (Apple Silicon) with Docker, against a private relay; two runs on November 6, 2026. Listeners were read from each fleet machine's /proc/net/tcp and /proc/net/tcp6 while it ran, sidecars included; the proxy's list and log from the proxy itself.
Numbers
Listeners reachable from the network 1 → 0; private CAs 1 → 0; pinned self-signed certificates 0 → 3; processes 4 → 4 + 4 sidecars. The proxied agent has no direct route out (checked), its proxy refuses a CONNECT to any other address (403, checked), and its traffic went through CONNECTs to the listed addresses only.
Reproduce
(a) With the released binaries: the files at the end on two machines, the agent's machine with HTTPS_PROXY set to your proxy; list listening sockets with ss -ltn. (b) Our check: the example's end-to-end test in nfltr's repository, which is not public (Docker and openssl).
Limits
The relay is not counted: hosted, it is not yours to run; your own relay is one more public endpoint, shared by every fleet of the account. An account key file now sits on every machine. Three agents, not three hundred.

What the customer's network team is asked

Before, each vendor hands each customer its own list: its endpoints, regions and ports, often wildcard domains or IP ranges that change. On nfltr, the list is the same for every customer and every vendor using it: allow outbound CONNECT (or plain outbound TCP) to grpc.nfltr.xyz:443 and nfltr.xyz:443. No inbound rule, at the customer or at the vendor.

Faults and restarts

The application keeps its own deadlines, cancellation and retries; the sidecar does not hide failures. A session the sidecar loses is closed on the local socket, so gRPC sees UNAVAILABLE at once and the application's retry runs.

faultfailure the application saw afterfirst successful call started after
before: the proxied agent killed mid-command, its session over TLS132 to 184 ms910 to 1227 ms
after: the proxied agent killed mid-command (its sidecar kept running)118 to 252 ms642 to 1036 ms
after: an agent and the sidecar it owns killed, called by name122 to 139 ms779 to 896 ms

Times are from the kill, with Docker restarting the container. A 1 s deadline on a 5 s command was cancelled on every agent, before and after, on both shapes. Progress updates arrived as the agent sent them: the first 102 to 123 ms after the call for 100 ms of work, before and after.

Evidence: restarts, deadlines and network drops

Setup
The fleet's test as above, two runs (restarts: an agent killed mid-command, the control plane retrying every 250 ms; the Mac was shared with other work). Network drops: our separate comparison of a conventional HTTPS port forward against the sidecar, one Go client and server, the home uplink dropped without a reset for 5, 60 and 180 s.
Numbers
In the table above. After every network drop both versions answered and streamed again within 3 s; neither returned a wrong, duplicated or missing answer or stream item.
Reproduce
(a) With the released binaries: kill the agent's program or its nfltr up while a call runs, and time your own retry. (b) Our checks: the example's end-to-end test and the port-forward comparison, both in nfltr's repository.
Limits
One host, no real network distance. A single relay in both tests, so a relay restart is an outage until it is back.

Migration effort

The protocol and the business logic did not change. A channel's target became a local endpoint of the sidecar, the listeners moved to loopback, and the TLS client code went:

filescode lines added / removed
application code, the migration4+52 / −73
application code, new and optional: call an agent by name, an agent that owns its sidecar4+158 / −0
configuration4+27 / −19

Code lines exclude comments and blank lines. The removed lines are the TLS client: trust of the private CA, the server-name check, keepalives, the connect timeout and the proxy's CONNECT. The protocol and the business logic are untouched; the test applies the patch to the before tree, builds the after fleet from the result and checks that every command's outcome, progress and output are the same.

Evidence: the size of the migration

Setup
The example's migration.patch, counted by its report script; two runs of the test on November 6, 2026 applied it and built the after fleet from the result.
Numbers
As in the table. Unchanged: the protocol definition, the business logic and both project files.
Reproduce
(a) Not from outside: the example is not published, so the counts are our record. (b) Our check: the example's end-to-end test in nfltr's repository.
Limits
One small fleet in one language, written for this comparison. A real codebase with TLS and proxy options in several places will have a larger diff.

Rollout can be per agent: both paths run side by side, configuration picks one, and setting the old value back rolls an agent back. A call on an open session cost 0.06 to 0.21 ms more at the median on one host (two sidecars and the relay, end-to-end encryption on, against the direct TLS connection); on a real network each hop adds its round trip to the relay.

The relay never sees your traffic

With e2ee: true on a service, the two sidecars run TLS 1.3 inside the session, and the client accepts only the certificate whose SHA-256 it was given (the pin). There is no CA: provisioning makes a self-signed certificate per machine and records its pin in your inventory. The test checks that a wrong pin fails closed (nfltr dial exits 7, nothing delivered) and that a plain open of an encrypted service is refused (exit 4).

The relay still sees which agent talks to which service, when, and how many bytes, and it can refuse or cut a session. It cannot read or change what is inside.

Where nfltr is not a fit

What it costs

A comparison of both sides at the same workload (agents connected around the clock, bytes per command and per session) will be here once prices are set. What nfltr will meter is on the pricing page: connected time per agent and bytes per kind of traffic. On the build side, the lines to count are the ones in the problem: engineering for the client and the way back in, the public endpoints and their certificates, and each customer's firewall work.

Is it for you?

Fits if your component sits behind customers' NATs and proxies, and your cloud or your support engineers need to reach one component by name, or you are about to build that path and the client-side connection code in more than one language.

Not for a component that only sends data out over a path that already works, a latency-critical data plane, or sites with no outbound connection.

Try it

On the vendor's side, the control plane publishes its service and maps each agent's:

# controlplane.yaml: nfltr up -c controlplane.yaml --api-key-file <key file>
agent: controlplane
services:
  fleet: { tcp: 5000, e2ee: true }
connect:
  edge-1: { target: edge-1/execute, listen: "127.0.0.1:7001", pin: "<edge-1's pin>" }

At the customer's site, beside your component:

# edge-1.yaml: HTTPS_PROXY=http://proxy.customer.example:3128 nfltr up -c edge-1.yaml --api-key-file <key file>
agent: edge-1
services:
  execute: { tcp: 50051, e2ee: true }
connect:
  fleet: { target: controlplane/fleet, listen: "unix:/run/nfltr/fleet.sock", pin: "<the control plane's pin>" }

Your component then talks to unix:/run/nfltr/fleet.sock instead of a host on the network, and your control plane calls 127.0.0.1:7001 to reach edge-1. Services and remote clients and the migration story have the details: pins, status, metrics, who runs the sidecar, and a gradual rollout.