Local Push Connectivity - Unreliable Connection

Hi! My project has the Local Push Connectivity entitlement for a feature we have requiring us to send low-latency critical notifications over a local, private Wi-Fi network.

We have our NEAppPushProvider creating a SSE connection using the Network framework with our hardware running a server. The server sends a keep-alive message every second. On an iPhone 16 with iOS 18+, the connection is reliable and remains stable for hours, regardless of whether the iOS app is in the foreground, background, or killed.

One of our QA engineers has been testing on an iPhone 13 running iOS 16, and has notice shortly after locking the phone, specifically when not connected to power the device seems to turn off the Wi-Fi radio. So when the server sends a notification, it is not received. About 30s later, it seems to be back on. This happens on regular intervals.

When looking at our log data, the provider does seem to be getting stopped, then restarted shortly after. The reason code is NEProviderStopReasonNoNetworkAvailable, which further validates that the network is getting dropped by the device in regular intervals.

My questions are:

  1. Were there possibly silent changes to the framework between iOS versions that could be the reason we're seeing inconsistent behavior?
  2. Is there a connection type we could use, instead of SSE, that would prevent the device from disconnecting and reconnecting to the Wi-Fi network?
  3. Is there an alternative approach to allow us to maintain a persistent network connection with the extension or app?

Observing this too. Unclear what's happening.

First off, I need to start with a general warning/comment. I've been supporting our voip APIs since iOS 4 and, across that time, there is one lesson I've seen OVER and OVER again:

WiFi network are terrible.

More specifically:

  1. Many "basic" WiFi network (like home WiFi routers with a single AP) have significant issues (for example, double NAT) which the user is unaware of because they don't effect things web browsing.

  2. Large scale network have exactly the same problem, except they also have serious issues with coverage gaps and/or lower level configuration issue that don't come up in #1.

Understanding this reality is critical because the other lesson I've learned is that "socket based voip" (our old voip sockets and now NEAppPushProvider) only work well on high quality networks. Note that this reality is what directly lead to PushKit, as the biggest reliability benefit PushKit provides is that push delivery over cellular makes it more likely that the push will reach the device.

Next, I need to comment on this:

The server sends a keep-alive message every second.

Again, my experience has been:

  • On a properly configured network where "everything" works, this kind of frequency is totally unnecessary. The network should be able to keep an idle connection "live" for minutes or even hours.

  • On a network that has problems, it's only marginally helpful, if at all.

My questions are:

Were there possibly silent changes to the framework between iOS versions that could be the reason we're seeing inconsistent behavior?

This isn't a framework issue as such. NEAppPushProvider's underlying implementation isn't all that complicated- check the SSID when joining the network, launch the app extension if it matches. All of the complexity here is in the network and driver stack underneath it.

Lots of stuff obviously change across the broader system, so I also can't say that system doesn't have any role here. However, what I can say is:

  • I'm not aware of any specific bug/issue with the iPhone 13 that would match what you're describing.

  • I'm not aware of any issue* with iOS 16 that would match what you're describing.

*One small exception to that. While looking into this, I did find one bug from iOS 16 about use disassociating at lock from AP with an internetworking IE configuration that indicates it's a battery powered hotspot. If that's what you're specifically testing with, then that might be a factor here.

Is there a connection type we could use, instead of SSE, that would prevent the device from disconnecting and reconnecting to the Wi-Fi network?

No, I wouldn't expect that to make any difference. You're already communicating once per second so as far the device is concerned that's an active connection. There isn't any way to be "more" active, and I suspect the actual issue here is much lower in the network stack.

Is there an alternative approach to allow us to maintain a persistent network connection with the extension or app?

Unfortunately, the place to start here is with the network, not the device.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

There are no issues if the device is unlocked and/or plugged into power, which led us to believe that the connection is dropped due to the system falling asleep. Connection to other things like Console.app log streaming seems to be dropped as well.

By the way, these devices only have Wi-fi enabled and no cellular (no SIM attached). Could that also have something to do with this situation? And are there anything useful to pull out from the device logs that can help us with this?

