Antivirus, VPNs, and Corporate Proxies
Security products interpose on exactly the things a mesh agent depends on: they re-sign TLS connections, pull traffic into their own tunnels, and filter packets. On a machine running one — most corporate laptops, and any consumer machine with a security suite — noBGP works, but a few products need to be told about it. This page covers what to exclude, per vendor, and how to recognize which product is interfering.
What noBGP traffic looks like
Everything the agent does is outbound. It never needs an inbound port opened.
| Traffic | Protocol | Destination |
|---|---|---|
| Control channel + mesh | QUIC, UDP 443 | your router (e.g. router.nobgp.com) |
| Fallback control channel + mesh | TLS (wss), TCP 443 | same |
| Install and upgrades | HTTPS, TCP 443 | downloads.nobgp.com |
If UDP 443 is blocked, the agent detects it and falls back to TLS over TCP on its own — a firewall that drops UDP costs some latency, never connectivity.
From agent 0.4.162 a node on the fallback moves back to QUIC on its own. While its control channel is on wss, the agent makes one QUIC handshake with the router on a widening schedule — first about five minutes after it landed on the fallback, then at doubling intervals up to an hour — and only when that answers does it register again to use it. So allowing UDP 443 afterwards is picked up without a restart, and a node that fell back over a moment of blocked UDP, or over a slow name lookup while it was starting, does not stay on TCP for the life of its connection. The move is one re-registration of the control channel: the node can show as offline for about a second, and control_reconnects goes up by one. nobgp status shows the move as router.transport. ⚠ A node the router itself keeps on wss is tried once and then left alone until its network, its certificate pin or its QUIC endpoint changes, or until its control connection is re-established for another reason, so the retry cannot cost it a registration an hour forever.
Either row can travel over IPv4 or IPv6, and by default the agent tries both and takes whichever answers first. From agent 0.4.113 a node can be told to lead with one family and use the other only as a fallback — transport-family — which is the setting for a machine whose own hardware or uplink misbehaves on one of them, rather than for a filtering product.
⚠ A host firewall no longer filters traffic from other nodes, from agent 0.4.140. A peer's TCP, UDP and ping arrive as a local connection the agent opens on the machine, so Windows Firewall, ufw, firewalld and macOS pf see local traffic rather than traffic from the network, and another node reaches what a program on the machine reaches — a service bound to 127.0.0.1 included. This is the intended behaviour and there is no setting that narrows it; what limits who reaches a machine is which network its node is in. See How another node reaches this machine's own services.
Recognizing the interference
| Symptom | Likely cause |
|---|---|
| Certificate error during install or upgrade | HTTPS inspection (antivirus "web shield" or corporate proxy) re-signing the download connection |
Windows: the install command answers Access is denied (in PowerShell: Program 'powershell.exe' failed to run: Access is denied), or it installs without the registration key | Microsoft Defender refused an older install command, the one that ends in iex — see Windows Defender |
| Node registers and shows online, but connections to peers stall or drop | A VPN client's virtual adapter pulling routes (its adapter usually shows a 169.254.x.x or its own tunnel address) — or, on Linux, another product's firewall rule sitting ahead of noBGP's in INPUT, or its routing rule taking noBGP's whole address range into another routing table, see Other VPN clients |
| Node goes offline for half a minute to two minutes whenever a VPN connects or disconnects, then comes back on its own | The machine's route to noBGP moved to another interface; agents through 0.4.167 waited out the transport deadline, see Node Goes Offline for a Minute or Two After a VPN or a Network Change |
| Node stays online after a VPN comes up on the machine, but every overlay packet is lost until the agent is restarted | The route moved onto a path whose MTU is smaller than the one the control connection was made on; agent 0.4.183 kept that connection, and agent 0.4.184 replaces it, see Node Goes Offline for a Minute or Two After a VPN or a Network Change |
Node shows offline in noBGP for hours while nobgp status on the machine reports router.connected: true on wss, and only a restart recovers it | Something terminating the WebSocket — an egress gateway or an inspecting proxy — answers noBGP's keepalives after its own connection onward is gone; agents through 0.4.175 never noticed, see A Node Reads Connected While Nothing Reaches It |
Agent always on wss, never quic | Outbound UDP 443 blocked — harmless, see above. On agent 0.4.162+ the node keeps checking, so one that stays on wss for hours with UDP 443 open has another cause: transport: wss in its configuration, an HTTP proxy between it and the router, a router that keeps it on wss, or an older agent. On agents through 0.4.168, also a verdict recorded while a full-tunnel VPN held the route, which stayed in force for up to an hour after that VPN was gone, see Other VPN clients |
| The whole machine loses DNS whenever the noBGP agent stops | /etc/resolv.conf is generated by another overlay product whose resolver has noBGP as its only upstream, so noBGP is what resolves everything on that machine, see Other VPN clients |
| Another overlay product reports that its resolver configuration was changed | noBGP writes its own nameserver on the first line of /etc/resolv.conf, which that product generates; both programs are working as intended, see Other VPN clients |
| Overlay names resolve only about two lookups in three, on a machine running another overlay product | Agents 0.4.169 and 0.4.170 left that product's /etc/resolv.conf alone and let it answer for the overlay zone, see Other VPN clients |
nobgp status reports which transport the node is actually using (router.transport), and recent agents also flag detected TLS interception and foreign VPN adapters directly in the status output.
What to exclude
You never need to disable a security product. Three narrow exclusions cover everything:
- Exclude from HTTPS/SSL scanning:
downloads.nobgp.comand your router's domain (router.nobgp.comunless self-hosted). Inspection proxies replace the server certificate, which breaks the installer's certificate validation. - Allow the agent through the firewall: the
nobgpbinary (/usr/local/bin/nobgp,C:\Program Files\nobgp\nobgp.exe), outbound TCP 443 and UDP 443. - If a VPN client is active: add noBGP to its split-tunnel exclusions so overlay traffic doesn't get pulled into the VPN's tunnel.
Per-vendor notes
Norton 360 / Norton Security
Two separate components interfere, and they produce different symptoms:
- Web/Mail Shield re-signs HTTPS (its root certificate is literally named "generated by Norton Antivirus for SSL/TLS scanning") — this is the install-time certificate error. Settings → Antivirus → Scans and Risks → Web/Mail Shield → exclude
downloads.nobgp.comand your router domain, or disable HTTPS scanning. - Norton VPN installs Wintun/OpenVPN adapters that grab routes — this is the "online but peers flaky" symptom. Either enable split-tunneling for nobgp under the VPN's settings, or turn the VPN component off.
- Norton Firewall: Settings → Firewall → Traffic Rules → allow
nobgp.exe, outbound TCP/UDP 443.
Windows Defender
Defender does not inspect TLS and normally coexists cleanly. Three known cases:
-
During install, real-time scanning of the freshly written binary can delay the service's first start by a few seconds. No action needed; the installer waits.
-
If Controlled Folder Access is enabled, allow
nobgp.exeunder Ransomware protection → Allow an app. -
Defender can refuse the older install command, the one that ends in
| iex. Defender judges the text of the command before anything is downloaded, so the installer and the agent are not involved. You see one of these results:Access is denied, and nothing is installed. In PowerShell the full text isProgram 'powershell.exe' failed to run: Access is denied. Windows Security → Protection history showsTrojan:Win32/Commando.A!mlfor the command line.- In Windows PowerShell 5.1, the install runs but ignores the registration key, and the installer asks you to sign in.
- In Windows PowerShell 5.1, the older command without a key can stop with
Cannot bind argument to parameter 'Command' because it is an empty stringorMissing closing '}'.
To repair an older command, change its last word from
iextoInvoke-Expression, and keep the rest of the command as it is. The commands on Installation and fromregister_nodeend in| Invoke-Expression. Defender accepted that form in our tests on Windows 11, from Command Prompt and from Windows PowerShell 5.1. You do not need to turn Defender off or add an exclusion. ⚠ The verdict comes from a Defender model, so a Defender update can change it.
Corporate proxies (Zscaler, Netskope, Palo Alto, Cisco Umbrella)
These re-sign all TLS, with a root certificate your IT department deployed via MDM — so connections succeed, silently inspected, on managed machines. noBGP still works, but ask IT to add downloads.nobgp.com and your router domain to the SSL-inspection bypass list: inspection middleboxes buffer long-lived connections and can drop the agent's control channel on idle. On an unmanaged machine behind such a proxy, the missing root causes the same certificate errors as consumer antivirus — the bypass list is the fix there too.
A proxy that terminates the WebSocket answers noBGP's keepalives, and through agent 0.4.175 that could leave a node stuck. On the WebSocket transport the agent knew the router was there from WebSocket pongs, which such a gateway sends from its own side — including after its own connection onward to the router has gone. The node then showed offline in noBGP while nobgp status on the machine read router.connected: true, with nothing in the agent's log and no reconnect, until somebody restarted it. From agent 0.4.176 the agent asks the router for an answer only the router can give and closes the connection when it does not come, so such a node recovers in about a minute and a half. There is nothing to exclude for it; adding your router domain to the SSL-inspection bypass list keeps the control channel off that path altogether. See A Node Reads Connected While Nothing Reaches It.
A gateway that ends the WebSocket at a fixed age no longer takes the node offline each time, from agent 0.4.177. Some egress paths cut a connection once it reaches a certain age, whatever it is carrying — measured at about five minutes on one node — and keepalives do not help, because the limit is on the connection's age and not on how idle it has been. The agent now learns that age from two such losses and registers again shortly before it, while the old connection is still carrying traffic, so the node stays online across the cut; control_reconnects in nobgp status then rises by one every few minutes, which is the rotation working. Before, each cut cost the node 68 to 97 seconds offline on agent 0.4.176, and hours on anything older. From agent 0.4.184 the sessions on such a path survive the cut as well: the channel that carries traffic to another node, and the one that carries commands, file transfers and terminals, are WebSocket sockets of their own through the same gateway, so each was cut at the same age and the session on it was dead until its own deadline — an ssh over the overlay broke every few minutes. Each is now replaced shortly before the limit while it is still carrying traffic. Adding your router domain to the bypass list avoids it altogether. See Node Drops Offline for a Minute Every Few Minutes Behind an Egress Gateway.
Zscaler Client Connector and similar agents also install a packet-filter tunnel; if peers are unreachable, ask IT to add the node's overlay slice to the tunnel's exclusions. nobgp show reports it — it is a /20 of 100.64.0.0/10 on nearly every machine, and from agent 0.4.161 it can also be a /20 of 172.16.0.0/12 or 10.0.0.0/8.
macOS: MDM profiles, Little Snitch, LuLu
- Managed Macs trust MDM-deployed inspection roots, so installs succeed even under inspection — see the corporate-proxy section above.
- Little Snitch / LuLu prompt on the agent's first outbound connection. Approve
nobgpfor TCP and UDP 443 to your router domain anddownloads.nobgp.com. If you dismissed the prompt, the agent appears installed-but-offline; open the firewall's rule list and allownobgp.
macOS: the built-in packet filter (pf)
macOS ships pf, its built-in packet filter, disabled, and from agent 0.4.110 noBGP leaves that decision to you. The agent writes one anchor file for its overlay interface (/etc/pf.anchors/nobgp-<device>, a single permissive pass in quick on lo0 rule): if pf is enabled the rule is loaded, and if pf is disabled the agent notes it in its log at debug level and runs without it. Nothing about connectivity, peers or the overlay depends on that rule — it permits traffic, it does not isolate anything — so both states work.
⚠ Agents through 0.4.109 could switch pf on. When the anchor could not be loaded because pf was off, the agent ran pfctl -e and retried, and nothing ever turned it back off — stopping or uninstalling the agent removes only its own anchor. So a Mac that ran one of those agents while pf was off can have pf enabled, applying whatever else /etc/pf.conf carries.
⚠ An enabled pf is not proof that noBGP enabled it. macOS enables pf at boot on some machines, and other network tools enable it too. The check below reports the state and never says who set it — and where this was measured on Macs running the agent, pf was already on at boot and the agent's enable path had never run. Read the state first. Disable pf only if you know that nothing else on the machine needs it.
sudo pfctl -s info | head -1 # "Status: Enabled" or "Status: Disabled"
sudo pfctl -d # disable it — only if nothing else on this Mac needs it
Upgrading to 0.4.110 does not change an already-enabled pf either way. Deliberately: switching a filter off on a machine whose rules you may since have come to depend on is your call, not the agent's.
Other VPN clients (WireGuard, Tailscale, OpenVPN, vendor VPNs)
noBGP coexists with other overlay networks — it detects a VPN already holding an address in a range it could use, and reads the machine's routing table so it can pick its own /20 around whatever the other product has already claimed. Interference, when it happens, is route-based: a full-tunnel VPN that claims the default route can swallow overlay traffic. Prefer split-tunnel mode, or exclude the agent's slice (nobgp show) from the VPN's routes.
Turning such a VPN on or off moves the route to noBGP, and through agent 0.4.167 the node went offline while that happened. The control connection was made on the route the machine had at the time, nothing told the agent the route had moved to another interface, and the connection then sat until the transport's own deadline — up to two minutes. From agent 0.4.168 the agent notices the move and reconnects in less than half a minute, keeping the connection when it still works on the new path. See Node Goes Offline for a Minute or Two After a VPN or a Network Change. It is not interference and there is nothing to exclude for it: the route genuinely moved. From agent 0.4.169, when the agent closes the control connection for a move, it also asks the channels that carry commands, file transfers and terminals on the new path, and remakes the ones that do not answer, so a call made in the seconds after the move is no longer lost while one of them waits out its own deadline.
⚠ A tunnel whose MTU is smaller than the uplink's needs agent 0.4.184. The question the agent asks on the new path is now sent at the size of the packets its connection actually sends, so a route that cannot carry them reads as a path to replace rather than one to keep. On agent 0.4.183 the question was a small packet, which such a path answers: measured on a Mac where WireGuard (link MTU 1420) came up under a connection made on a 1500-byte link, the node stayed online while every overlay packet was lost, and only restarting the agent recovered it. Split-tunnelling the agent's traffic avoids the move altogether; upgrading is the fix when the tunnel carries it.
A QUIC attempt lost through such a VPN no longer keeps the node on the WebSocket fallback, from agent 0.4.169. noBGP's control channel prefers QUIC, and a node that finds UDP blocked remembers that verdict for an hour — against the network it is on, which it identifies from the machine's physical interfaces and their addresses. A full-tunnel VPN or an exit node changes none of those while it holds the default route, so an attempt lost through the tunnel marked the physical network and the node stayed on the WebSocket fallback for up to an hour after the tunnel was gone. The path the tunnel carries is now part of what identifies the network, so the verdict belongs to it: turning the tunnel off is a different network, and QUIC is tried again on the next connection. A machine with no such tunnel is identified exactly as before. nobgp status reports the verdict as router.udp_blocked.
Where those routes live matters, and through agent 0.4.110 the agent looked in one place. On Linux it read only the main routing table, while products that use policy routing — Tailscale among them — keep their routes in a table of their own and reach them with a rule that sits ahead of main. Such a claim was invisible: the agent took 100.64.0.0/20, the kernel went on sending that range to the other tunnel, and the node registered and reported healthy while no peer was reachable. From agent 0.4.111 every routing table is read, so a partial claim is dodged properly — and a route over the whole of 100.64.0.0/10 left no /20 to take at all. From agent 0.4.112 such a node comes up anyway, without an overlay: peers are unreachable, which is what the condition means, but the control channel, command, file, the shared drive and nobgp status all keep working — so the machine can be fixed remotely, which 0.4.111 alone could not be, since it exited on this verdict and stayed down. nobgp status names the reason under network.overlay.
From agent 0.4.161 a claimed range is skipped and the agent has two more to try. A product that routes a whole range owns it on that machine, so the agent takes its /20 from 172.16.0.0/12 or 10.0.0.0/8 instead of sharing — on every platform, and the other product keeps every one of its peers. A Mac that started the agent while Tailscale was connected therefore has an overlay again. ⚠ The upgrade moves the slice on such a machine, which changes every peer's address there (names are unaffected), and only a machine where all three ranges are claimed still comes up without an overlay. Agent 0.4.160 instead made macOS and Windows take a /20 inside that product's range, on the grounds that those systems route by longest prefix — which is why that /20's traffic then reached noBGP rather than the other product. See Overlay addressing for the three ranges and the rules for each.
A route the other product adds inside noBGP's slice moves the slice, from agent 0.4.161. A product started after the agent installs one route per peer rather than one wide route, and such a route inside the node's /20 used to be read as the agent's own leftover: the slice was kept and the noBGP peer at that address became unreachable. The agent now picks another slice at its next start. See Peers Are Unreachable on a Machine Running Another Overlay VPN.
The Linux firewall is the second place they meet, and agent 0.4.159 is where noBGP stopped losing it. Another product's rule in the kernel's INPUT chain can drop the replies to connections a program on this machine makes to a noBGP peer — the reply arrives on noBGP's own overlay interface carrying the peer's overlay address as its source, which looks to that rule like unsolicited traffic from the CGNAT range. Measured on 1 October 2026 against Tailscale 1.102.4 at its defaults, whose INPUT rule drops 100.64.0.0/10 arriving on anything but its own interface: every outbound overlay TCP connection timed out, and the machine's own resolution of noBGP names went with it.
Two things changed in the agent's own chain, and the second has several properties worth knowing:
- The agent accepts its slice arriving on its overlay interface, which is what the reply to a locally-opened connection looks like. ⚠ This opens nothing to a peer: since agent 0.4.144 nothing a peer sends travels through that interface at all — a peer's traffic arrives as a local connection the agent opens — so the rule only ever matches the return half of a flow this machine started.
- The agent keeps its own jump first in
INPUT, checking once a minute and moving it back when a rule belonging to another program has got ahead of it. Starting first is not enough, because the other program can be restarted at any time:systemctl restart tailscaledput its chain back at the top and took the overlay dark again until the agent itself was restarted. - A new interface or a new address on the machine also starts a check, from agent 0.4.166 — a quarter of a second after the kernel reports it, then again one, three and ten seconds later. A product that writes to
INPUTusually brings its own interface up in the same moment, so the order is repaired within a second of that rather than at the next minute's check. Measured against each start oftailscaledon three machines: outbound overlay connections and the machine's own resolution of noBGP names were dark for 38 to 60 seconds before this. The minute check stays, for a program that changesINPUTand brings up no interface. - ⚠ The checks that follow a report have a budget. A machine that gets a new interface every few seconds — one starting containers in a loop — would otherwise read
INPUTcontinuously. Eight checks may follow reports, and each minute's own check gives one back, so on such a machine the repair settles at about one more check a minute than the regular one until the changes stop, and a machine that has been quiet for a few minutes has the whole budget ready for the next program that starts. - It names no product. The check is only is another program's rule ahead of ours, so it covers any firewall manager or VPN that writes to
INPUT; a sibling noBGP profile's chain is not treated as another program. - It writes nothing while the order holds — one read of
INPUTa minute (two on a node that also has an IPv6 overlay slice), plus the reads that follow a new interface or address, with no change and no log line. A move is logged once for each rule that was ahead, then at debug level, so a product that re-asserts its own position on a schedule does not fill the log. - ⚠ It moves the jump and nothing else. A chain another program deleted is not rebuilt until the agent restarts, and a host where the agent cannot read
INPUTat all keeps the rules it installed and no reordering. - A check that is running when the agent stops finishes first, from agent 0.4.175, and the stop then removes what it did — so an agent that has left the machine cannot leave its jump behind, which a check landing after the stop's own cleanup used to be able to do.
- ⚠ It sees only the iptables
INPUTchain. A product that drops the range in its own nftables chain on the input hook (Tailscale in nftables mode is one) drops those replies whatever the order inINPUT: an accept ends one chain, and a drop in any chain on that hook is final. A machine with noiptablescommand at all, or with the legacy one beside a program that writes nftables rules, is in that state. Measured against such a rule placed by hand in a table of its own: every IPv4 connection the machine started to a peer was lost, with the agent's own jump first in its own chain and nothing in the agent's log.
From agent 0.4.172 the agent finds that out and moves its slice out of the way. It cannot read every table of every firewall, so it asks the kernel instead, and what it learns decides where the slice goes:
- It asks rather than reads. No rule is read and no product is named: the agent sends one UDP datagram from its own overlay address to an address inside its own slice, then writes the answer to that datagram into the overlay interface and looks for it on the socket that sent it. That answer passes every chain on the input hook, whoever wrote them. Nothing leaves the machine.
- The datagram is the answer to a flow this machine started, which is why an ordinary stateful firewall is not reported:
firewalldin its default zone, and every "default deny, accept established" rule set, lets a reply in while dropping a new flow from outside. - Three probes, 35 seconds apart, must all fail, the first ten seconds after the agent starts. A shorter window than the minute between the
INPUTorder checks above would report a state the agent repairs by itself, and one lost datagram on a busy machine is not a firewall. A probe that could not be made at all is not an answer and is not counted. - The slice then moves out of
100.64.0.0/10— to172.16.0.0/12or10.0.0.0/8, by the rules in Overlay addressing — and the agent starts its overlay again, once. The measured drop is of the CGNAT range, which is the range other overlay products use and filter. ⚠ The move renumbers every peer's overlay address on that machine; names are unaffected, andnobgp showreports the new slice. - The log names the rule that would keep the slice where it is: accept, on the overlay interface, from the slice, ahead of whatever drops it. A machine that has to stay in
100.64.0.0/10— one whose other firewall rules name that range — is fixed that way instead of being moved. - ⚠ At most one move is ever made. A slice outside
100.64.0.0/10is not moved again, because nothing says another range passes — so a rule that drops more than the CGNAT range leaves the node with the warning and the slice it has. A machine with no free/20in another range keeps its slice too: a filtered slice still carries what peers start and the node's IPv6 overlay, and no slice carries nothing. - A node configured entirely from its environment gets the warning and nothing else, since it has nowhere to record a new slice and nothing restarts it with one.
- The verdict is kept beside the profile for 30 days, so a restart does not land back in the range that drops it. After that a new pick may try
100.64.0.0/10again — a firewall rule can go away — while a node that already has a slice keeps the one it has.
They also meet on the lock that guards the rules, and agent 0.4.175 is where noBGP stopped losing that race. Docker, Tailscale, k3s and firewalld take the kernel's xtables lock while they write rules, and most builds of the legacy iptables fail immediately rather than wait when another program holds it (Another app is currently holding the xtables lock, or Can't lock /run/xtables.lock: Resource temporarily unavailable, exit 4 — measured on 1.8.4, 1.8.7 and 1.8.11). The agent read that failure as it reads a host with no usable firewall at all, with two consequences: at startup it carried on without the accept rule for its own overlay interface, so on a machine whose other rules drop the overlay range every connection this machine started to a peer timed out until the agent was restarted; and at shutdown its flush could fail, and an existence check that hit the lock read the same as this chain is not there, so the chain and its jump could be left on the machine with nothing in the log saying so. A failed flush was seen on an upgrade to 0.4.174, where the next start rebuilt the chain. Every iptables and ip6tables call the agent makes now waits up to five seconds for that lock.
- The wait is bounded, never indefinite. Five seconds for the first command, then one second for each later command while the lock is known to be held. The bound is per command and the agent's own stop budget is per process — a teardown is about a dozen commands — so the shorter later wait is what keeps a held lock from making the stop take minutes and getting the agent killed before it closes its overlay interface. A plain
-wwaits for ever, which would hand the agent's start and its stop to whatever holds the lock. - A check that could not be made is no longer read as an answer. A removal now attempts the delete anyway and fails loudly if the lock is still held; the log line is
cannot tell whether <chain> exists: another program held the xtables lock for the whole wait. A decision that needs positive evidence — whether an older release left its own delivery rules behind — still acts only on a chain it could see. - ⚠ Two legacy builds behave the other way round, and the agent says so.
iptables-legacy1.8.9 and 1.8.10 wait for the lock with no-wat all, so there a command can now fail after five seconds where it used to wait longer; that is the price of a bound, and the failure is logged.iptables-nfttakes no such lock, so the wait changes nothing on an nftables host. - An
iptablestoo old for the wait is detected once and then left alone. Builds of 1.4.21 and older either do not know-wor take it without a number (CentOS 7's backported 1.4.21 does accept it). The agent notices the refusal, runs the command again without the wait, logsthis iptables does not accept -w 5; firewall commands run without a wait for the xtables lockonce, and remembers it — such a host behaves exactly as it did before.
Linux routing rules are the third place they meet, and agent 0.4.167 is where noBGP keeps its own range. The kernel reads policy routing rules in order of priority, and the first table holding any route for an address ends the lookup. A product that keeps its routes in a table of its own, reached by a rule ahead of the one for main, therefore takes noBGP's whole /20 with a route less specific than noBGP's — or with a plain default route, which is the layout of a VPN exit node — while the agent's own route for its slice sits in main the entire time. Measured against a table holding nothing but a default route behind a rule at priority 5270: a packet for a noBGP peer left on the other product's interface, and no peer was reachable from that machine.
- The agent asks the kernel where a packet for its slice goes, and installs a policy rule of its own —
to <slice> lookup main suppress_prefixlength 0— only when the answer is another table. It reads neither the other rules nor who wrote them, so it names no product and covers any of them. - ⚠ Nothing is installed on a machine that does not need it, deliberately. The rule sends the whole slice to
main, so where another product holds a route for one single address inside noBGP's slice — one of its own peers, since both products use100.64.0.0/10— that address would come to noBGP instead. While the slice as a whole is not taken, that product keeps that address and noBGP keeps the rest, which is how two overlays share a machine today. - The rule sits directly after the rule for the
localtable, never ahead of it. The slice holds this machine's own overlay address, and the agent's DNS server answers on it; ahead oflocal, a query to that address would be sent into the overlay interface and get nothing back. A rule of noBGP's that another program has moved ahead oflocalis moved back. - It is checked once a minute (twice on a node that also has an IPv6 overlay slice) and put back when another program removes it —
systemd-networkdremoves rules it did not write at each restart, with its defaultManageForeignRoutingPolicyRules=yes, andip rule flushremoves every rule on the machine. While the rule holds, the check reads the rules and writes nothing, logs nothing and starts no process. - Once the rule is in, it stays until the agent stops, which removes it: to know the rule is no longer needed, the agent would have to ask the kernel again without it. A rule left behind by a killed agent is removed at the next start, which then decides again from a clean machine.
- ⚠ When the rule changes nothing, the agent says so. Another program's rule can sit ahead of the position after
local, and the log then reads that the overlay does not work on this machine rather than reporting a repair. Each outcome is logged once for each slice and then at debug level, so a program that re-asserts its own rules on a schedule does not fill the log. - Linux only. macOS and Windows route by the most specific prefix, so a route of another product that is less specific than noBGP's slice never takes it there.
To see the rules on a machine, ip rule show; ip route show table all shows what each table carries.
DNS is the fourth place two overlays meet. Where the agent answers every query on the host, it forwards the ones that are not its own to the resolvers the machine already had — and it drops any of those that belong to an overlay, because two forwarders holding each other as upstream is a query loop that answers SERVFAIL and takes name resolution on the box down with it. Tailscale drops noBGP's resolver for the same reason. Through agent 0.4.78 only Tailscale's IPv4 MagicDNS address (100.100.100.100) was recognised, so a host where Tailscale had installed its IPv6 resolver instead (fd7a:115c:a1e0::53) could reach exactly that loop; from agent 0.4.79 the whole fd7a:115c:a1e0::/48 range is excluded, the way 100.64.0.0/10 always was.
Excluding the loop is not the same as having somewhere to forward to, and agent 0.4.81 closes the case where it is not. On a host whose only other resolver belongs to an overlay, dropping it leaves the agent with no upstream at all: it answered "not my name" and the resolver it declined to ask had noBGP as its upstream and nothing else, so the machine resolved nothing — including the router's own hostname. 0.4.79 turned that from an endless query loop into an immediate double failure, which is better and is still a box with no DNS. From 0.4.81 the agent measures before it decides: while any of the resolvers it declined to forward to can still resolve the router's name, "not my name" is a working answer and nothing changes — this is the ordinary Tailscale case, and it must keep working, because a MagicDNS name answered by a public resolver would come back as no such host rather than falling through to Tailscale. Only once none of them can answer does the agent forward out-of-zone queries to public resolvers (1.1.1.1, 8.8.8.8) — the state where nothing is being taken away, because nothing was resolving. It follows the machine in both directions: when the other resolver recovers, the agent goes back to leaving those names alone. On a healthy host the measurement never runs and costs no queries.
A name this machine already reaches is left to whatever reaches it, from agent 0.4.116. Before answering a bare name, the agent asks its own hosts file and the machine's own nameservers — never its own zone — and where they answer, noBGP's zone hands that answer back rather than an overlay address of its own, so the lookup takes that path. Through agent 0.4.138 it said not found instead and left your resolver to fall through, which a resolver that never asks the LAN for a bare name — systemd-resolved — could not do. On a host running a second overlay that means a name its resolver answers wins locally, including an address elsewhere in 100.64.0.0/10 where that product's peers live; only an address inside noBGP's own slice is refused as evidence, so noBGP's answer can never satisfy the check that decides whether to give it up. Other nodes' names are never given up this way — see A name this machine reaches itself.
Where the other product generates /etc/resolv.conf, noBGP is still first in that file, from agent 0.4.171. On a host with no systemd-resolved the first nameserver line is how the overlay zone reaches the agent. Agents 0.4.169 and 0.4.170 stayed out of such a file and let that product answer for the overlay zone on noBGP's behalf, which it cannot do reliably: it asks every upstream at once and returns the first answer, so a real upstream's no such name beat noBGP about one lookup in three (measured, 24 to 28 of 40 against 40 of 40 with noBGP's line first). Expect two things on such a host: the other product writes its file again and may report that its configuration was changed — the agent restores its line within a tenth of a second and the file settles in a second or a few — and everything outside the overlay zone still resolves through that product, because noBGP refuses those names and the machine's resolver falls through to the next line. ⚠ The one host where this costs something is one whose resolver has noBGP as its only upstream: there noBGP forwards out-of-zone names to public resolvers to keep the machine working, so the machine's DNS lasts only as long as the agent runs — measured, a node with no DNS for 14 minutes after an upgrade. The agent names that state in its log, with the resolvers holding the file; ending it means taking that program off /etc/resolv.conf or installing systemd-resolved. See When another overlay's resolver owns the resolver file.
If a node running Tailscale reports DNS failures for the router's own hostname, see Node Is Online but Every Command and File Call Fails.
Still stuck?
Run nobgp status. The agent reports interference in three different places, and which one is populated tells you what kind of problem you have:
| Where | Field | Means |
|---|---|---|
router | transport | What is actually carrying traffic (quic or wss). |
router | tls_intercepted | Something re-signed the control channel — antivirus HTTPS scanning or a corporate proxy. Names the certificate's owner. |
environment | tunnels | Another product's active VPN adapter, with the address it holds — the usual route thief. |
environment | security_products | Third-party antivirus or firewall registered with the OS. |
network | route | missing means overlay traffic is not going through the noBGP interface (Windows: another product's route is winning). |
transport answers for the process running right now. For what happened while nobody was watching, read the log: since agent 0.4.115 every successful registration writes one line naming the transport that carried it — control channel registered over wss (<address>) — so nobgp service logs shows, with a timestamp, when a node moved onto the fallback.
The environment block is deviation-only: it is omitted entirely when nothing was detected, so on a clean machine you will not see it at all. Its absence is a healthy result, not a missing field.
Stock Windows Defender is filtered out of security_products deliberately — this is a report of deviations from a normal machine, not an inventory of everything installed.
If the node shows online but a peer is unreachable, run nobgp status on both ends — the interference is usually on exactly one of them.