ntfy
Self-hosted push notification service using simple HTTP pub-sub. Publish notifications from any script, CI pipeline,
or monitoring system with a single curl command. Subscribe on Android, iOS, or the web. No signup, no cost, no
third-party service — all traffic stays on your infrastructure.
ntfy.authDefaultAccess defaults to read-write, meaning any unauthenticated user can publish and subscribe to any
topic. If you expose ntfy to the internet, set authDefaultAccess: deny-all and configure authentication via
ntfy.extraConfig to prevent unauthorized access and notification spam.
Key Features
- HTTP pub-sub — publish via
PUT/POST, subscribe viaGETor WebSocket - Mobile and web clients — Android, iOS apps and progressive web app
- Access control — per-topic user and permission management
- Attachment support — configurable file attachment size and expiry limits
- Behind-proxy mode — correct client IP identification via
X-Forwarded-For - Prometheus metrics — optional
/metricsendpoint with ServiceMonitor support - Persistent storage — PVC-backed SQLite cache and authentication databases
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install ntfy helmforge/ntfy
OCI registry:
helm install ntfy oci://ghcr.io/helmforgedev/helm/ntfy
Deployment Examples
# values.yaml — ntfy with ingress, behind Traefik
ntfy:
baseUrl: 'https://ntfy.example.com'
behindProxy: true
persistence:
enabled: true
size: 2Gi
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: ntfy.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: ntfy-tls
hosts:
- ntfy.example.comAfter deploying, send your first notification:
curl -d "Hello from Kubernetes!" https://ntfy.example.com/my-topic# values.yaml — ntfy with authentication enabled (deny anonymous access)
ntfy:
baseUrl: 'https://ntfy.example.com'
behindProxy: true
authDefaultAccess: deny-all
extraConfig: |
auth-file: /var/cache/ntfy/user.db
persistence:
enabled: true
size: 2Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: ntfy.example.com
paths:
- path: /
pathType: PrefixAfter deploying, create users via the ntfy CLI inside the pod:
# Create an admin user
kubectl exec -it deployment/ntfy -- ntfy user add --role=admin admin
# Publish with authentication
curl -u admin:password -d "Secure notification" https://ntfy.example.com/my-topic# values.yaml — ntfy with file attachment support
ntfy:
baseUrl: 'https://ntfy.example.com'
behindProxy: true
attachmentTotalSizeLimit: '200M'
attachmentFileSizeLimit: '20M'
attachmentExpiryDuration: '24h'
persistence:
enabled: true
# Increase PVC size to accommodate attachments
size: 10Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: ntfy.example.com
paths:
- path: /
pathType: Prefix# values.yaml — ntfy with Prometheus metrics
ntfy:
baseUrl: 'https://ntfy.example.com'
enableMetrics: true
# Expose metrics on a separate port to avoid routing /metrics via Ingress
metricsListenHttp: ':9090'
persistence:
enabled: true
size: 2Gi
metrics:
serviceMonitor:
enabled: true
interval: 30s
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: ntfy.example.com
paths:
- path: /
pathType: PrefixConfiguration Reference
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
nameOverride | string | "" | Override the chart name. |
fullnameOverride | string | "" | Override the full release name. |
commonLabels | object | {} | Extra labels added to all resources. |
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository | string | docker.io/binwiederhier/ntfy | ntfy container image. |
image.tag | string | "v2.21.0" | Image tag. |
image.pullPolicy | string | IfNotPresent | Image pull policy. |
imagePullSecrets | array | [] | Pull secrets for private registries. |
ntfy Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
ntfy.baseUrl | string | "" | Public base URL of the instance (e.g. https://ntfy.example.com). |
ntfy.authDefaultAccess | string | read-write | Default access for unauthenticated users: read-write, read-only, deny-all. |
ntfy.behindProxy | boolean | true | Trust X-Forwarded-For headers for correct client IP and rate limiting. |
ntfy.enableMetrics | boolean | false | Enable Prometheus metrics at /metrics. |
ntfy.metricsListenHttp | string | "" | Separate listen address for the metrics endpoint (e.g. :9090). |
ntfy.attachmentTotalSizeLimit | string | "" | Total attachment storage limit per visitor (e.g. 100M). |
ntfy.attachmentFileSizeLimit | string | "" | Maximum size per attachment file (e.g. 15M). |
ntfy.attachmentExpiryDuration | string | "" | How long attachments are retained (e.g. 3h). |
ntfy.extraConfig | string | "" | Raw server.yml configuration appended to the generated ConfigMap. |
ntfy.extraEnv | array | [] | Extra environment variables injected into the container. |
The ntfy.extraConfig field appends raw server.yml lines to the generated ConfigMap. Use it for any ntfy server
option not exposed as a dedicated value, such as authentication providers, Firebase credentials, or per-topic limits.
See the ntfy server configuration reference for all available options.
When running behind a Kubernetes Ingress controller, all requests arrive from the Ingress pod IP unless
X-Forwarded-For is trusted. Keep ntfy.behindProxy: true (the default) so rate limits and client IP logging work
correctly.
Persistence
ntfy stores its SQLite cache database and authentication database in /var/cache/ntfy. Persistence is enabled by
default to survive pod restarts without losing message history and user accounts.
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled | boolean | true | Enable a PVC for /var/cache/ntfy. |
persistence.size | string | 2Gi | PVC size. Increase if using attachments. |
persistence.storageClass | string | "" | StorageClass for the PVC. |
persistence.accessModes | array | ["ReadWriteOnce"] | PVC access modes. |
persistence.existingClaim | string | "" | Use an existing PVC instead of creating one. |
Service
| Parameter | Type | Default | Description |
|---|---|---|---|
service.type | string | ClusterIP | Kubernetes service type. |
service.port | integer | 80 | Service port exposed to the cluster. |
service.annotations | object | {} | Annotations for the Service. |
Ingress
| Parameter | Type | Default | Description |
|---|---|---|---|
ingress.enabled | boolean | false | Enable an Ingress resource. |
ingress.ingressClassName | string | traefik | Ingress class name. |
ingress.annotations | object | {} | Annotations for the Ingress (e.g. cert-manager). |
ingress.hosts | array | [] | Ingress host and path rules. |
ingress.tls | array | [] | TLS configuration (secret name and hosts). |
Probes
Probes use the /v1/health endpoint.
| Parameter | Type | Default | Description |
|---|---|---|---|
probes.startup.enabled | boolean | true | Enable startup probe. |
probes.startup.initialDelaySeconds | integer | 5 | Startup probe initial delay. |
probes.startup.periodSeconds | integer | 5 | Startup probe period. |
probes.startup.timeoutSeconds | integer | 3 | Startup probe timeout. |
probes.startup.failureThreshold | integer | 30 | Startup probe failure threshold. |
probes.liveness.enabled | boolean | true | Enable liveness probe. |
probes.liveness.initialDelaySeconds | integer | 0 | Liveness probe initial delay. |
probes.liveness.periodSeconds | integer | 15 | Liveness probe period. |
probes.liveness.timeoutSeconds | integer | 5 | Liveness probe timeout. |
probes.liveness.failureThreshold | integer | 3 | Liveness probe failure threshold. |
probes.readiness.enabled | boolean | true | Enable readiness probe. |
probes.readiness.initialDelaySeconds | integer | 0 | Readiness probe initial delay. |
probes.readiness.periodSeconds | integer | 10 | Readiness probe period. |
probes.readiness.timeoutSeconds | integer | 5 | Readiness probe timeout. |
probes.readiness.failureThreshold | integer | 3 | Readiness probe failure threshold. |
Metrics
| Parameter | Type | Default | Description |
|---|---|---|---|
metrics.serviceMonitor.enabled | boolean | false | Create a ServiceMonitor for Prometheus Operator. |
metrics.serviceMonitor.interval | string | 30s | Prometheus scrape interval. |
metrics.serviceMonitor.labels | object | {} | Extra labels applied to the ServiceMonitor. |
Resources and Security
| Parameter | Type | Default | Description |
|---|---|---|---|
resources | object | {} | CPU and memory requests and limits. |
podSecurityContext | object | {} | Pod-level security context. |
securityContext | object | {} | Container-level security context. |
Service Account
| Parameter | Type | Default | Description |
|---|---|---|---|
serviceAccount.create | boolean | false | Create a dedicated ServiceAccount. |
serviceAccount.name | string | "" | Override the ServiceAccount name. |
serviceAccount.annotations | object | {} | Annotations for the ServiceAccount. |
Scheduling
| Parameter | Type | Default | Description |
|---|---|---|---|
nodeSelector | object | {} | Node selector for scheduling. |
tolerations | array | [] | Tolerations for scheduling. |
affinity | object | {} | Affinity rules. |
topologySpreadConstraints | array | [] | Topology spread constraints. |
priorityClassName | string | "" | PriorityClass for the pod. |
terminationGracePeriodSeconds | integer | 30 | Termination grace period. |
podLabels | object | {} | Extra labels for the pod. |
podAnnotations | object | {} | Extra annotations for the pod. |
Extra
| Parameter | Type | Default | Description |
|---|---|---|---|
extraVolumes | array | [] | Extra volumes to attach to the pod. |
extraVolumeMounts | array | [] | Extra volume mounts for the container. |
extraManifests | array | [] | Extra Kubernetes manifests deployed alongside the chart. |
Common Issues
If rate limiting is not working or logs show all requests from the same IP (the Ingress pod), verify that
ntfy.behindProxy: true is set (it is the default) and that your Ingress controller is forwarding the
X-Forwarded-For header.
After deploying, verify your setup with a quick curl command:
curl -d "Test notification from Kubernetes" https://ntfy.example.com/test-topicSubscribe to https://ntfy.example.com/test-topic in the ntfy app to receive it.