Thanks for this tutorial! I followed it and after browsing the other topics on ntfy + Element on the forum, decided to add an extra security layer via nginx that others might find useful.
What’s different from the original tutorial:
The main addition is an nginx restriction that ensures only localhost (Synapse) can POST to UnifiedPush topics. Without this, anyone on the internet who discovers a UP topic name could send you spam notifications. With this setup, the random topic name is still secret, but even if discovered, external POST requests are blocked.
Steps:
1. Create user accounts as above. Configure this account in the ntfy Android app settings.
Note: YunoHost LDAP users don’t work with ntfy, this is an upstream ntfy limitation (no LDAP support). For multiple users, create separate ntfy accounts as the original tutorial suggests:
sudo -u ntfy /var/www/ntfy/ntfy.sh user add <username>
2. Fix authentication through YunoHost’s proxy (required for the Android app to log in):
yunohost app setting ntfy protect_against_basic_auth_spoofing -v false
yunohost app ssowatconf
3. Allow UnifiedPush topic writes:
sudo -u ntfy /var/www/ntfy/ntfy.sh access everyone 'up*' write
4. nginx restriction (the main security addition).
Create /etc/nginx/conf.d/ntfy.yourdomain.tld.d/unifiedpush.conf:
# UnifiedPush topics - POST from localhost only
location ~ ^/(up[a-zA-Z0-9_-]+)$ {
limit_except GET HEAD OPTIONS {
allow 127.0.0.1;
allow ::1;
deny all;
}
proxy_pass http://127.0.0.1:8081;
include proxy_params_no_auth;
more_set_input_headers 'Authorization: $http_authorization';
proxy_set_header Authorization $http_authorization;
proxy_buffering off;
proxy_request_buffering off;
proxy_redirect off;
proxy_connect_timeout 3m;
proxy_send_timeout 3m;
proxy_read_timeout 3m;
client_max_body_size 0;
}
# Matrix gateway - POST from localhost only
location = /_matrix/push/v1/notify {
limit_except GET HEAD OPTIONS {
allow 127.0.0.1;
allow ::1;
deny all;
}
proxy_pass http://127.0.0.1:8081;
include proxy_params_no_auth;
proxy_buffering off;
proxy_request_buffering off;
proxy_redirect off;
proxy_connect_timeout 3m;
proxy_send_timeout 3m;
proxy_read_timeout 3m;
client_max_body_size 0;
}
Then reload nginx:
nginx -t && systemctl reload nginx
5. Synapse setting: In YunoHost admin panel, go to Synapse > Config panel > Advanced Settings > Security, and enable “Allow synapse to send request to localhost”.
Notes:
The “Test push loop back” in Element X’s troubleshooter will fail with this setup. My guess is that the test tries to POST directly from your phone, which nginx blocks. Real notifications work because they go through Synapse on localhost.
Important: iOS limitations for self-hosters
This setup only applies to Android. iOS users don’t use ntfy at all, and unfortunately there’s no easy way to fully self-host push notifications for iOS.
Apple requires all push notifications to go through their APNs (Apple Push Notification service), and only registered Apple developers can send to APNs. From what I could observe in my local synapse postgres’ DB, Element maintains a push gateway at matrix.org with their Apple developer credentials, so all Element X iOS notifications route through matrix.org regardless of which homeserver you use.
The notification flow differs by platform:
Android: Synapse → your ntfy server → ntfy app → Element X ✓ fully self-hosted
iOS: Synapse → matrix.org → Apple APNs → Element X ✗ routes through third party
You can see for yourself by running
SELECT user_name, app_display_name, pushkey, data FROM pushers;against the synapse DB (sudo -u postgres psql synapse)
Privacy implications: Your iOS notifications pass through matrix.org servers
. Element X uses format: event_id_only by default, which means matrix.org only learns “user X on homeserver Y has a notification”, not the message content. Element X then fetches the actual message directly from your homeserver. This is a reasonable privacy compromise, but it’s important to understand that full sovereignty isn’t possible on iOS without building your own app with your own Apple developer account.
This is an Apple platform restriction, not a Matrix or Element design choice. For users who prioritise complete self-hosting, Android with ntfy is currently the only option.