Skip to main content

ntfy

A simple HTTP-based pub/sub push notification service.

ntfy is an open-source service for sending push notifications to phones and desktops via HTTP PUT/POST requests from any script or application. It requires no sign-up, works with a simple topic-based subscription model, and has mobile apps for iOS and Android. Self-hosting ensures privacy and no rate limits.

Alternatives considered​

Cloud Hosted

ToolOpen SourceFree TierMonthly Cost
PushoverNoNo$4.99 one-time/platform
PushbulletNoLimited$4.99/mo

Self Hosted

ToolOpen SourceFull FeaturesNotes
GotifyYesYesNo iOS app

Installation​

Architecture​

  • Deployment: Single ntfy deployment in the ntfy namespace
  • Image: binwiederhier/ntfy, digest-pinned via Renovate
  • Storage: Longhorn PVC (ntfy, annotated k8up.io/backup: "true") for message cache and auth database
  • Networking: ClusterIP service on port 80, HTTPRoute via public gateway

Security​

  • No runAsUser is set, so the container starts as the image's default user, which is root; privilege escalation is blocked and all capabilities are dropped
  • Longhorn PVC encrypted at rest via SOPS-managed keys

Updates​

Managed by Renovate. Image is digest-pinned.

Data Management​

  • PVC: ntfy (Longhorn-encrypted, k8up.io/backup: "true") for message cache and auth database
  • Backups: k8up Schedules to Hetzner and Backblaze via restic, covering the Longhorn PVC.

User Management​

Login enabled with file-based auth (NTFY_AUTH_FILE). NTFY_AUTH_DEFAULT_ACCESS controls default topic access. No OIDC configured.

Configuration Management​

  • NTFY_ENABLE_LOGIN, NTFY_AUTH_DEFAULT_ACCESS, NTFY_BEHIND_PROXY, NTFY_BASE_URL, NTFY_AUTH_FILE, NTFY_CACHE_FILE from ConfigMap
  • Longhorn encryption key from SOPS-encrypted secret

Usage​

Subscribe to a topic in the ntfy mobile app or web UI. Send notifications via curl -d "message" Grafana alerts and other monitoring tools use ntfy as a notification channel. The ntfy-authsecret in the monitoring namespace contains credentials for thegrafana-to-ntfy` proxy.

Cluster-specific deviations from the above live in the per-cluster README — see k8s/apps/ntfy/talos/README.md.

Cluster Deployment​

ntfy — Talos cluster

Kubernetes Metadata
  • Image: binwiederhier/ntfy:v2.31.1@sha256:91bb7fa607b02006c119bb4a31032923b414a713a032ecb40f0c5d6bfd5b7e40
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
labels:
app.kubernetes.io/name: ntfy
name: ntfy
namespace: ntfy
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
app.kubernetes.io/name: ntfy
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/name: ntfy
spec:
automountServiceAccountToken: false
containers:
- args:
- serve
env:
- name: NTFY_ENABLE_LOGIN
value: 'true'
- name: NTFY_AUTH_DEFAULT_ACCESS
value: deny-all
- name: NTFY_BEHIND_PROXY
value: 'true'
- name: NTFY_AUTH_FILE
value: /var/lib/ntfy/auth.db
- name: NTFY_CACHE_FILE
value: /var/lib/ntfy/cache.db
- name: NTFY_SMTP_SENDER_ADDR
valueFrom:
secretKeyRef:
key: hostPort
name: allinkl-smtp-credentials
- name: NTFY_SMTP_SENDER_USER
valueFrom:
secretKeyRef:
key: username
name: allinkl-smtp-credentials
- name: NTFY_SMTP_SENDER_PASS
valueFrom:
secretKeyRef:
key: password
name: allinkl-smtp-credentials
- name: NTFY_SMTP_SENDER_FROM
valueFrom:
secretKeyRef:
key: from
name: allinkl-smtp-credentials
- name: TZ
value: CET
- name: NTFY_DEBUG
value: 'false'
- name: NTFY_LOG_LEVEL
value: INFO
- name: NTFY_BASE_URL
value: https://ntfy.web.kueber.eu
image: binwiederhier/ntfy:v2.31.1@sha256:91bb7fa607b02006c119bb4a31032923b414a713a032ecb40f0c5d6bfd5b7e40
name: ntfy
ports:
- containerPort: 80
name: web
resources:
limits:
memory: 128Mi
requests:
cpu: 50m
memory: 128Mi
volumeMounts:
- mountPath: /var/lib/ntfy/
name: cache-volume
- mountPath: /etc/ntfy/server.yml
name: config-volume
subPath: server-config.yaml
priorityClassName: tier-1
securityContext:
seccompProfile:
type: RuntimeDefault
volumes:
- name: cache-volume
persistentVolumeClaim:
claimName: ntfy
- configMap:
name: server-config-52bbc48m5c
name: config-volume