Amazon.co.uk Widgets

Log in

X
Chrome Local Network permission on macOS

 

Chrome Local Network Permission on macOS

What It Is and Why It Can Be So Hard to Diagnose

Starting with Chrome 130, Google introduced a new permission prompt that asks users whether a website should be allowed to access devices on their local network. This change aligns Chrome's behavior with the privacy goals outlined in the Private Network Access specification, a W3C proposal designed to prevent malicious websites from reaching routers, printers, smart home devices, and other hardware that sits behind a firewall.

On macOS, this permission works alongside the operating system's own network permission layer, which means users and administrators can find themselves troubleshooting problems that involve two entirely separate permission systems at once. The result is a diagnostic challenge that is easy to underestimate.

TL:DR – If you find this because Google sent you here after a few hours of staring at `ERR_NAME_NOT_RESOLVED`, `ERR_ADDRESS_UNREACHABLE`, or `net::os_error: 65` (ENETUNREACH) when Chrome can't reach a host on your own network, scroll to "The fix" at the bottom. The first 80% of this post is the diagnostic journey, because the journey is the lesson: the obvious suspects were all innocent, and the real cause was a quietly broken state in macOS itself.

What the Permission Actually Controls

When a web page attempts to make a request to an IP address in a private range, such as 192.168.x.x, 10.x.x.x, or 172.16.x.x through 172.31.x.x, Chrome evaluates whether the site has been granted local network access. If the site has not been granted permission, Chrome blocks the request and may show a prompt asking the user to allow it.

This affects a wide range of real-world use cases, including:

  • Web-based interfaces for routers and network-attached storage devices
  • Developer tools that serve a local preview on a loopback or LAN address
  • Enterprise web applications that communicate with on-premises hardware
  • Home automation dashboards that talk to local hubs

Why Diagnosis Is Difficult on macOS

Two Permission Layers, One Symptom

The core difficulty is that a blocked local network request in Chrome on macOS can be caused by at least two independent systems:

  1. Chrome's Private Network Access permission, managed within the browser itself
  2. macOS Local Network privacy controls, found in System Settings under Privacy and Security

Both systems can produce a silent failure or a vague network error in the browser. A developer or IT administrator who sees a failed fetch request to a local IP address may not immediately know which layer is responsible, and the browser's developer console does not always make the distinction obvious.

The macOS Local Network Permission

Apple introduced the Local Network privacy permission in macOS 10.15 Catalina. It requires applications to declare that they need local network access and prompts the user to approve it. Chrome must hold this macOS-level permission before it can even attempt to reach a local network address. If the user previously denied this permission, or if it was never granted, Chrome's own permission prompt becomes irrelevant because the operating system blocks the traffic before Chrome can act on it.

Users can review and modify this setting by handling to System Settings, then Privacy and Security, then Local Network, and checking whether Google Chrome is listed and enabled.

Accumulated and Conflicting Policy Files

A particularly thorny source of confusion on macOS is the presence of enterprise policy files that were installed for earlier versions of Chrome. Chrome on macOS reads managed policy from several locations, including:

  • /Library/Managed Preferences/com.google. Chrome.plist
  • /Library/Preferences/com.google. Chrome.plist
  • Profiles delivered through a mobile device management (MDM) system

Organizations that deployed Chrome policies in the past, perhaps to control update behavior, disable certain features, or configure proxy settings, may have policy files on disk that were never cleaned up. When Chrome 130 or later reads those files, older or conflicting entries can interact unexpectedly with the new Private Network Access controls.

For example, a policy file written for Chrome 100 might contain a URLBlocklist entry or a PrivacySandboxPromptEnabled flag that was appropriate at the time but now interferes with how Chrome evaluates network permissions. Administrators who are unaware these files exist may spend considerable time adjusting Chrome settings in the browser or in a current MDM profile without realizing that a stale plist file on disk is overriding their changes.

You can inspect the effective policy state at any time by typing chrome://policy into the Chrome address bar. Any active policies, including those sourced from local files, will appear there along with the source that provided them.

The setup that cause the issue for me

