WhisperDocs
Concepts

Whalenet mesh

A fleet mesh where every node is a routable IPv6 /128 with a name anyone can check from the IANA root - and where the tool tells you, per peer, whether the path it is drawing actually exists.

Whalenet is the fleet view of something Whisper already had: an agent is an address, the address is the identity, and the identity is published in DNS where a stranger can validate it without an account. Whalenet adds the part a fleet needs - who else is in it, whether you can reach them, and what the path between you actually is.

The verbs, flags and argument order mirror tailscale, so what you already know transfers. What differs is said out loud rather than left to be discovered.

What is true today

Read this section before the commands. Every line of it is enforced by the tool's own output, so you will meet these facts anyway; meeting them here is cheaper.

Fact What it means
East-west is relayed by default Traffic between two of your nodes is carried by the Whisper box both peers terminate on, unless that pair has a direct path and this host's own tunnel has seen the handshake land. The round trip through the box includes the hairpin: 23.8 to 24.3 ms relayed against 0.13 to 0.28 ms direct, about 130x. We hold the plaintext for that hop, which is categorically weaker than a relay that only ever carries ciphertext, and it is the reason a direct path is worth having. No customer pair has been measured on one yet: a node has to declare its private endpoints at connect before it is eligible, and whale status prints relayed for every peer that has not.
A cross-box pair has no path at all Two nodes that terminate on different boxes cannot reach each other. whale status says so per peer rather than drawing a row that implies reachability.
Traversal is deliberately small No STUN, no DERP, and no third party anywhere in it. The cases covered are the ones that need no traversal at all (two nodes on a shared segment, or a peer whose public endpoint nothing rewrites) plus a punch where both ends handshake at the endpoints a box observed them arriving from. A pair where BOTH ends sit behind a NAT that varies its port per destination still relays, and a punch that fails costs latency and nothing else.
The namespace is public Agent names live in public DNS and the reverse tree is walkable. Anyone can enumerate what you publish.
Mobile is not supported There is no iOS or Android Whalenet client, and no date to promise.
Direct paths will move the boundary Once a pair talks directly, revocation depends on both ends applying it, and a peer entry's lifetime is not bounded yet. That is a cooperative boundary, not an enforced one, and it is worth knowing before you rely on it.

The verbs

$ whisper whale
whisper whale - the Whalenet command shell.

  status     this node, your fleet, and the path between them
  ip         this node's address (Whalenet is IPv6-only today)
  ping       a real round trip to a peer, and what it proves
  netcheck   what works between this host and the fleet, measured
  whois      who holds an address, keylessly, validated from the IANA root
  dns        the resolver in effect, a query with the graph's view, your records
  route      subnet routes: what this node carries, and what advertising needs
  exit-node  the ways out, ranked on the graph rather than on latency alone
  ssh        SSH to a node, host key proven from the IANA DNSSEC root
  migrate    move a Tailscale tailnet here, plan first, nothing silently widened
  serve      put a local port in front of your fleet, pinned by the published TLSA
  funnel     the same port, answered for the whole internet, deliberately
  syspolicy  the settings your MDM manages here, and the profile that sets them
  acl        the document that decides who in your fleet may reach what

Joining a node is still separate work: a half-working join would be worse than
none. Everything else here reads before it writes, `migrate plan` changes nothing
at all on either side, and `acl test` answers from the artifact the wire reads.

(Trimmed at the same place the tool stops describing itself: the usage, the per-verb summaries and the global flags follow, and whisper whale <verb> --help is the better place to read them.)

Every transcript on this page is real output from the binary https://get.whisper.online/dl/linux-arm64/whisper serves, whisper version 0.211.0. Nothing here is a mock-up. Two things in it are rewritten rather than captured, and it is worth knowing which: API keys are redacted, and every tenant label is replaced by a placeholder of the form t00000000000000000000000000000001. The addresses are rewritten to match: their routing half is zeroed the same way the tenant label is, so a block here shows you the shape of a real record while resolving to nothing. That matters more than it looks - an address is the PTR and RDAP lookup key, so publishing a real one alongside a redacted label would hand back the label the redaction removed. To check a real one, run whisper whale whois against an address you hold.

Where am I, and what can I reach

netcheck measures rather than asserts. It is the first thing to run on a host that is behaving oddly, and it needs no key.

