Kirill Zonov

Networking behind iOS push notifications

December 10, 2025 | 4 Minute Read

Me trying to grasp the push notifications mechanic

This morning I was poking at my phone and suddenly realized: How do the push notifications actually work???

Does the server know my phone’s public IP? Does the phone poll every second? Is every app keeping its own connection open?

I had no answers. So I went down the rabbit hole — at least the networking side — and here’s the short version of what I learned.

Polling? Websocket? Nope, it’s not

iOS apps don’t poll Apple’s servers for push notifications. There’s no loop of “got anything for me yet?” It is quite natural to assume, and I honestly would be shocked if it were the case :) Nice that it’s not.

Then what is the mechanism?

Instead, there is an iOS system daemon called apsd (Apple Push Services Daemon) that opens one long-lived TLS connection to Apple’s Push Notification service (APNs). All apps on your phone share this connection. And the daemon knows a unique so-called Device Token for each app, that helps it to route the notifications to the correct app.

In fact, it is the same mechanism for the OSX.

Apsd process on OSX

Also worth mentioning that your backend never talks to the phone — only to APNs — and APNs pushes data into that already-open connection.

How does Apple reach my phone?

So how does APNs know where to send anything?

Most phones on mobile networks sit behind Carrier-Grade NAT (CGNAT) and do not have public IPv4 addresses.

So how can Apple “reach” your phone?

  • It is your phone that initiates an outbound TCP connection to APNs.
  • NAT on your ISP creates a mapping (public_port <-> private_port).
  • APNs simply sends packets back through the existing TCP session.
  • NAT on your ISP allows return traffic to your phone.

APNs never needs your public IP — the TCP socket is already open, authenticated, and routed. It is essentially the same mechanism as the one used for the web servers.

This is classic NAT behavior: outbound connection -> NAT entry -> return traffic allowed.

No polling. No IP-based routing. Just one long-lived socket.

Why the connection stays alive?

Mobile carriers aggressively drop idle connections, so iOS sends periodic keep-alive packets to prevent NAT expiration. Apple doesn’t document the exact intervals, but from what I found:

  • They are infrequent (battery-friendly)
  • They depend on network conditions
  • They ensure APNs can always reach the device

When you toggle Wi-Fi, turn on Airplane mode, or switch networks, apsd seems to reconnect automatically.

Can I see the connection?

Yes, you can definitely play with it!

One way would be to check the logs of the apsd process.

$ log stream --predicate 'process == "apsd"' --info

For me it showed an interesting detail:

Timestamp                       Thread     Type        Activity             PID    TTL  
2025-12-11 12:13:46.489671+0100 0x1215aa   Default     0x0                  360    0    apsd: (libxpc.dylib) [com.apple.xpc:connection] [0xc9732ca00] activating connection: mach=false listener=false peer=true name=com.apple.apsd.peer[49350].0xc9732ca00
2025-12-11 12:13:46.490382+0100 0xd5b      Default     0x0                  360    0    apsd: Failed entitlement check 'com.apple.private.aps-connection-initiate' for 49350
2025-12-11 12:13:46.490449+0100 0xd5b      Activity    0x1a5c23             360    0    apsd: (libsystem_trace.dylib) Activity for state dumps
2025-12-11 12:13:46.490439+0100 0xd5b      Fault       0x1a5c23             360    14   apsd: Peer connection [pid=49350] lacks APSConnectionInitiateEntitlement

The PID of 49350 is /System/Library/Frameworks/CoreTelephony.framework/Support/CommCenter, which seems to be somehow related to the cellular or eSIM connectivity. I couldn’t figure out why is it attempting to connect to APNs directly but I guess it is some sort of fallback mechanism or a legacy thing related to the daemon being present on both OSX and iOS.

You can also use the netstat command to see the connection:

$ netstat -anv | grep apsd

For me it produced the following output:

tcp4       0      0  <my local ip>.<ephemeral local port>    <apple APN ip>.5223      ESTABLISHED        17867       175129  131072  132240             apsd:<apsd PID>    00000 00000000 0000000000000000 00000000 00000000      0      0 000000

fd119f3c5f66d88b stream      0      0                0 4f2ee168ea61008f                0                0            4          164    8192    8192             apsd:<apsd PID>    00082 00000000 00000000000b4e2b 00000001 00000000      2      0 000000

kctl       0      0       775040       249120  131072    2048             apsd:<apsd PID>    00182 00000000 000000000000121a 00008001 00000000      1      0 000000     12      7 com.apple.netsrc

Here is a rough breakdown of the output line by line:

  1. It shows the local and remote IP addresses and ports and mentiones the TCP4 connection being established.
  2. It shows a stream connection, however, it is not a network socket, it is some internal IPC streaming mechanism.
  3. kctl is another kernel control connection, it is also an internal kernel-related communication channel.

Conclusion

There is no real conclusion here, as I simply satisfied my curiosity and found it interesting. I hope you enjoyed reading this as much as I enjoyed writing it, and managed to learn something new about how your phone works on the networking level.

My dog satisfied with my research

References