Push notifications not delivered over Wi-Fi with includeAllNetworks = true regardless of excludeAPNS setting

We have a VPN app that uses NEPacketTunnelProvider with includeAllNetworks = true. We've encountered an issue where push notifications are not delivered over Wi-Fi while the tunnel is active in a pre-MFA quarantine state (tunnel is up but traffic is blocked on server side), regardless of whether excludeAPNS is set to true or false.

Observed behavior

Wi-Fi excludeAPNS = true - Notifications not delivered

Wi-Fi excludeAPNS = false - Notifications not delivered

Cellular excludeAPNS = true -	Notifications delivered

Cellular excludeAPNS = false - Notifications not delivered

On cellular, the behavior matches our expectations: setting excludeAPNS = true allows APNS traffic to bypass the tunnel and notifications arrive; setting it to false routes APNS through the tunnel and notifications are blocked (as expected for a non-forwarding tunnel). On Wi-Fi, notifications fail to deliver in both cases.

Our question

Is this expected behavior when includeAllNetworks is enabled on Wi-Fi, or is this a known issue / bug with APNS delivery? Is there something else in the Wi-Fi networking path that includeAllNetworks affects beyond routing, which could prevent APNS from functioning even when the traffic is excluded from the tunnel?

Sample Project

Below is the minimal code that reproduces this issue. The project has two targets: a main app and a Network Extension. The tunnel provider captures all IPv4 and IPv6 traffic via default routes but does not forward packets — simulating a pre-MFA quarantine state. The main app configures the tunnel with includeAllNetworks = true and provides a UI toggle for excludeAPNS.

PacketTunnelProvider.swift (Network Extension target):

import NetworkExtension

class PacketTunnelProvider: NEPacketTunnelProvider {

    override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
        let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")

        let ipv4 = NEIPv4Settings(addresses: ["198.51.100.1"], subnetMasks: ["255.255.255.0"])
        ipv4.includedRoutes = [NEIPv4Route.default()]
        settings.ipv4Settings = ipv4
        
        let ipv6 = NEIPv6Settings(addresses: ["fd00::1"], networkPrefixLengths: [64])
        ipv6.includedRoutes = [NEIPv6Route.default()]
        settings.ipv6Settings = ipv6
        
        let dns = NEDNSSettings(servers: ["198.51.100.1"])
        settings.dnsSettings = dns
        settings.mtu = 1400

        setTunnelNetworkSettings(settings) { error in
            if let error = error {
                completionHandler(error)
                return
            }
            self.readPackets()
            completionHandler(nil)
        }
    }

    private func readPackets() {
        packetFlow.readPackets { [weak self] packets, protocols in
            self?.readPackets()
        }
    }

    override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        completionHandler()
    }

    override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
        if let handler = completionHandler {
            handler(messageData)
        }
    }

    override func sleep(completionHandler: @escaping () -> Void) {
        completionHandler()
    }

    override func wake() {
    }
}

ContentView.swift (Main app target) — trimmed to essentials:

import SwiftUI
import NetworkExtension

struct ContentView: View {
    @State private var excludeAPNs = false
    @State private var manager: NETunnelProviderManager?

    var body: some View {
        VStack {
            Toggle("Exclude APNs", isOn: $excludeAPNs)
                .onChange(of: excludeAPNs) { Task { await saveAndReload() } }
            Button("Connect") { Task { await toggleVPN() } }
        }
        .padding()
        .task { await loadManager() }
    }

    private func loadManager() async {
        let managers = try? await NETunnelProviderManager.loadAllFromPreferences()
        if let existing = managers?.first {
            manager = existing
        } else {
            let m = NETunnelProviderManager()
            let proto = NETunnelProviderProtocol()
            proto.providerBundleIdentifier = "<your-extension-bundle-id>"
            proto.serverAddress = "127.0.0.1"
            proto.includeAllNetworks = true
            proto.excludeAPNs = excludeAPNs
            m.protocolConfiguration = proto
            m.localizedDescription = "TestVPN"
            m.isEnabled = true
            try? await m.saveToPreferences()
            try? await m.loadFromPreferences()
            manager = m
        }
        if let proto = manager?.protocolConfiguration as? NETunnelProviderProtocol {
            excludeAPNs = proto.excludeAPNs
        }
    }

    private func saveAndReload() async {
        guard let manager else { return }
        if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol {
            proto.includeAllNetworks = true
            proto.excludeAPNs = excludeAPNs
        }
        manager.isEnabled = true
        try? await manager.saveToPreferences()
        try? await manager.loadFromPreferences()
    }

    private func toggleVPN() async {
        guard let manager else { return }
        if manager.connection.status == .connected {
            manager.connection.stopVPNTunnel()
        } else {
            await saveAndReload()
            try? manager.connection.startVPNTunnel()
        }
    }
}

Steps to reproduce

  1. Build and run the sample project with above code on a physical iOS device. Connect to a Wi-Fi network.

  2. Set excludeAPNS = true using the toggle and tap Connect.

  3. Send a push notification to the device to a test app with remote notification capability (e.g., via a test push service or the push notification console).

  4. Observe that the notification is not delivered.

  5. Disconnect. Switch to cellular. Reconnect with the same settings.

  6. Send the same push notification — observe that it is delivered.

Environment

iOS 26.2

Xcode 26.2

Physical device (iPhone 15 Pro)

while the tunnel is active in a pre-MFA quarantine state

I’d like to clarify my understanding of this. First up, what does “MFA” stand for in this context?

Second, it sounds like your concerned about this sequence:

  1. The system wants to bring up the tunnel.
  2. So it instantiates your provider and calls the startTunnel(…) method.
  3. Your provider opens a connect to your VPN server.
  4. And then calls setTunnelNetworkSettings(…) to bring up the tunnel.
  5. However, the tunnel isn’t really up at this point, in that your VPN server won’t forward packets to the networks that the tunnel claims.

Is that right?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

MFA stands for Multi-Factor Authentication. But that detail isn't critical here — the key point is simply that the tunnel is up and established successfully (startTunnel completes without error, setTunnelNetworkSettings is applied), routing is in place, but the server is not forwarding traffic. The sample code I shared reproduces this exact state — it's a loopback tunnel that never forwards packets.

The core issue remains: with includeAllNetworks = true, push notifications are not delivered over Wi-Fi regardless of the excludeAPNS setting, while on cellular they work as expected when excludeAPNS = true.

Push notifications not delivered over Wi-Fi with includeAllNetworks = true regardless of excludeAPNS setting
 
 
Q