Bypassing Google and Apple: Implementing True De-Googled Push Notifications with UnifiedPush and ntfy

When engineering a sovereign communication stack, the most difficult architectural hurdle is rarely the chat protocol itself. The true weakest link for metadata leakage is the push notification pipeline.

Historically, mobile operating systems have forced developers into a centralized paradigm. If a message arrives on your private server, that server has to ping Google’s Firebase Cloud Messaging (FCM) or the Apple Push Notification Service (APNS) to wake up the device in your pocket. Even if your message payload is end-to-end encrypted, Google and Apple can still harvest the metadata: they know exactly when you are receiving messages, how frequently, and often from which server.

For the Remote Rails Sovereign Appliance, relying on Big Tech to deliver notifications for a dark, air-gapped Matrix server was unacceptable. We needed a pipeline that allowed Android users to completely sever ties with Google, while maintaining an airtight privacy model for iOS users restricted by Apple’s walled garden.

Here is how we architected a zero-metadata notification layer using UnifiedPush, ntfy, and cryptographic wake-up pings.

The Architecture: ntfy as the Notification Broker

To take ownership of the push pipeline, we deploy ntfy as a dedicated container within our Docker Compose stack. Rather than functioning as a standard push service, we configure it specifically to act as a UnifiedPush distributor for our Tuwunel Matrix homeserver.

We lock this container down by passing NTFY_WEB_ROOT=disable to shut off the web UI, and NTFY_AUTH_DEFAULT_ACCESS=deny-all to ensure no unauthenticated read/write access is permitted by default. To ensure battery-efficient, persistent WebSocket connections on de-googled devices (like GrapheneOS), we configure an aggressive NTFY_KEEPALIVE_INTERVAL=20s. State is safely persisted to local files (cache.db and auth.db).

Most importantly, we place the entire ntfy routing namespace behind Traefik’s vpn-only@docker middleware. This means your device cannot even subscribe to the notification stream unless it has already negotiated a secure WireGuard tunnel into the infrastructure.

Android: Achieving True 100% Self-Hosted Push

For Android users running hardened operating systems or standard devices configured to bypass Google Play Services, we achieve true 100% self-hosted push notifications. The architecture relies on strict Access Control Lists (ACLs) within the ntfy container. UnifiedPush requires that the Matrix homeserver can write to specific topics, while the mobile client reads from them.

We configure this by splitting the permissions:

  • The Write Path: We grant the Tuwunel Matrix homeserver anonymous write-only access strictly to topics beginning with up* (UnifiedPush) using the command ntfy access '*' 'up*' write-only.
  • The Read Path: We provision a dedicated, shared push_user account inside the ntfy container.
  • The Binding: We grant this push_user read-only access to the up* topics via ntfy access push_user 'up*' read-only.

The Shared-Secret Fallacy: Why a Single push_user is Cryptographically Airtight

From a traditional enterprise security perspective, provisioning a single, shared push_user account and password across all authenticated internal clients might trigger immediate red flags. If every user’s mobile device utilizes the exact same credentials to subscribe to the up* wildcard topic namespace, what stops User A from eavesdropping on the push notifications of User B?

In a standard messaging architecture, this would be a catastrophic flaw. But under the UnifiedPush and Matrix specification, it is cryptographically airtight due to three strict architectural constraints:

1. Total Entropy of the Topic ID

When an internal client (like Element) registers with the local ntfy distributor, it does not subscribe to a predictable topic like up_john_doe. Instead, UnifiedPush generates a completely randomized, high-entropy string for that specific device session—resulting in a topic namespace that looks like upA8f9Xz2Kq91pLmN7v. Because ntfy does not provide a “list topics” API to authenticated clients, an internal actor has no way to discover what other topic strings exist on the server. They are effectively blind to their peers’ endpoints.

2. Absolute Payload Redaction

Even in the mathematically impossible event that a malicious internal user guessed a peer’s high-entropy topic ID, the data they would intercept is entirely useless. By protocol enforcement, the payloads sent through UnifiedPush to the up* topics contain absolutely zero message metadata. There are no sender names, no room IDs, and no timestamps. The payload is reduced to a generic, opaque notification event signaling the client to wake up.

3. End-to-End DTLS/SFrame Isolation

The actual cryptographic payload fetching happens completely outside of the ntfy infrastructure. The moment the mobile device receives the wake-up ping over its ntfy WebSocket, the client initiates an independent, end-to-end encrypted HTTPS sync request directly to the private Tuwunel homeserver over the WireGuard VPN. The text and media frames are decrypted locally on the device using keys that never traverse, touch, or get indexed by the notification broker.

By leveraging wild-carded write-only ACLs alongside a single read-only gateway user, we eliminate the administrative overhead of managing thousands of individual database entries for notification accounts. We weaponize the mathematical design of the protocol to ensure that a unified internal pipeline delivers total cryptographic isolation between users.

The Flow

When a user configures their Element client on Android, they select their self-hosted ntfy app as the UnifiedPush distributor. When a message hits the Tuwunel server, Tuwunel POSTs the event directly to the local ntfy container. The ntfy container streams it down the active WebSocket directly to the Android device. Google never sees a single byte of data.

iOS: The “Wake-Up Ping” Privacy Defense

Unlike Android, Apple’s iOS ecosystem is a strict walled garden. Apple completely forbids third-party applications from maintaining persistent background WebSockets to save battery life. If you want a notification to wake up an app on an iPhone, it must travel through APNS. (The only way to bypass APNS on iOS is to compile a custom build of the Element app using your own paid Apple Developer Account, which is outside the scope of a standard appliance deployment).

Does passing traffic through Apple compromise the privacy of your sovereign stack? No.

To bridge the iOS gap, we configure the ntfy container with the environment variable NTFY_UPSTREAM_BASE_URL=https://ntfy.sh. This allows our private server to leverage the public ntfy.sh infrastructure solely as a gateway to Apple’s APNS.

However, we utilize a highly secure architectural pattern known as the “Wake-up Ping”:

  1. When a message arrives, your private Matrix homeserver does not send the message content, the sender’s name, or any identifying metadata to the public upstream.
  2. Instead, the server sends a highly redacted, completely silent “wake-up” ping through ntfy.sh to APNS.
  3. Apple delivers this empty ping to your iPhone, which wakes up the Element app in the background.
  4. Once awake, Element immediately reaches out directly and securely over your encrypted WireGuard VPN to the Tuwunel homeserver to fetch the actual encrypted chat payload.

In this model, Apple knows that your device received a signal, but they have absolutely zero insight into who sent it, what it contains, or which private server it originated from.

Owning the Pipeline

By integrating UnifiedPush and ntfy directly into the routing layer of the sovereign appliance, we strip the metadata harvesting power away from centralized tech giants. For Android power-users, the pipeline is entirely dark and locally hosted. For iOS users, we weaponize Apple’s own infrastructure to deliver secure wake-up signals without compromising the cryptographic integrity of the payload.

Digital sovereignty is not just about where your data lives at rest; it is about owning the exact pathways it takes to reach you.