Thanks in advance,

We’ve noticed brief connection drops only after an iPhone has been locked and asleep for a while—almost as if the Wi-Fi radio powers down and the access point simply queues traffic until the device wakes. When the phone is unlocked, the connection remains solid.

Our use-case requires the link to stay alive continuously, even during extended sleep. Are there any iOS settings (or other device-level options) that can keep Wi-Fi awake under all circumstances?

Beyond Wi-Fi configuration itself, are there iPhone- or iOS-specific factors that might influence connection stability while the device is locked? We don’t see the same behaviour on Android handsets connected to the exact same network.

By the way, these devices only have Wi-fi enabled and no cellular (no SIM attached).

Yes, that's the scenario that tends to have the most problems.

Could that also have something to do with this situation?

Only indirectly. Having an active cellular radio tends to mask these issues by hiding WiFi issues.

And are there anything useful to pull out from the device logs that can help us with this?

The word "anything" is what's tricky there. Yes, I've definitely seen cases were sysdiagnose data helped identify and resolve issues. The problem is that this is true simply because the sysdiagnose provides a highly detailed "window" into exactly what's happening on the device, not because it provides a clear/quick/easy answer.

What I can't provide is a detailed guide to what you should look for or a systemic approach to this kind of investigation.

Our use-case requires the link to stay alive continuously, even during extended sleep. Are there any iOS settings (or other device-level options) that can keep Wi-Fi awake under all circumstances?

No, not that I'm aware of.

Beyond Wi-Fi configuration itself, are there iPhone- or iOS-specific factors that might influence connection stability while the device is locked?

No, nothing I can specifically point to.

However, there is one point I do want to comment on from the original post:

One of our QA engineers has been testing on an iPhone 13 running iOS 16

How well "WiFi-only" device/apps work definitely varies between iOS releases. If this particular use case is critical to your product, my recommendations are:

  • Limit your supported hardware/system range to something you can proactively validate and continually test. There may vary well be system versions and/or devices that simply aren't reliable and the best answer for those cases may simply be to not support them at all.

  • Focus that testing on new/future release, not just shipping system. Whenever possible, devices should not be upgraded until that particular device/system configuration has been validated.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi @DTS Engineer ! As we've taken some time to deep-dive into this, we're running into a dead-end here and are hoping to seek further guidance. To clarify, when our test iPhones (iPhone 15 on iOS 18.5, and iPhone 16 Pro on iOS 18.4) are locked and the app is backgrounded, the app extension will stop receiving the keep-alive messages over the SSE connection. Given our solution supports first responders, it is critical that the connection is stable to allow critical notifications to arrive as quick as possible.

If the app does not receive the keep-alives from our local server within 5s, it tears down the connection and establishes a new one.

While testing at home on an Eero router and TP Link Deco XE75 Pro, I have been unable to reproduce any issues with it. I will leave the phone, unplugged, with the app running in the background and it works as expected.

Given this, we determined our issue was likely with the vendor who supplies our customer routers, which are Cradlepoint IBR900s. We have been working with the Cradlepoint engineering team to investigate a possible root cause.

However, to help debug, Cradlepoint also purchased a TP-Link XE75 Pro in their lab to test with, along with our local server and they are able to replicate the issue within 1min of locking their iPhone.

I ended up changing my wi-fi name to kick all devices off the network except for my iPhone 16 Pro (cellular and wifi connections) and the server connected via LAN to the TP-Link. Now, every time I lock the phone, within 1min it sends a push notification indicating it lost connection and then immediately reconnects.

Given this, it seems like the behavior is not unique to a particular router after all, but has something to do with the iPhone's power saving mechanism given it's only when the phone is locked and not on power.

We have tested with iPhone 13 on iOS 16.7 (wifi only and no other apps), iPhone 15 on iOS 18.5 (wifi only and no other apps), iPhone 16 Pro on iOS 18.5 (cellular, many apps, and wifi assist turned on/off), and iPhone 14 Pro on iOS 18.5 (cellular, many apps, and wifi assist turned on/off).

