docs/production/mobile-push-notifications.md
Zulip's iOS and Android mobile apps support receiving push notifications from Zulip servers to notify users when new messages have arrived. This is an important feature for having a great mobile app experience.
Google's and Apple's security model for mobile push notifications does not allow self-hosted Zulip servers to directly send mobile notifications to the Zulip mobile apps. The Zulip Mobile Push Notification Service solves this problem by forwarding E2EE mobile push notifications generated by your server to the Zulip mobile apps. With Zulip Server 12.0+ and an up-to-date Zulip mobile app, the notifications are encrypted end-to-end from your server to your mobile device.
:::{important}
The Zulip Server 10.0+ installer
includes a --push-notifications flag that automates this
registration process.
These instructions apply to Zulip 9.0+. If you are running an older version of Zulip (check if you are unsure), see the Zulip 8.x documentation. :::
You can enable the mobile push notification service for your Zulip server as follows:
Make sure your server has outgoing HTTPS access to the public Internet. If that is restricted by a proxy, you will need to configure Zulip to use your outgoing HTTP proxy first.
Make sure that the ZULIP_ADMINISTRATOR setting in your
/etc/zulip/settings.py file is a real email address which you
monitor. If the Mobile Push Notification Service needs to contact
you regarding your server, and will use this email address. See
below for instructions if
this contact needs to be updated later.
Set ZULIP_SERVICE_PUSH_NOTIFICATIONS = True in your
/etc/zulip/settings.py file. Simply uncomment the appropriate line in
settings.py by deleting the initial # .
Decide whether to share usage statistics with the Zulip team.
By default, Zulip installations using the Mobile Push Notification Service
submit additional usage statistics that help Zulip's maintainers allocate
resources towards supporting self-hosted installations
(details). You can disable submitting usage
statistics now or at any time by setting
ZULIP_SERVICE_SUBMIT_USAGE_STATISTICS=False in /etc/zulip/settings.py
(uncomment the appropriate line).
Note that all systems using the service upload basic metadata about the organizations hosted by the installation.
Restart your Zulip server so that your configuration changes take effect.
Run the registration command. If you installed Zulip directly on the server (without Docker), run as root:
su zulip -c '/home/zulip/deployments/current/manage.py register_server'
Or if you're {doc}using Docker <docker:index>, run:
cd docker-zulip
./manage.py register_server
This command will print the registration data it would send to the Mobile Push Notification Service, ask you to accept the terms of service, and if you accept, register your server. If you have trouble, contact Zulip support with the output of this command.
Organizations with more than 10 users must upgrade their plan in order to access the Mobile Push Notification Service. See plan management for details.
If you or your users have already set up the Zulip mobile app, you'll each need to log out of the mobile app, and log back in again in order to start getting push notifications.
Congratulations! You've successfully set up the service. You can now test mobile push notifications by following these instructions.
To access the Mobile Push Notification Service, organizations with more than 10 users must upgrade to a paid plan, or the free Community plan (if eligible). While upgrading your Zulip server to version 8.0+ makes it more convenient to manage your plan, the same plans are offered for all Zulip versions.
On a self-hosted Zulip server running Zulip 8.0+, organization owners and billing administrators can conveniently access plan management from the Zulip app. See help center documentation for detailed instructions.
::::{tab-set}
:::{tab-item} Zulip Server 10.0+
Follow these instructions to configure who can manage plans and billing.
:::
:::{tab-item} Older versions
You can add billing administrators using the change_user_role management
command, passing the organization's
string_id, and the email address of the Zulip user who
should be added as a billing administrator.
/home/zulip/deployments/current/manage.py change_user_role -r '' [email protected] is_billing_admin
You can remove a user's billing administrator permissions with the --revoke
option:
/home/zulip/deployments/current/manage.py change_user_role --revoke -r '' [email protected] is_billing_admin
:::
::::
Servers running Zulip releases older than Zulip 8.0 can start the plan management log in process at https://selfhosting.zulip.com/serverlogin/. This option is also available for Zulip 8.0+ servers, and makes it possible to use a single plan for multiple organizations on one installation. See help center documentation for detailed log in instructions.
You will use your server's zulip_org_id and zulip_org_key as the username
and password to access plan management. You can obtain these from
/etc/zulip/zulip-secrets.conf on your Zulip server, or via the following
commands:
/home/zulip/deployments/current/scripts/get-django-setting ZULIP_ORG_ID
/home/zulip/deployments/current/scripts/get-django-setting ZULIP_ORG_KEY
Both Google's and Apple's push notification services have a security model that does not support mutually untrusted self-hosted servers sending push notifications to the same app. In particular, when an app is published to their respective app stores, one must compile into the app a secret corresponding to the server that will be able to publish push notifications for the app. This means that it is impossible for a single app in their stores to receive push notifications from multiple, mutually untrusted, servers.
Zulip's solution to this problem is to provide a central push notification forwarding service, which allows registered Zulip servers to send push notifications to the Zulip app indirectly (through the forwarding service).
:::{note} Use of the push notification bouncer is subject to the Zulip Cloud Terms of Service, Privacy Policy and Rules of Use. By using push notifications, you agree to these terms. :::
Zulip Server 12.0+ uses end-to-end encryption (E2EE) when sending push notifications to the Zulip mobile app. E2EE ensures that message content and metadata (including the sender's name, and the recipient's name or channel and topic where the message was sent) are not visible to Apple, Google, or Zulip's Mobile Push Notification Service.
The Zulip server encrypts the contents of each notification with a per-device symmetric key shared between the Zulip server and the user's mobile app. The Push Notification Service forwards the encrypted payload without being able to decrypt it.
If either the Zulip Server or the mobile app is a version predating the 12.0 release (April 2026) that does not support the E2EE protocol, the legacy push notifications protocol will be used. Organization administrators can require E2EE for message content in mobile push notifications.
If you have any questions about the security model, contact Zulip support.
For each notification, the Push Notification Service receives and forwards to Apple or Google:
push_key_id) that the mobile app
uses to select the correct decryption key. This identifier is opaque to
the service.The notification contents — including the message, sender, recipient, channel, and topic — are inside an encrypted ciphertext that Apple, Google, and the service cannot decrypt.
All network requests — from Zulip servers to the Push Notification Service, and from the Push Notification Service to Apple and Google — are encrypted over the wire with SSL/TLS.
The Push Notification Service stores the following metadata for as long as a device's registration is active:
The Zulip server is the source of every notification it sends, and holds the symmetric key used to encrypt each notification for delivery.
E2EE makes notification contents opaque to Apple, Google, and the Push Notification Service, but it does not protect against a compromised or malicious Zulip server. Learn about Zulip's approach to security, and keep your server safe.
The E2EE protocol has the following known limitations:
Incomplete forward secrecy. A device's symmetric key persists until the mobile app rotates it (generally every 30 days), and is stored by both the mobile app and the Zulip server. An attacker who obtains both a historical capture of encrypted push payloads and the device's current key — for example, by compromising the mobile device or the Zulip server — can decrypt the notifications captured since the key was last rotated.
Metadata side channels. The Push Notification Service, and indirectly Apple and Google, can observe the timing, delivery priority, and size of each push notification. These side channels can reveal coarse patterns of activity even though message contents are encrypted.
The E2EE protocol encrypts each notification payload with libsodium's
crypto_secretbox_easy (XSalsa20-Poly1305 authenticated encryption) using
a per-device 256-bit symmetric key and a fresh 192-bit random nonce per
notification. Initial registration of a device with the Push Notification
Service uses libsodium's crypto_box_seal (Curve25519 + XSalsa20-Poly1305)
so that the Zulip server can forward the device's APNs/FCM token to the
bouncer without learning the token itself. A one-byte algorithm prefix on
each symmetric key reserves space for future cipher migrations.
The code for the push notification forwarding service is 100% open source, and is available as part of the Zulip server project on GitHub (specifically, here).
All Zulip installations running Zulip 8.0 or greater that are registered for the Mobile Push Notification Service regularly upload to the service basic metadata about the organizations hosted by the installation. (Older Zulip servers upload these metadata only if uploading usage statistics is enabled).
Uploaded metadata consists of, for each organization hosted by the installation:
A subset of the basic metadata returned by the unauthenticated GET /server_settings API
endpoint.
The purpose of that API endpoint is to serve the minimal data needed by the Zulip mobile apps in order to:
Most of the metadata it returns is necessarily displayed to anyone with network access to the Zulip server on the login and signup pages for your Zulip organization as well.
(Some fields returned by this endpoint, like the organization icon and description, are not included in uploaded metadata.)
The organization type and creation date.
The number of user accounts with each role.
Our use of uploaded metadata is governed by the same Terms of Service and Privacy Policy that covers the Mobile Push Notification Service itself.
By default, Zulip installations that register for the Mobile Push
Notification Service upload the following usage statistics. You can
disable these uploads any time by setting
ZULIP_SERVICE_SUBMIT_USAGE_STATISTICS=False in /etc/zulip/settings.py.
Some of the graphs on your server's usage statistics page can be generated from these statistics.
When enabled, usage statistics are submitted via an hourly cron
job. If you'd like to access plan management immediately after
enabling SUBMIT_USAGE_STATISTICS=True (the legacy form of this setting)
on a pre-8.0 Zulip server, you can run the analytics job manually via:
/home/zulip/deployments/current/manage.py update_analytics_counts
Our use of uploaded usage statistics is governed by the same Terms of Service and Privacy Policy that covers the Mobile Push Notification Service itself.
The Mobile Push Notification Service API has a very high default rate limit of 1000 requests per minute. A Zulip server makes requests to this API every time it sends a push notification, which is fairly frequent, but we believe it to be unlikely that a self-hosted installation will hit this limit.
This limit is primarily intended to protect the service against DoS attacks (intentional or otherwise). If you hit this limit or you anticipate that your server will require sending more push notifications than the limit permits, please contact support.
Your server's registration includes the server's hostname and contact
email address (from EXTERNAL_HOST and ZULIP_ADMINISTRATOR in
/etc/zulip/settings.py, aka the --hostname and --email options
in the installer). You can update your server's registration data by
running manage.py register_server again.
If you'd like to rotate your server's API key for this service
(zulip_org_key), you need to use
manage.py register_server --rotate-key option; it will automatically
generate a new zulip_org_key and store that new key in
/etc/zulip/zulip-secrets.conf.
When migrating your Zulip deployment to a new machine, you will likely want to retain your original registration and successfully transfer it. This is especially important if you have an active plan for the Mobile Push Notification Service.
The best way to preserve your registration when moving to a new server is to
copy over the credentials from the old server. These credentials are stored in
the /etc/zulip/zulip-secrets.conf file, specifically in the zulip_org_id
and zulip_org_key fields. After installing Zulip on the new machine, ensure
that zulip_org_id and zulip_org_key are set to the same values as on the
old server.
If you used the official backup tool to restore your Zulip deployment on the new machine, it will have automatically transferred all secrets, including the registration credentials, correctly.
If you have lost your original credentials, you can still transfer your Zulip registration to a new server by following these steps:
Ensure Zulip is installed and accessible:
Run the below command to transfer your registration to the new server. This will
execute a verification flow to prove to our Mobile Push Notification Service that
you control the hostname and upon success, re-generate the credentials for
using the registration and write them to the /etc/zulip/zulip-secrets.conf file.
/home/zulip/deployments/current/manage.py register_server --registration-transfer
Note that the zulip_org_key value changes in the process, and therefore if you
still have an old server running using the service, it will lose access upon
execution of this command.
Apply the changes by restarting the server:
/home/zulip/deployments/current/scripts/restart-server
Finally, verify that push notifications are working correctly. If you encounter further issues, contact [email protected].
If you store /etc/zulip/zulip-secrets.conf secrets externally in
an external configuration management tool (Ansible, etc.), or
backups, this is a good time to
update that configuration.
If you are deleting your Zulip server or otherwise no longer want to use the Mobile Push Notification Service, you can deactivate your server's registration.
Cancel any paid plans associated with your server.
Run the deregistration command. If you installed Zulip directly on the server (without Docker), run as root:
su zulip -c '/home/zulip/deployments/current/manage.py register_server --deactivate'
Or if you're {doc}using Docker <docker:index>, run:
cd docker-zulip
./manage.py register_server --deactivate
Comment out the
ZULIP_SERVICE_PUSH_NOTIFICATIONS = True line
in your /etc/zulip/settings.py file (i.e., add # at the
start of the line), and restart your Zulip
server.
If you ever need to reactivate your server's registration, contact Zulip support.
You can temporarily stop using the Mobile Push Notification Service. Comment out
the PUSH_NOTIFICATION_BOUNCER_URL = 'https://push.zulipchat.com' line in your
/etc/zulip/settings.py file (i.e., add # at the start of the line), and
restart your Zulip server. This approach makes it
easy to start using the service again by uncommenting the same line.
Zulip Servers running 12.0+ sending notifications to a modern mobile app version will always use the E2EE protocol. If either the Zulip Server or the mobile app is a version predating the 12.0 release (April 2026) that does not support the E2EE protocol, the legacy non-E2EE protocol will be used.
The central design goal of the legacy Push Notification Service service was to avoid any message content being stored or logged by the service, even in error cases.
The Push Notification Service only stores the necessary metadata for delivering the notifications to the appropriate devices and otherwise operating the service:
The Push Notification Service receives (but does not store) the contents of individual mobile push notifications:
Zulip 11.0+ has an organization-level setting available to disable
message content being sent via the push notification bouncer (i.e.,
message content will be replaced with New message), for clients
that don't support the new end-to-end encrypted notifications
protocol. (Prior to Zulip 11.0, this functionality was available via the
PUSH_NOTIFICATION_REDACT_CONTENT server-level setting).
All of the network requests (both from Zulip servers to the Push Notification Service and from the Push Notification Service to the relevant Google and Apple services) are encrypted over the wire with SSL/TLS.