$ whisper whale netcheck
NETCHECK
ipv6        yes
ipv4        yes
udp         yes
anchor box  ns1.whisper.online  25.0 ms
mtu client  1280
mtu server  1340

BOX                 FAMILY  ADDRESS                        UDP/53   TCP/443  NOTE
ns1.whisper.online  ipv6    2a05:f480:1400:ab7::fe45:10c   38.4 ms  25.0 ms  -
ns1.whisper.online  ipv4    78.141.218.153                 38.5 ms  25.1 ms  -
ns2.whisper.online  ipv6    2001:19f0:5000:15f6::fe45:110  35.4 ms  35.4 ms  -
ns2.whisper.online  ipv4    45.32.186.184                  35.4 ms  30.5 ms  -

  mtu: client is this host's tunnel interface; server is the wg-agents interface on the box (the fleet's published value, not measured from here). The smaller of the two governs.
  A v4-only host can still join: the WireGuard endpoint is resolved over both families, so the tunnel rides IPv4 while the identity inside it stays IPv6.
  PATH relayed: east-west traffic is carried by the box both peers terminate on. direct-local means the two nodes share a segment and the packets never touch a box; direct-public means the peer's endpoint is reachable as-is; direct-punch means a path was opened through a NAT by both ends handshaking at the endpoints a box observed them arriving from. A punch that fails costs latency and nothing else - the relay carries the peer throughout, and the line under this table says so in those words - and a pair where BOTH ends sit behind a NAT that varies its port per destination still relays. A pair split across two boxes has no path at all. Nothing here is measured until you run `whisper whale ping <peer>`.

The two MTU numbers are the usual cause of "it connects and then large transfers hang": 1280 is the client netstack default, 1340 is what the wg-agents interface on the box publishes, and the smaller one governs.

The fleet, and the path to each peer

$ whisper whale status
NODE
host        build-vm
address     2a04:2a01:9::50
tier        -
connection  not connected
key         set (WHISPER_API_KEY)

PEER                     ADDRESS                                  STATE   PATH     RTT  NAME IN DNS
agent-a7b3c624cc037b3cd  2a04:2a01:0000:0001:0000:0000:c037:b3cd  active  relayed  -    a7b3c624cc037b3cd.t00000000000000000000000000000002.agents.whisper.online
agent-aaa1ad6fb73bb9d5b  2a04:2a01:0000:0002:aa1a:d6fb:73bb:9d5b  active  relayed  -    aaa1ad6fb73bb9d5b.t00000000000000000000000000000002.agents.whisper.online

  PATH relayed: east-west traffic is carried by the box both peers terminate on. direct-local means the two nodes share a segment and the packets never touch a box; direct-public means the peer's endpoint is reachable as-is; direct-punch means a path was opened through a NAT by both ends handshaking at the endpoints a box observed them arriving from. A punch that fails costs latency and nothing else - the relay carries the peer throughout, and the line under this table says so in those words - and a pair where BOTH ends sit behind a NAT that varies its port per destination still relays. A pair split across two boxes has no path at all. Nothing here is measured until you run `whisper whale ping <peer>`.
  agents.whisper.online snapshot age: ns1.whisper.online 49m, ns2.whisper.online 49m. Every node is answering from the same recent snapshot.
  this host is pinned to 2a04:2a01:9::50, which is not in the fleet this key can see - the node half and the peer half are describing different accounts

(The peer table is trimmed here; the run it was captured from listed the whole fleet.)

Three things worth noticing in that output.

The PATH column is relayed, not a hopeful direct. A direct form is printed only when this host's own tunnel records a handshake that landed, because the handshake never touches a box and the box's answer therefore cannot settle it.

The snapshot age line is how stale each authoritative node's copy of the zone is, asked of each nameserver directly rather than through a resolver. That distinction is the point: the question is not what some node said, it is whether the nodes agree. Who may log in to a node is decided on that node from a signed record, and a node that has lost contact with the primary keeps serving the copy it holds with nothing bounding how long, so two nodes on different snapshots means whichever one replies decides what you are told. When that happens the line says so in those words. It never invents a number it cannot stand behind either: a serial it cannot honestly read as a time, or a nameserver that did not answer, is reported as unknown with the reason rather than dropped.

And the last line is the tool refusing to paper over a mismatch: this host is pinned to an address the presented key cannot see, so the node half and the peer half are describing different accounts, and saying that is more useful than rendering a tidy table that quietly means nothing.

Proving a path rather than believing one

status draws the topology. ping is the one that measures it.

