A self-hostable Rust implementation of Nextcloud's push proxy protocol — the relay that sits between a Nextcloud server and Apple/Google/UnifiedPush so mobile Talk/Files clients can receive push notifications without Nextcloud handing out its own APNs/FCM developer credentials to every self-hosted instance.
The protocol is not officially published by Nextcloud; this implementation
is derived directly from the open-source client (nextcloud/talk-ios) and
server-side app (nextcloud/notifications, see docs/push-v2.md there).
See the workspace's crates/protocol crate docs for the full reverse-
engineered wire format and crypto scheme.
cargo build --release -p nc-push-proxyBy default only the sqlite storage backend is compiled in. For Postgres:
cargo build --release -p nc-push-proxy --no-default-features --features postgresNC_PUSH_PROXY_DB=sqlite://devices.db?mode=rwc \
NC_PUSH_PROXY_BIND=0.0.0.0:8080 \
./target/release/nc-push-proxyOr via Docker: docker build -t nc-push-proxy . && docker run -p 8080:8080 nc-push-proxy.
| Variable | Required | Notes |
|---|---|---|
NC_PUSH_PROXY_DB |
no (default sqlite://devices.db?mode=rwc) |
sqlite://... or postgres://...; requires a restart to change |
NC_PUSH_PROXY_BIND |
no (default 0.0.0.0:8080) |
requires a restart to change |
NC_PUSH_PROXY_APNS_P8_PATH |
for iOS | path to the .p8 key from the Apple developer portal |
NC_PUSH_PROXY_APNS_KEY_ID |
for iOS | |
NC_PUSH_PROXY_APNS_TEAM_ID |
for iOS | |
NC_PUSH_PROXY_APNS_TOPIC |
for iOS | app bundle id, e.g. com.nextcloud.Talk; VoIP pushes use {topic}.voip |
NC_PUSH_PROXY_APNS_PRODUCTION |
no (default true) |
set false to use the APNs sandbox endpoint |
NC_PUSH_PROXY_FCM_SERVICE_ACCOUNT_PATH |
for Android (FCM) | path to a Firebase service-account JSON file |
NC_PUSH_PROXY_SELF_UPDATE |
no (default false) |
true/1 to enable periodic GitHub-release self-update |
NC_PUSH_PROXY_UPDATE_REPO_OWNER / _REPO_NAME |
if self-update enabled | |
NC_PUSH_PROXY_UPDATE_CHECK_INTERVAL_SECS |
no (default 3600) |
UnifiedPush needs no configuration — it's just an HTTP POST to whatever distributor endpoint the device registered.
Any backend left unconfigured falls back to a logging stub rather than failing startup, so e.g. an FCM-only deployment doesn't need APNs credentials at all.
Sending SIGHUP re-reads the NC_PUSH_PROXY_APNS_*/NC_PUSH_PROXY_FCM_*
variables and swaps in freshly-built backends without dropping in-flight
connections — useful for rotating an expiring .p8 key or service account
file. NC_PUSH_PROXY_DB/NC_PUSH_PROXY_BIND are not covered; changing those
needs a real restart.
kill -HUP $(pgrep nc-push-proxy)On the Nextcloud instance, the mobile clients themselves choose the proxy by
sending a proxyServer value when registering
(POST /ocs/v2.php/apps/notifications/api/v2/push) — there's no server-side
setting to change. What you need on the server side is for
IRemoteHostValidator to accept your proxy's host: any public https://
host works out of the box; localhost/*.internal/*.local are allowed
without TLS for local development (see PushController.php in
nextcloud/notifications).
The current mobile app releases hardcode
https://push-notifications.nextcloud.com, so redirecting an existing App
Store/Play Store install to a self-hosted proxy isn't possible through
server config alone — it needs a client-side code change (a custom build, or
a future Nextcloud client setting for a configurable proxy URL, should one
ever ship). This proxy is protocol-compatible with what the stock client
already speaks; it just isn't reachable from stock builds today.
cargo test --workspaceIntegration tests spin up the real axum router against an in-memory
SQLite database — no external services required. Real delivery through
APNs/FCM/UnifiedPush needs live credentials and can't be exercised in CI;
those backends are unit-tested for request-shape/routing logic only.