Active / synthetic network testing
What it is
A synthetic test is a robot that pretends to be one of your users. On a schedule you set, it sends real traffic across the network — a ping, a connection attempt, a web request, a name lookup — and times the answer. probectl calls one of these scheduled tests a canary, after the caged birds miners once carried underground: a small, expendable sentinel that shows trouble before the people behind it get hurt.
The thing that actually runs the canaries is the canary agent — a single small program you deploy out where your users (or your services) really sit: in a branch office, a cloud region, a data center, next to a particular customer. The agent carries its test types compiled in, runs them on their intervals, and ships each result back to the control plane. Four test families are the subject of this page:
- Reachability and latency — Internet Control Message Protocol (ICMP, the
protocol behind
ping), Transmission Control Protocol (TCP), and User Datagram Protocol (UDP) probes that measure whether a host answers and how long it takes. - Web / Hypertext Transfer Protocol (HTTP) tests that measure availability and break a page fetch into its phases.
- Name resolution — Domain Name System (DNS) lookups, including Domain Name System Security Extensions (DNSSEC) validation.
- Agent-to-agent (A2A) two-way measurement, where a pair of agents probe each other to measure each direction of a path separately.
probectl is observe-only: a canary sends test traffic and records what came back. It never blocks, reroutes, or modifies your real traffic, and it never phones home — every result stays inside your own network. Terms of art (ICMP, TCP, jitter, DNSSEC, and the rest) are defined in the glossary.
Why it exists
Imagine waiting for a smoke detector that only beeps after the house has burned down. A dashboard built solely from real user complaints works that way: by the time tickets arrive, customers are already unhappy. Synthetic tests are the smoke detector you place before the fire — they probe continuously, so you learn a login page is slow at 2 a.m. when nobody is watching, not at 9 a.m. when everybody is.
Two specific problems they solve:
- Catch problems before users do. A canary runs every 30 seconds whether or not a human is looking. A target that starts failing trips an incident long before a support queue fills up.
- Measure from where your users actually are. A service can be perfectly healthy from the data center and miserable from a branch office two countries away — the difference is the network between them. Because you place agents at the vantage points you care about, a canary measures the path your users take, not an idealized one. Put an agent in each region and the same test reveals which location is slow.
A canary failure is a signal, not an action. probectl raises it, correlates it with other planes, and shows it to you — it does not take the target down, open a firewall, or reroute anything. Acting on a signal is always a separate, human-decided step.
For a full outside-in deployment pattern — owned vantages, site/region labels,
coverage-map review, probe-pack templates, and MSP resale guidance — use
../outside-in.md. That guide is the replacement motion for
vendor-owned global probe fleets: the probes are yours, the custody boundary is
yours, and the coverage map says where you do and do not have eyes.
The map is native: Targets → Owned-vantage coverage, backed by
GET /v1/coverage/vantages and probectl coverage vantages. It needs no
Grafana/Perses runtime, IP-geolocation feed, or vendor probe fleet.
The same screen also contains Cross-plane coverage debt, backed by
GET /v1/coverage/debt and probectl coverage debt. The bounded response
flattens every known tenant-local site or topology entity across synthetic,
path, flow/eBPF, routing/BGP, and device planes. A row is covered only when
that exact entity has fresh persisted local evidence for that exact plane.
Registration and topology presence are readiness clues, not evidence; absent
correlation, an unwired producer, or a truncated scan stays explicitly
unknown. The response exposes evidence timestamps, age, basis, producer
registration/readiness, and every truncation reason. Its only actions navigate
to the native Targets or Topology read surfaces.
This debt map performs no IP geolocation, cloud/CMDB discovery, or outbound lookup and adds no dashboard runtime or service dependency. The relational agent/test and producer reads carry an explicit tenant predicate under RLS; the topology read is obtained only from a tenant-bound store handle.
The owned-vantage matrix also shows an Execution cadence receipt. “Fresh”
only says the newest result is recent; cadence asks the stricter question:
“did the configured rounds actually keep arriving?” For each exact test_id,
the receipt compares the server definition interval with the effective interval
stamped by the local agent, deduplicates retries, and reports observed,
expected, and missed rounds plus the largest gap in a bounded window.
The states are intentionally closed and cautious:
on_cadence— at least three exact rounds, matching intervals, no observed gap, and complete bounded local history;gaps_observed— positive evidence shows one or more expected rounds missing;never_observed— complete local history contains no exact result for this definition;unknown— evidence is unwired, legacy/unattributed, interval-mismatched, insufficient, future-dated, definition-mismatched, truncated, or the bounded window crosses the current control-plane process start.
Every receipt says the current local assignment is unverified. Agents run
their local YAML canaries plus the tenant's central test set, which they pull
and verify themselves
(central test distribution); the
control plane neither reads nor pushes the YAML. This feature does not
schedule, assign, rebalance, update, disable, or retarget an agent.
Every non-healthy receipt provides a read-only Inspect test action. The
action carries the exact server test_id to Targets, where a tenant-scoped
GET /v1/tests/{id} selects one definition even when human-readable names are
duplicated; its existing Results and YAML actions remain available. A failed
exact lookup shows no fallback list. The independent coverage-repair action
remains separate, and on_cadence rows add no inspect prompt.
At narrow viewports the native UI renders each receipt as one bounded record
that keeps test name, probe type, target, coverage state, and cadence proof
together. The desktop surface remains a semantic table. Cadence state, reason,
counts, window, completeness, and assignment caveat use the en, es, ar,
and pseudo-locale catalogs; Arabic retains the shell's RTL direction.
Expected rounds begin with each agent's first exact observation in the bounded
window; probectl does not invent rounds before that point because it cannot know
when the local YAML assignment began. Interval validation uses every exact row
inside that window plus only the nearest exact row before its cutoff. Older
retained rows describe earlier windows and cannot poison current cadence truth.
How it works
The control plane never goes out and measures the network itself; it consumes. The measuring is done by the agent you deploy. The agent is a courier, not a postal depot: it holds a direct, authenticated connection to the control plane and hands its results straight over, so it needs nothing else standing up — no message bus, no extra datastore, just the control plane reachable.
The transport is mutually authenticated and encrypted. The agent talks to the control plane over gRPC (a connection protocol built on HTTP/2) wrapped in mutual Transport Layer Security (mTLS). "Mutual" is the important word: the server proves its identity to the agent and the agent proves its identity to the server, each presenting a certificate. Until an agent holds a valid identity certificate, the connection is refused and nothing it sends lands anywhere. There is no plaintext mode for this channel.
Each agent is bound to exactly one tenant. A tenant is one isolated customer or organization. The agent's identity certificate names its tenant, every result it sends is stamped with that tenant, and the control plane verifies the pairing before accepting anything. The practical guarantee: a result an agent produces can only ever land in its own tenant's view — it is structurally unable to write into another tenant's data, and a query you run only ever returns your own telemetry.
The test types, in plain terms. Each canary type sends a specific kind of traffic and reports a specific kind of result:
| Type | What it sends | What you get back |
|---|---|---|
icmp |
ping echo requests |
loss, latency, and jitter (the variation between round-trips — what voice and video feel) |
tcp |
a connection attempt | reachability plus connect latency |
udp |
an echo datagram | round-trip time, or a clear failure if nothing echoes |
dns |
a name lookup | resolution time, the answer, DNSSEC validation result, and an optional delegation trace (walking root → top-level domain → authority instead of trusting a cached answer) |
http |
an HTTP(S) request | availability plus a phase breakdown: DNS, connect, TLS handshake, time-to-first-byte (TTFB), and total |
For http over HTTPS, the canary also captures the TLS handshake details, which
feed the certificate-posture view so you can spot an expiring certificate before
it bites.
Agent-to-agent (A2A) turns a pair of agents into a two-way measurement. Both ends timestamp each probe and reply, so each direction's latency is measured on its own rather than guessed from a single round-trip — useful when a path is asymmetric (fast one way, slow the other). A2A is off by default; you opt in, and every brokered frame is authenticated with a per-session key delivered over the existing mTLS channel.
Results flow through one pipeline. Whatever the type, a result lands in the same place: the control plane stores it, plots its numbers as metrics, and feeds it into incident correlation. When a canary to a service starts failing and that service also appears in another plane (say, a service map), both signals land in one correlated, tenant-scoped incident instead of two unrelated alerts.
The agent never loses data to a network blip. If the control plane is briefly unreachable, results buffer to a local on-disk queue and drain on reconnect.
Use it
Here is the whole loop: define a test, run the agent, read the result. Probes run straight from the agent's configuration file — you do not have to create anything server-side first.
A minimal agent configuration with one inline HTTP test. The tls: block points
at the identity files the agent received when it enrolled; the canaries: block
defines what to probe:
control_plane:
grpc_addr: "control.example:9443" # the control plane's agent listener
tls:
cert_file: ./identity/cert.pem # the agent's identity (proves the agent to the server)
key_file: ./identity/key.pem
ca_file: ./trust/ca.crt # verifies the SERVER to the agent
server_name: "control.example" # must match the server certificate
agent:
capabilities: ["http", "icmp", "dns"]
heartbeat_interval: 30s
canaries:
- type: http
# Optional exact attribution: copy the id from `probectl test list`.
test_id: "018f2d5e-7b3a-7aa2-8b8a-9c21b0c6d991"
target: "https://app.example/health"
interval: 30s
timeout: 10s
params:
method: "GET"
expect_status: "2xx,3xx"
- type: dns
target: "app.example"
interval: 60s
params:
type: "A"
dnssec: "true"
Start the agent:
probectl-agent -config /etc/probectl/agent.yml
Within one interval (30 seconds for the HTTP test above) the agent connects, runs the probe, and streams the result. Read the latest result per target from the API:
curl https://control.example/v1/results/latest
What you should observe: one entry per target. The http result carries
success: true plus the phase breakdown (DNS, connect, TLS, TTFB, total), so a
slow page tells you which phase was slow. The dns result carries the
resolution time, the answer, and whether DNSSEC validation passed. The same data
renders on the Targets / synthetic results screen in the web interface.
You can also create a test from the command-line tool — for example a scripted browser-style transaction (which runs as an HTTP transaction by default):
probectl test create \
--name login-check \
--type browser \
--target https://app.example/login \
--param 'script={"name":"login","start_url":"https://app.example/login","steps":[{"action":"goto"},{"action":"assert_status","status":200}]}'
Pitfalls & limits
- No agent, no data. A freshly installed control plane observes nothing — it is a healthy, empty database. The fix is always to attach an agent. If a probe shows no results, the most common cause is that no agent is enrolled or running for that tenant.
- The agent must have an identity first. The mTLS channel refuses an un-enrolled agent. If results never arrive, confirm the agent enrolled successfully and its certificate has not expired. Certificates auto-rotate at roughly two-thirds of their lifetime only when an identity server is configured; with operator-managed certificates you rotate them yourself.
- A UDP or voice target must actually echo. UDP and RTP-style probes need the far end to reflect the datagrams. A target that does not echo shows up honestly as 100% loss with an explicit "unmeasurable" failure — the canary never invents a result from a silent target.
- A canary failure is a signal, not a verdict on the whole world. A single agent's probe reflects the path from that agent. To distinguish "the service is down" from "this one location's path is bad," run the same test from more than one vantage point and compare.
- probectl is observe-only. It does not block, throttle, or reroute traffic, and it is not an intrusion-prevention system. A canary tells you something is wrong; a human decides what to do about it.
- A2A is opt-in. Two-way agent measurement is disabled by default; you must enable it and have agents at both ends. For site-to-site testing, start a tenant-scoped mesh with site-labeled agents so every directed site pair gets a brokered A2A session and topology edge.
- Probes fail closed. A probe with a missing credential, an unverifiable target identity, or an unreachable control plane stops or reports failure rather than guessing — silence is never reported as success.
Reference
- Test types:
icmp,tcp,udp,dns(with DNSSEC and delegation tracing),http(with DNS/connect/TLS/TTFB/total breakdown),browser(scripted transaction),voice(call-quality scoring), plus agent-to-agent (a2a) two-way measurement and anoopheartbeat. - Transport: agent → control plane over gRPC wrapped in mutual TLS; the agent is bound to one tenant by its identity certificate; results buffer to local disk during an outage and drain on reconnect.
- Read results:
GET /v1/results/latest(latest result per target), or the Targets / synthetic results screen in the web interface. - Create a test from the CLI:
probectl test create --name … --type … --target …. - Related capabilities (separate pages): Digital experience (last-mile, voice, real-user monitoring); Topology & change (the correlated picture a failing canary feeds into); Owned outside-in monitoring (self-hosted vantages and probe packs). See also the deployment and getting-started guides for standing up an agent end to end.
Covers: F1, F2, F4, F5