$ whisper whale ping 2a04:2a01:0000:0003:a0c4:6935:6908:b236
reply from 2a04:2a01:0000:0003:a0c4:6935:6908:b236 tcp/443 in 39.8 ms  path=relayed
reply from 2a04:2a01:0000:0003:a0c4:6935:6908:b236 tcp/443 in 25.6 ms  path=relayed
reply from 2a04:2a01:0000:0003:a0c4:6935:6908:b236 tcp/443 in 26.1 ms  path=relayed

3 sent, 3 answered, path relayed, rtt min/avg/max 25.6/30.5/39.8 ms
  the path exists and it is relayed by a Whisper box, so this round trip includes the hairpin out to the box and back.

Who holds an address - with no key at all

whois is the verb to hand somebody outside your organisation. It needs no account, and it does not ask you to trust our answer: the name is validated in the client process, from the IANA DNSSEC root trust anchor compiled into the binary, because our own resolver never sets the AD bit and a tool that read AD would be trusting the wrong thing.

$ whisper whale whois 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
WHOIS
target           2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
address          2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
name             ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online
name proof       DNSSEC-validated here, from the IANA root
forward confirm  AAAA(ae3b051ff3bf7f478.tdc38e7c55bad3306a92b830f9bb1e4f9.agents.whisper.online) contains 2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
rdap handle      ae3b051ff3bf7f478
rdap name        scout
range            2a04:2a01:b69a:6717:e3b0:51ff:3bf7:f478
country          NL
status           active

  trust anchor: IANA DNSSEC root -> ip6.arpa -> the delegation that holds this /128
  validated by this client, in-process (our resolver never sets AD). Pass --json for the verbatim RDAP object.

forward confirm is the half that matters: a PTR alone proves nothing, because whoever holds the reverse tree writes it. Resolving the name it gives back and finding the original address in the AAAA is what makes it FCrDNS.

Naming, and what the resolver did with your query

$ whisper whale dns query whisper.online
DNS QUERY
question   whisper.online A
resolver   127.0.0.53:53  via the system resolver, no Whisper profile applied on this host
rcode      NOERROR  43.1 ms
answer     whisper.online. 300 IN A 78.141.218.153
           whisper.online. 300 IN A 45.32.186.184
policy     none  the resolver answered normally and attached no policy signal
graph      NONE  coverage known-clean

  policy is the resolver's own signal, read off the wire. graph is the assessment the resolver consults, not a second verdict computed here.

policy and graph are deliberately two fields. One is what the resolver did; the other is what the graph knows. Collapsing them into a single verdict would hide the case where they disagree, which is exactly the case worth seeing.

Ways out, and what ranks them

$ whisper whale exit-node list
CANDIDATE           ASN       DENSITY  RTT      NOTE
ns2.whisper.online  AS219419  unknown  35.3 ms  the graph holds no routed space for this ASN, so there is no density to compute - absence of a signal, not a clean bill of health
ns1.whisper.online  AS219419  unknown  35.3 ms  the graph holds no routed space for this ASN, so there is no density to compute - absence of a signal, not a clean bill of health
  These are the Whisper egress points this node can use today. A PEER-advertised exit node is kernel-tier only and needs a direct node-to-node path to EVERY node that would use it, which this build does not have for the general case: many pairs get one, but a pair with a port-varying NAT at both ends still relays and a pair split across two boxes has no path at all. Density is the graph's listed-IP ratio for the ASN, read from asnThreatDensity().densityRatio and explain().breakdown.graphDensityRatio - never from the prose in an explanation.

unknown is a deliberate value, not a missing one. An ASN the graph holds no routed space for has no density to compute, and rendering that as 0.00 would read as a clean bill of health for something nobody has looked at.

Subnet routes

A Whisper agent peer is bound to exactly one /128, and that is a security property rather than a limitation to flag away: it is what stops one agent sourcing another's identity. A subnet router is therefore a separate class of peer, kernel-tier only, and direct-path only - two customers routinely advertise the same 10.0.0.0/8, and one shared box cannot hold both.

$ whisper whale route list
PREFIX  SOURCE  DIRECTION
-       -       nothing routed
  Nothing is connected, so this node routes nothing into a tunnel. Run `whisper connect` first.
  No subnet route is advertised, and none can be in this build: run `whisper whale route advertise <prefix>` to see exactly what is missing.

SSH without trust-on-first-use