I run a self-hosted Gitea, an open-source Git service, on a Mac Studio at home, served as a Homebrew's service and listening on port 3000 over HTTPS. It's mapped to the same url for use both internally and externally. Internally, my devices have an `/etc/hosts` entry (yeah I know I'm old  school, Sun Microsotyems ran the whole company on a single hosts file in 1989).  It points the hostname to its LAN IP `192.168.0.x`; externally, public DNS resolves to my home WAN IP and a NAT rule forwards 3000 to the Mac Studio.

It works in every browser, on every machine, from inside the house and outside. Except for one specific case: Chrome on my MacBook. Firefox on the same MacBook works fine. The `git` CLI on the same MacBook works fine. Chrome on every other machine works fine. Just this one combination fails with:

 

> This site can't be reached
> https://git.multizone.co.uk:3000/angusf/linen is unreachable.
> ERR_NAME_NOT_RESOLVED

The first hour: every red herring you can think of

Naturally I start with the obvious things, in this order:

  1. DNS. `nslookup git.multizone.co.uk` returns the public IP. But Firefox + git CLI work fine — they're reading `/etc/hosts` and getting `192.168.0.x`. So DNS itself isn't broken on the machine.
  2. Chrome's Secure DNS / DoH. A common gotcha: Chrome bypasses the OS resolver via DoH and queries Cloudflare/Google directly, which doesn't know about your private hosts. I turned it off in `chrome://settings/security` → Use Secure DNS → Off. No change.
  3. Async DNS resolver flag (`chrome://flags/#enable-async-dns`). Removed in Chrome 124+ — it's now the default and there's no toggle. Dead end.
  4. HSTS / HTTPS-First Mode. Maybe Chrome is forcing HTTPS-upgrades and the parent domain `multizone.co.uk` is HSTS-preloaded. Checked `chrome://net-internals/#hsts` — nothing.
  5. Cache poisoning. Cleared host cache, flushed socket pools at `chrome://net-internals/#dns` and `#sockets`.
  6. Profile state. Tried Incognito. Tried a Guest profile. Both failed identically.
  7. Reinstalled Chrome. From scratch. Still failed.
  8. Router? Tested with `nc -vz 192.168.0.4 3000` — connection succeeded. The kernel can route. So routing isn't broken.

By this point I'd ruled out:

  • DNS (LAN, OS, and DoH layers)
  • HSTS / TLS / HTTPS-First Mode
  • Browser profile / cache state
  • Network routing
  • Chrome itself (reinstalled)

I was running out of things to blame.

The breakthrough: capture a netlog

The single most useful thing I did all session. `chrome://net-export/` → start logging → reproduce the failure → stop. Open the file in an editor.

Buried in the events for the failed `URL_REQUEST`:

 

```
TCP_CONNECT_ATTEMPT to 192.168.0.4:3000
  os_error: 65    (ENETUNREACH — "Network is unreachable")
  net_error: -109 (ERR_ADDRESS_UNREACHABLE)
```

 

Two things became unmissable at once:

  1. DNS resolution was fine — the netlog clearly showed Chrome reading `/etc/hosts` and getting `192.168.0.4`. The omnibox saying `ERR_NAME_NOT_RESOLVED` was just Chrome's misleading surface-level error code.
  2. The kernel itself returned ENETUNREACH on `connect()`. But `nc` from the same machine to the same address worked. So the kernel was rejecting the syscall *only when it came from Chrome*.

That's a per-process network filter. On macOS Tahoe, that has a small set of plausible causes: a third-party network extension, an MDM-installed configuration profile, packet filter rules, or TCC's "Local Network" privacy permission (introduced in Sequoia, tightened in Tahoe).

What macOS Local Network actually does

Since Sequoia, an app needs explicit user consent to connect to RFC1918 addresses (your LAN). Without it, `connect()` calls to local IPs fail at the kernel level — and macOS surfaces the failure as `ENETUNREACH`, which looks identical to a routing problem. That's why every "DNS/router/firewall" debugging instinct steers you wrong.

Apps you launched from Terminal (`nc`, `git`, `curl`) inherit Terminal's grant, so they work. GUI apps each need their own.

Check the macOS Local Network Permission - The real culprit: stale duplicate TCC entries

Conventional diagnostic would be to open System Settings, handle to Privacy and Security, and select Local Network. Confirm that Google Chrome appears in the list and that its toggle is enabled. If Chrome does not appear, try visiting a local network address so that macOS has an opportunity to prompt for the permission.

When I checked System Settings → Privacy & Security → Local Network, Chrome was listed there. Allowed. Done, right?

No. There were two Chrome entries. Both toggled on. That should have been my flag earlier, but I dismissed it as cosmetic.

Each TCC entry is keyed by the app's code signature. When you uninstall and reinstall Chrome (especially across major versions), the signature can change. macOS doesn't always replace the old entry — it adds a new one and keeps both. The app firewall pane confirmed the same picture: two Chrome entries.

The kernel evaluates the currently running binary's signature against TCC. If, due to caching or a mismatch between what's installed and what's recorded, none of the duplicates match cleanly, the syscall is denied. Toggling them on does nothing because the kernel isn't matching them to the running process.

The cleanup that actually fixed it

Diagnostics first to rule everything else out:

 

# Third-party network extensions (Little Snitch, LuLu, NextDNS, ESET, NordVPN, etc.)
systemextensionsctl list

# MDM-installed configuration profiles
sudo profiles -P

# Packet filter rules
sudo pfctl -sa | head -40

 

 

All three came back clean for me. Just a Dymo printer driver, no profiles, no PF rules.

That meant the problem really was Chrome's TCC state. The fix:

Verified FIX

  1. Quit Chrome fully. Cmd+Q, then check Activity Monitor for any `Google Chrome Helper*` strays.
  2. Remove every Chrome entry from System Settings → Network → Firewall (with the `–` button) and from System Settings → Privacy & Security → Local Network. Empty the list.
  3. Reset all of Chrome's TCC permissions so old records are wiped:
    tccutil reset All com.google.Chrome
  4. Delete Chrome and its Chrome-only data (leave shared `Google` parent folders alone — Drive, Earth, Keystone all live there):
    rm -rf "/Applications/Google Chrome.app"
    rm -rf ~/Library/Application\ Support/Google/Chrome
    rm -rf ~/Library/Caches/Google/Chrome
    rm -rf ~/Library/Saved\ Application\ State/com.google.Chrome.savedState
    rm -f ~/Library/Preferences/com.google.Chrome.plist
    rm -rf ~/Library/HTTPStorages/com.google.Chrome*
    rm -rf ~/Library/WebKit/com.google.Chrome
    rm -rf ~/Library/Cookies/com.google.Chrome.binarycookies
  5. Reboot. This is the step I'd skipped for hours and one that actually really matters. The kernel caches code-signature evaluations and TCC decisions; without a reboot, the old denial is still in memory even after you've deleted the records that produced it. A logout might be enough; I went with a full reboot.
  6. After reboot, install Chrome fresh from chrome.com.
  7. Open your LAN URL. macOS prompts: "Google Chrome would like to find and connect to devices on your local network." Allow
  8. Worked instantly.

Signposts for future me (and you)

If you're staring at `ERR_NAME_NOT_RESOLVED` or `ERR_ADDRESS_UNREACHABLE` from Chrome on macOS Sequoia/Tahoe, only for LAN hosts:

  • Always grab a netlog before you start guessing. `chrome://net-export/` is the single most efficient debugging tool. Look at the actual `os_error` from `TCP_CONNECT_ATTEMPT`. If it's `65` and `nc` from Terminal works, this whole post is for you.
  • `ERR_NAME_NOT_RESOLVED` from Chrome lies. It uses that code for several distinct underlying failures. It does not necessarily mean DNS.
  • `ENETUNREACH` from a LAN connect is almost certainly TCC, not routing. The OS deliberately surfaces TCC denials as routing-style errors.
  • More than one entry for the same app in any privacy/security/firewall list is a code-smell. Macros: it should be one. If you see two or three, your reinstall left orphan records and the kernel might not be matching the running binary to any of them. Delete them all and let macOS prompt fresh.
  • Reboot after wiping TCC state. The kernel caches; the GUI doesn't tell you that.
  • Don't trust the nuke-and-reinstall step alone. Without the reboot in between, the old kernel state survives the reinstall.
  • `tccutil reset All com.google.Chrome` is heavier than `tccutil reset LocalNetwork com.google.Chrome` but worth using when LocalNetwork-only fails (it sometimes does, with `Failed to reset` errors). You'll re-grant camera, mic, screen, etc. on next use, but Chrome doesn't lose any user data.
  • Diagnose third-party filters first with `systemextensionsctl list`, `sudo profiles -P`, and `sudo pfctl -sa`. Faster than a reinstall when those find something.

For Administrators Managing Chrome at Scale

If you are deploying Chrome across multiple macOS machines through an MDM such as Jamf Pro or Microsoft Intune, consider the following:

  • Audit existing policy payloads and remove or update any Chrome configuration profiles that were created for versions significantly older than the current release.
  • Use the PrivateNetworkAccessRestrictionsEnabled policy, introduced in Chrome 130, to control how Private Network Access restrictions are enforced across your fleet. Setting this to false disables the restrictions entirely, which may be appropriate in a trusted enterprise environment.
  • Use the PrivateNetworkAccessRestrictionsAllowedForUrls policy to grant specific origins access to local network resources without disabling the feature globally.
  • Ensure that your MDM configuration also grants the macOS Local Network permission to Chrome at the system level, since user-facing prompts may not appear in managed environments.

Google documents these policies in the Chrome Enterprise policy list, which is maintained at the Chrome Enterprise Help Center and updated with each major release.

The lesson - Its NOT always DNS

The thing that wasted hours fixing Google Chrome on macOS Tahoe wasn't difficulty — it was that the obvious suspects (DNS, routing, certificates, the router, the hosts file) were all superficially plausible and all wrong.

The Chrome Local Network permission on macOS sits at the intersection of browser-level controls and operating system privacy enforcement, and it is made more complicated by the potential presence of policy files installed for earlier versions of Chrome. When a local network request fails, the cause might have been the macOS Local Network privacy toggle, Chrome's own permission state, a current enterprise policy, or a stale plist file that nobody remembered was there.

Modern macOS quietly intercepts `connect()` at the kernel level for privacy reasons, and when its records get out of sync with the installed binary it fails closed in a way that mimics three or four other classes of bug.

If you take one thing from this: on a modern Mac, when one specific GUI app can't reach a LAN host that everyone else can reach, suspect TCC's Local Network permission record before anything else, and reset it from a clean reboot.

The router was innocent the whole time.