The tunnel opens when you request one internal name and stays shut when the next app needs it just as badly. That is not a bug in the app; it is the shape of a domain rule. Apple's on-demand configuration describes conditions for making a connection, and the domain form of those conditions asks the system resolver a question before a single packet moves. Two questions get conflated constantly: when the tunnel should come up, and what the tunnel carries once it exists. The first is a trigger, the second is a route and resolver policy, and one profile usually sets both in the same payload, which is why a correct domain rule can still leave you on the wrong network.

Trigger and traffic selection sit at different layers

An on-demand rule lives in the on-demand dictionary of a VPN payload: an ordered list of rules, each with an action and a set of match conditions. The system evaluates them when connectivity changes or when something tries to reach the network, and the action decides whether the tunnel is brought up, evaluated, disconnected, or ignored. All of this happens in the platform's networking layer, before your app has a socket.

What happens after that is a separate group of keys: the tunnel's IPv4 and IPv6 settings, its included and excluded routes, its DNS server addresses, and on iOS and iPadOS the per-app mapping that decides which apps may use the tunnel at all. A profile can pair a broad trigger with a narrow route, or a narrow trigger with a default route. Read only one half and the other half looks like a malfunction.

I keep a lab profile that connects whenever anything under an internal domain is requested but routes only one /16 into the tunnel. It does exactly what its payload says, and it is also the kind of profile that generates tickets: people assume a connected tunnel means their traffic is inside it.

What a domain rule actually asks the resolver

The domain form of the rule, often labelled connect-if-needed, does not inspect packet payloads or watch URLs. When a name is resolved, the system compares it with the domains in the rule and, on a match, asks whether that name can be resolved through the DNS servers the tunnel provides. If the tunnel's resolver answers, the name counts as internal and the connection is started. If the local resolver answers instead, the name is treated as external and no tunnel appears. Apple's deployment overview describes these as network detection followed by connection evaluation.

Two consequences follow. The profile has to name DNS servers inside the tunnel; a domain rule with no tunnel resolver has nothing to compare, so the evaluation cannot produce the match you expect. And the lookup has to reach the system resolver. An app or browser that runs its own encrypted DNS, an extension that answers locally, or a connection to a literal address never produces the name resolution the rule was waiting for. One of the longest afternoons I have spent ended when I found the client in front of the tunnel was using DNS over HTTPS.

Caching removes the trigger as well. A name already in the cache may be answered without the probe that would have started the tunnel, so a rule that worked yesterday fails after a reboot, or after the tunnel has been up for a while. On macOS, sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder resets the state; on iOS, toggling airplane mode or restarting the device gets you a comparable clean slate.

Match scope: names, apps, and networks

Domain matching is one type among several. A rule can also match the interface type (Wi-Fi, cellular, Ethernet) and a specific SSID, and per-app or Safari-domain entries bind a tunnel to one workflow. That binding explains the odd experience where only one app behaves differently from everything else: the other apps are outside the tunnel's scope, so they keep using the local path even while the tunnel icon says connected.

Order matters more than people expect. Rules are evaluated in sequence and the first one that matches supplies the action, so a broad connect-on-any-Wi-Fi rule placed above a narrow domain rule will win silently and make the domain rule untestable. Before you rewrite the domain list, confirm that no earlier rule already matched.

This is also why always-on is not just a stricter on-demand rule. Always-on enforcement is a separate deployment mode with its own supervision and management requirements; it holds a tunnel open instead of reacting to a name. If what you need is a tunnel that never drops, a domain rule is the wrong tool.

Failure modes that imitate a broken rule

Most reports of a domain rule not working land in one of five places, and only the last of them is the rule text.

What you seeLayerFirst check
Tunnel comes up, internal site still failsroute or resolver after connectnetstat -rn, scutil --dns
Tunnel never comes up for that nametrigger pathfresh lookup, cache flush, private DNS
Tunnel comes up for every appscope, or an earlier rulerule order, per-app mapping
Tunnel drops and will not return on the same Wi-Fiinterface or SSID match, admissioncaptive portal, SSID string, auth prompt
Tunnel returns after you disconnect itprofile ownershipprofiles show

Captive portals deserve their own line, because hotel and airport Wi-Fi intercepts the very lookup the rule depends on. Until the portal admits the device, the tunnel's DNS server is unreachable, the probe fails, and the failure looks like a misspelled domain. Authentication is the other silent case: the rule can fire, the tunnel can start connecting, and the connection dies on an expired certificate or a prompt that appears on another display.

IPv6 causes a variant that is easy to misdiagnose. A name with both address families can be answered from an AAAA record first, while the tunnel's route list carries only IPv4. One browser succeeds, another fails, and the domain list is entirely innocent.

Test with a real request, in this order

Read the policy before guessing at it. On macOS, sudo profiles show -type configuration prints installed profiles including the VPN payload, scutil --nc list shows service state, and scutil --nc show <service> prints the on-demand dictionary the system is using. On a supervised iPhone or iPad the same payload arrives from the management console or an MDM report. Networking logs are available with log stream --predicate 'subsystem == "com.apple.networkextension"' on macOS, and through the device console when the phone is attached.

Then test with a hostname that was not queried earlier in the session. Pick a name in the tunnel's DNS zone that you know resolves internally, request it once, and watch whether the tunnel appears. Run the same lookup twice: the second attempt exercises the cache path, and the difference between the two tells you whether the trigger is the resolution or the routing.

After the tunnel is up, verify the two layers separately. netstat -rn -f inet and netstat -rn -f inet6 show where each prefix points, scutil --dns shows resolver order and which interface owns each resolver, and nettop -m route shows which interface carried the bytes. If the tunnel is up and the resolver is correct but the resource still fails, stop editing the rule; check server-side authorization and the return route.

When the rule belongs to someone else

If the tunnel reappears after you disconnect it, the rule usually belongs to a profile rather than an app: an MDM-pushed configuration, a leftover personal profile, or a profile installed by another client. sudo profiles show -type configuration plus a search for the on-demand keys will narrow it down, and the removal path is the profile, not the toggle in the menu bar. Deleting the service entry or the app leaves the rule behind, and the next installation inherits it again.

A single test settles ownership: disconnect, wait, request the internal name again. If the tunnel returns, the trigger is working as designed and the open question is who manages it and whether that scope is what you want.

Before filing this as a bug, work through the same five checks: tunnel resolver addresses present in the payload, a name that was not cached, no earlier rule that already matched, an AAAA path the route list covers, and the app inside the tunnel's scope. When all five hold, the rule is doing its job and the fault is somewhere further along the path.

Makomi ya ndenge moko