The symptom
You enable Wireless debugging on an Android phone, grab the IP and port, and run:
$ adb connect 10.0.0.126:42035
failed to connect to '10.0.0.126:42035': No route to hostOr you try adb pair and get a vaguer cousin of the same failure:
$ adb pair 10.0.0.126:39311 892426
error: protocol fault (couldn't read status message): Undefined error: 0Meanwhile the phone is demonstrably reachable. You're on the same Wi-Fi network, the macOS firewall is off, and there's no VPN running — yet both of these succeed against the exact same address:
$ ping 10.0.0.126
64 bytes from 10.0.0.126: icmp_seq=0 ttl=64 time=48 ms
$ nc -vz 10.0.0.126 42035
Connection to 10.0.0.126 port 42035 [tcp/*] succeeded!So the port is open, the host is up, and the route exists — yet adb insists there is “no route to host.” That contradiction is the whole story.
The misleading clue: ping works, adb doesn't
“No route to host” is the text for the EHOSTUNREACH socket error — the kernel telling a process it cannot reach the destination. Normally that means a real network problem. But here ping and nc reach the device in the same breath that adb cannot. The error is real, but it isn't about the network — it's per-application. One process on your Mac is allowed onto the local network and another is not.
That single observation — ping and nc succeed, adb fails, to the same IP and port — is the fastest way to stop blaming the phone and start looking at macOS.
Root cause #1: macOS Local Network Privacy
Starting with macOS 15 Sequoia (and unchanged through macOS 26), Apple gates access to the local network on a per-application basis. An app has to be granted permission before it can reach other devices on your LAN — the same model iOS has used for years. When an app is denied, its connections to local addresses fail (Apple TN3179: Understanding local network privacy).
adb is a third-party binary, so it is subject to this. The permission isn't attached to “adb” in isolation, though — it's attributed to the app that launched adb: Android Studio, Terminal, iTerm2, VS Code, whatever spawned the adb server. If that launching app was never granted Local Network access (or you once clicked Don't Allow), then every connection adb makes to a LAN address dies with “No route to host.”
And here is the part that sends everyone down a rabbit hole: Apple's own system binaries aren't gated the same way. ping and nc live in /sbin and /usr/bin, ship with the OS, and sail straight through. So your manual reachability tests pass while the one tool you actually care about is blocked — making it look like an adb bug or a phone problem when it is neither. This is a well-known macOS Sequoia regression for any dev tool that talks to the LAN (scrcpy #1341).
A 10-second test that proves it's not your phone
Point adb at something on your LAN that obviously isn't an Android device — your router:
$ adb connect 10.0.0.1:80
failed to connect to '10.0.0.1:80': No route to hostYour router's web port is open (nc -vz 10.0.0.1 80 succeeds), so if adb can't even reach it, adb can't reach anything on your network — the phone was never the problem. Once the permission is fixed, the very same command changes its error: the TCP connection now lands (the router simply isn't an adb device), and the No route to host is gone:
$ adb connect 10.0.0.1:80
failed to connect to 10.0.0.1:80 # reached it; just not an adb deviceThe shift from No route to host to a plain “failed to connect” is your green light: adb is back on the network.
The fix for Local Network Privacy
- Open System Settings → Privacy & Security → Local Network.
- Enable the toggle for the app that launches adb — Android Studio, Terminal, iTerm2, or your editor. Command-line adb often doesn't get its own row, because macOS attributes the permission to the launching app, not the CLI binary. Grant the launcher.
- Fully quit and reopen that app (⌘Q, not just close the window). A running app caches its Local Network decision; the new grant only takes effect on a fresh launch. This is the step people skip — and then conclude “the permission didn't help.”
- Restart the adb server so it picks up the change:
adb kill-server && adb start-server
If the toggle still seems to have no effect, switch it off and back on, then relaunch the app again — the cached decision is sticky (Praminda Jayawardana: Make Android Wireless Debugging Work on Mac). One more wrinkle: a reboot sometimes finally applies a grant that wouldn't stick otherwise.
Note that USB debugging is never affected by any of this — Local Network Privacy only governs the network. If you just need the device connected and don't care how, a USB cable sidesteps the entire problem.
Root cause #2: the device won't auto-discover — the Openscreen mDNS trap
Permission fixed, you reconnect — and a day later it's “gone” again. This is a second, unrelated trap. Android's Wireless debugging connect port rotates every time the phone reboots or you toggle the feature, so yesterday's adb connect <ip>:<port> silently points at a dead port. What's meant to paper over this is mDNS— the same zero-config protocol behind Bonjour — which lets the paired phone advertise itself as _adb-tls-connect._tcp so adb can find its current port and reconnect on its own.
adb has two mDNS backends, and on macOS they are not equal. Straight from adb's own docs:
“The default mDNS-SD backend is Bonjour (mDNSResponder). For machines where Bonjour is not installed, adb can spawn its own, embedded, mDNS-SD back end, openscreen.” (Android adb documentation)
- Bonjour hands discovery to the operating system's own mDNS daemon. On macOS that's
mDNSResponder, which is always running. - Openscreen is adb's own mDNS stack, bundled into the adb server so it can discover devices on Windows and Linux machines that have no system mDNS daemon. Support for it on macOS only arrived in platform-tools v35.
Here is why that second one misbehaves on a Mac. mDNS works by sending and listening for multicast on UDP port 5353, and that port really wants a single owner per machine. macOS always runs mDNSResponder, which already holds 5353. When adb's embedded Openscreen responder comes up alongside it, the two stacks contend for the same multicast socket — and the phone's announcements frequently get delivered to the system daemon instead of to Openscreen, so adb's discovery comes up empty (an mDNS primer on the 5353 multicast model). On Windows and Linux there's usually no always-on system responder to collide with — exactly the gap Openscreen was built to fill, and why it's fine there. Openscreen has also accumulated a track record of flakiness and outright crashes on the platform-tools tracker (Issue #294120933).
The result on macOS: adb mdns services returns nothing, even though your paired phone is right there advertising:
$ adb mdns services
List of discovered mdns services
# (empty — the phone is advertising, but Openscreen isn't seeing it)First, check which backend your adb server is actually on — and don't trust the “default is Bonjour” line, because recent platform-tools on macOS routinely come up on Openscreen anyway:
$ adb mdns check
mdns daemon version [Openscreen discovery 0.0.0] # <- the flaky pathIf you see “Openscreen,” force adb back onto macOS's native Bonjour stack — which simply asks mDNSResponder, the daemon that rightfully owns 5353, to do the lookup — with one environment variable (documented here):
export ADB_MDNS_OPENSCREEN=0 # 0 = Bonjour, 1 = OpenscreenPut that in your ~/.zshrc so every adb server you start uses the reliable backend. With it set, adb mdns services lists the phone consistently and auto-reconnect to a paired device actually works.
Two helpers worth keeping
Because the port rotates, hard-coding it is pointless. These two shell functions handle the day-to-day — drop them in ~/.zshrc under the export above:
# Reconnect to a paired device at whatever its current (rotated) port is:
adbw() { adb connect "$(adb mdns services 2>/dev/null | awk '/_adb-tls-connect/{print $NF; exit}')"; }
# Clear a wedged adb (zombie 'adb pair' / 'dns-sd' procs) and restart cleanly:
adbreset() { adb kill-server; pkill -f 'adb pair'; pkill -f 'dns-sd.*_adb'; adb start-server; }Day to day you just run adbw. One thing that trips people up: pairing persists across reboots — only the connect port changes. So you almost never need to re-pair; you just need to reconnect to the new port, which is exactly what adbw does. Reach for adbreset only when discovery genuinely wedges (it does occasionally, leaving orphaned adb pair processes behind).
The whole checklist
- Phone won't connect wirelessly? Run
adb connect <router-ip>:80. “No route to host” → it's Local Network Privacy, not the phone. - Grant Local Network to the app that launches adb, then quit and reopen it, then
adb kill-server && adb start-server. - Add
export ADB_MDNS_OPENSCREEN=0to~/.zshrcso discovery and auto-reconnect stop being flaky. - Pair once. After that, reconnect with
adbw— the port rotates, the pairing doesn't. - In a hurry and don't care about wireless? Plug in USB — it's immune to all of the above.
Sources
- Apple — TN3179: Understanding local network privacy
- Android Developers — Android Debug Bridge (adb): mDNS backends and
ADB_MDNS_OPENSCREEN - Fabien Sanglard — mDNS primer (multicast on UDP 5353)
- Google Issue Tracker #294120933 — adb Openscreen mDNS crashes
- scrcpy #1341 — “No route to host” on macOS
- Praminda Jayawardana — Make Android Wireless Debugging Work on Mac (Local Network permission)