whale ssh checks the host key in this process against the SSHFP record published for the node, validated from the IANA root trust anchor compiled into the binary. It is not ssh -o VerifyHostKeyDNS=yes: that reads the AD bit, our resolver never sets AD, and it would print a DNS match and then prompt you anyway.

There is no trust-on-first-use. If no SSHFP is published, or the offered key does not match one, the connection is refused and the message says which. Who may log in is decided on the target, by its authorized-keys command reading a DNSSEC-signed ACL, so a removed principal is denied there and the denial lands in the target's auth log. --explain names both halves without connecting.

Coming from Tailscale

whale migrate plan reads their tailnet and writes a plan file. It changes nothing, on either side. Read the plan before you apply it: it enumerates every write in the order it will happen, and names what mapped exactly, what mapped approximately, what mapped to something strictly broader than their rule, and what cannot map at all. A rule that would widen is refused until you pass --accept-widening, and that consent is written into the plan file so it can be audited later.

The Tailscale credential is read from the environment (TS_API_KEY, or TS_OAUTH_CLIENT_ID + TS_OAUTH_CLIENT_SECRET), never from a flag. It is never written to the plan, never logged and never echoed.

Serving a port to your fleet, or to the internet

serve puts a local port behind this node's own /128, on the TLS leaf the published TLSA record already pins - a caller verifies it from the IANA DNSSEC root, no CA and nothing to install. By default only your fleet is answered; the port is still globally routable, so a stranger who reaches it gets a 403 and nothing else.

$ whisper whale serve 8123 --yes
SERVING
scope             fleet - your fleet only
url               https://a93d11ccec4de521a.t00000000000000000000000000000001.agents.whisper.online/
origin            http://127.0.0.1:8123/
address           2a04:2a01:0000:0001:93d1:1cce:c4de:521a
name              a93d11ccec4de521a.t00000000000000000000000000000001.agents.whisper.online
identity headers  on
since             2026-08-30T05:21:53+01:00
pid               2617173
  callers outside your fleet get a 403. Ctrl-C stops it.

A caller outside the fleet gets exactly that, with a reason rather than a bare status line:

$ curl https://a93d11ccec4de521a.t00000000000000000000000000000001.agents.whisper.online/
this port is served to its fleet only, and 2001:db8:24d:5d00::dedb is not in it

Every request that does reach the origin carries who called, established by Whisper rather than claimed by the caller: Whisper-Client-Address always, and Whisper-Agent-Address/-FQDN/-Owner plus Whisper-Assess-Band/-Coverage when they could be proven. Every Whisper-* and Tailscale-* header the caller itself sent is stripped first, so an origin can trust Whisper-Agent-FQDN by its presence alone; --compat-headers also emits the Tailscale-User-* spellings, for an app mid-migration. whisper whale serve status shows what a host is serving; whisper whale serve off stops it immediately.

funnel is the same mechanism with the fleet check removed: the whole internet is answered, not just your fleet. Because the /128 is already globally routable there is no propagation wait and no certificate-authority rate limit to hit - going public is a decision, not a deployment, which is why it asks you to type yes (or pass --yes) rather than just doing it. One caveat: a caller that is itself a Whisper /128 in another tenant is still governed by the east-west plane above, so it can be refused before it ever reaches you while the rest of the internet is answered normally.

Both verbs serve on :443 because _443._tcp.<fqdn> TLSA is what makes the leaf verifiable, and both accept a target liberally - 3000, :3000, localhost:3000, [::1]:3000, http://127.0.0.1:3000/api all work. The three /.well-known/ identity paths stay Whisper's own and are never proxied through.

Installing, and removing

One command in, one command out, and the same pair on Linux and macOS:

curl -fsSL https://get.whisper.online | sh          # install
curl -fsSL https://get.whisper.online/uninstall | sh # remove

On Windows:

irm https://get.whisper.online/install.ps1   | iex          # install
irm https://get.whisper.online/uninstall.ps1 | iex          # remove

macOS also has a signed and notarized installer package, for people who would rather double-click than paste a command. The uninstaller above knows that layout too: it removes the /opt/whisper payload, stops the host sensor through the CLI's own service uninstall, forgets the package receipt, and withdraws this machine's /128 first so nothing keeps routing after the software is gone.

Next

Connect & egress - the three tiers a single agent uses to put its traffic on the wire · Verify an agent - the same checks whale whois runs, done by hand with dig and curl.