I am suspecting that the small keep-alive packets going from the server to the phone every second are too small to keep the phone from powering down the wifi radio, yet there is some other device, or traffic, on my home network that presumably keeps the phones active that has no other applications installed but our app from testflight.

For our implementation, we were originally using URLSession to establish the connection and parse the SSE events, but have since moved to a custom solution leveraging Apple's Swift-NIO library. Though SSE is uni-directional connection, we also tried enabling keep-alives from the phone, with the thought that it would help generate traffic to keep the phone active.

  1. Do you have any ideas as to what might be keeping the wifi radio active when the phone is locked?
  2. Do you have any information around how the phone's power saving mechanism works?
  3. You seemed to indicate before that this framework tends to only work properly on "high quality networks." Could you elaborate on what makes a high quality network?
  4. Are there specific network settings recommended by Apple for this framework to work right? Do you know of a router that you have tested with in the lab that works reliably?
  5. Do you suggest we open up a code-level support request via our developer account to get further assistance on this?
  6. Is there anything else you haven't already mentioned that might be relevant here with our setup?

Thank you again for all of your support on this. Our partners at Cradlepoint are eager to collaborate with our company to find a solution to this as well, so please let us know if there is any other information we can provide on this.

I was on vacation last week and am still catching up, so my response here will not be complete.

So, first and foremost, do you have these issues with our sample project?

If the app does not receive the keep-alives from our local server within 5s, it tears down the connection and establishes a new one.

Ruling an issue out, how frequently is the server emitting its keep-alive? One slightly less obvious detail here is that in a naive implementation where one device emits a keep-alive "every 5s" and the other requires a keep-alive "every 5s", you can create a situation where what you're actually depending on/measuring is the consistency of latency between the devices, NOT the reliability of the connection itself.

Putting that in concrete terms, imagine a connection with a latency of "2s" (I'm using a very large latency to make the math simpler). Your communication then looks like:

  • Time 0s-> Server sends.

  • Time 2s-> Client receives. Next reply must arrive at 2s + 5s-> 7s.

  • Time 5s-> Server sends.

  • Time 7s-> Client receives. Next reply must arrive at 7s + 5s-> 12s.

  • Time 10s-> Server sends. Network conditions temporarily reduce latency to 1s.

  • Time 11s-> Client receives. Next reply must arrive at 11s + 5s-> 16s.

  • Time 15s-> Server sends. Network conditions normal.

  • Time 16s-> Client fails to receive expected keep alive.

  • Time 17s-> Client receives

It's possible that your logic is much more sophisticated than that, but it's important to rule out obvious issues.

Also, not that you need to be sending actual data, not simply TCP Keep Alive headers (see the sample for an example).

Next, I need to clarify what you actually mean here:

Now, every time I lock the phone, within 1min it sends a push notification indicating it lost connection and then immediately reconnects.

What component actually sent that push? More specifically:

  1. Is your push provider extension still running, as it's the component that sent the push?

  2. Did some other component/mechanism send that push based on detecting that the extension was not longer running?

The difference is critical here because your extension’s life cycle is directly tied to iOS’s WiFi state, so if your push provider is running then the WiFi network is still "up", at least as far as iOS is concerned.

For our implementation, we were originally using URLSession to establish the connection and parse the SSE events, but have since moved to a custom solution leveraging Apple's Swift-NIO library.

The article "Maintaining a Reliable Network Connection" explicitly states that you should be using the Network framework:

"As shown in the previous section, your client extension creates its connection to your server in your implementation of the push provider’s startWithCompletionHandler: method. Use the Network framework to create and maintain this connection."

Note that part of the reason the Network framework is important is that it gives you direct control over what interface your connection is using. That's obviously important in a provider extension.

Though SSE is a uni-directional connection, we also tried enabling keep-alives from the phone, with the thought that it would help generate traffic to keep the phone active.

And what happened? What did both sides of the connection see? And have you tried monitoring traffic on both the WiFi and Ethernet links?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Local Push Connectivity - Unreliable Connection
 
 
Q