Fully verified35,024

Notifications

ntfy

ntfy delivers messages from scripts, monitoring, and home automations to subscribed browsers and mobile clients. This recipe requires login, denies anonymous topic access, stores messages and attachments locally, and provisions an administrator from a private bcrypt entry.

Alternative to PushoverApache-2.0 OR GPL-2.0-onlyVersion v2.34.0

Overview

ntfy is a publish/subscribe notification server. A backup job, availability check, or household script sends an HTTP request to a topic; subscribed clients receive the message through the browser or a mobile app. Priorities, titles, tags, and attachments help a short notification carry enough context to act.

Where it fits

Use it for personal monitoring and automation when you want to operate the notification server yourself. It can cover everyday Pushover-style publish/subscribe workflows. You take responsibility for HTTPS, accounts, uptime, storage, and the delivery path a phone or browser uses; it does not turn a notification cache into a permanent archive.

This recipe runs the official pinned image as UID/GID 1000 with a read-only root filesystem. An initialization helper prepares one named volume. The server keeps SQLite message/auth databases and attachment files together, requires login, disables signup, and denies topic reads and writes without an explicit permission. The administrator comes from a private declarative bcrypt entry before requests are served.

Data and delivery limits

Messages and attachments are cached for seven days. Accounts, access tokens, topic ACLs, and account subscriptions/settings persist in the same volume; the web client also keeps a local IndexedDB cache. Server backup does not copy a browser profile. A restored attachment retains its original expiration time.

Topic authentication does not make attachment URLs private. Anyone who knows an attachment URL can download its bytes without login. Treat the URL as a bearer secret, encrypt sensitive attachments before uploading, and avoid putting passwords or recovery keys in notification bodies. Data at rest is not end-to-end encrypted by this recipe.

The smoke checks a live HTTP subscriber, actual publishing, cached reads, accounts, token ACLs, attachment bytes, and recovery after deleting the volume. It also verifies that an attachment is downloadable without login when its URL is known. No physical phone or background browser delivery was tested.

Clients and optional services

The browser and official Android/iOS clients are available upstream. An open browser can receive messages directly. Android can use direct server connections; Firebase is a separate delivery path. Instant iOS notifications from a self-hosted server need upstream polling, normally through ntfy.sh, while closed-browser Web Push needs VAPID keys and browser push services. This recipe configures none of those optional relays, SMTP, phone calls, or external storage. Read their connectivity and privacy requirements before enabling them; do not assume the CPU/server smoke proves mobile background delivery.

Security and maintenance

Keep the web port on loopback or behind a trusted VPN/proxy. Publish only through HTTPS, protect .env, grant automation accounts only their required topics, and encrypt external backups. ntfy does not provide native OIDC, SAML, MFA, or trusted-user-header login in this version. Forwarded client-IP trust stays off, so proxy requests share the proxy’s rate limit.

The declarative administrator hash is authoritative on every startup. Rotate it deliberately and preserve a matching environment during restore. A regular account and token created through the native CLI/API are stored in SQLite and are included in backup.

Upstream is dual licensed under Apache-2.0 and GPLv2; the recipe is MIT. The catalog icon comes from the upstream mask asset under the Apache-2.0 option; its license is retained in ASSET-LICENSES.md.

Sources: v2.34.0, configuration and access control, publishing, subscriptions, license.

Update tracking

These are stored results of separate checks, not real-time data. An unknown status means the check has not been performed yet.

New releases

Recipe version compared with the latest known upstream release. A version number, not a reading of what changed.

Current version v2.34.0Oct 25, 2026, 3:20 PM UTC

Critical vulnerabilities

Result of a dedicated Docker image scan, not a Compose configuration check.

No critical vulnerabilities foundOct 25, 2026, 3:21 PM UTC

Breaking updates

A new major version is found by comparing version numbers. A compatibility verdict is entered by hand after reading the release notes and the migration steps.

Not checkedNot checked

compose.yaml

Upstream Compose changes relative to the verified recipe.

Upstream Compose is not trackedNot checked

Outdated Docker images

Pinned image tags checked for newer versions.

All images are currentOct 25, 2026, 3:20 PM UTC

From observation to action

Update plan

The recipe already pins the latest known release. The steps below show the evidence retained for that version.

v2.34.0v2.34.0
  1. Open the release evidence

    Open this release on GitHub ↗ The link is evidence to review, not proof of compatibility.

  2. Review the version boundary

    The version numbers do not cross a major compatibility boundary. No compatibility verdict has been recorded.

  3. Prepare image-tag changes

    No image-tag change is currently planned.

  4. Follow the documented migration shape

    Change the tag and restart. Release-specific migration steps still come from the release notes and installation guide.

  5. Protect the rollback point

    No version change is currently planned. The recipe includes a backup procedure. Restore has been tested.

  6. Verify the updated recipe

    An exact Fully verified check for this target was recorded on 2026-10-25.

Read the update procedure in the installation guide ↓

Specifications

Security and exposure

Installation levels

The status shows how fully the published recipe meets each level's requirements.

Ready

For home

A minimal setup for a local network or access through a VPN.

  • Single server
  • Local access
  • Minimum components

The recipe covers every requirement at this level.

Ready

For a VPS

A public deployment with protected access and backups.

  • Domain
  • HTTPS via reverse proxy
  • Off-server backup

The recipe covers every requirement at this level.

Not supported

Reliable

A setup designed for predictable operations and observability.

  • Separate database
  • Healthcheck
  • External monitoring
  • CPU and memory limits

You still need to add: separate database, external monitoring, cpu and memory limits.

Tailored to your server

Configuration generator

Answer five questions and download a ready-to-run ntfy bundle.

Runs locally
01 Do you have a domain?
02 Which reverse proxy?
03 Server architecture
04 Where should data be stored?

Use an absolute path for a separate disk, such as /srv/ntfy.

05 Is VPN access required?

Your bundle is ready

The archive is created in your browser. Your answers are never sent anywhere.

The same files as one shell script, written in plain heredocs so you can read the whole thing before it runs. It checks Docker, the architecture, memory, disk and the ports it needs, and writes nothing until you add --apply.

Screenshots

Quick start

Read the full guide first: it explains external access, backups, and data locations.

Prepare settingscp .env.example .env
Launchdocker compose up -d
Check statusdocker compose ps

Step-by-step guide

Requirements and credentials

Use Ubuntu 22.04+ or Debian 12+ on amd64/arm64 with Docker Engine and Compose v2.24+. Plan for 1 CPU, 128 MB RAM (256 MB recommended), and 2 GB disk plus growing message/attachment caches and off-server backups. Only amd64 was exercised.

Copy the complete recipe, scripts, and proxy/ directory. Create .env with mode 600, choose a strong unique administrator password, and use the pinned image to hash it:

cp .env.example .env
chmod 600 .env
docker run --rm -it binwiederhier/ntfy:v2.34.0 user hash

Put the printed hash in .env as NTFY_AUTH_USERS='ntfyadmin:<bcrypt-hash>:admin'. Single quotes preserve literal $; do not double them in .env. Keep the original password in a password manager. Do not paste the hash directly into Compose, where escaping rules differ.

Parameter Meaning
NTFY_VERSION Pinned app tag including v, initially v2.34.0.
NTFY_PORT Host loopback port, initially 8083.
NTFY_URL Exact browser origin, initially http://localhost:8083; external HTTPS URL behind a proxy. Used for attachment/client URLs.
NTFY_AUTH_USERS Single-quoted name:bcrypt:role administrator entry. Authoritative on startup, not a one-time setup password.
NTFY_DATA_VOLUME Named volume for cache.db, user.db and attachments/.
TZ IANA time zone, default Etc/UTC.
NTFY_BACKUP_DIR Optional absolute backup path; defaults to backups/ beside the recipe.

Start and verify

The scripts source .env as trusted shell configuration. Do not use a file from an untrusted source. The helper initializes the volume, then ntfy runs as UID/GID 1000 with a read-only root. Start and sign in as ntfyadmin with the password you hashed:

docker compose config --quiet
docker compose pull
docker compose up -d --wait --wait-timeout 180
curl --fail http://127.0.0.1:8083/v1/health

Open NTFY_URL, sign in, and subscribe to household. Send a test notification; curl prompts for the password without putting it in the command:

curl --fail -u ntfyadmin -d 'Test notification' http://127.0.0.1:8083/household

VPS

Set NTFY_URL to the external HTTPS origin and put a host proxy in front of loopback port 8083. Permit SSH/HTTP/HTTPS only, verify login and blocked anonymous topic access, and keep an encrypted backup off the VPS. The browser UI can load before login, but protected topic data cannot be read anonymously.

Trusted local network

Use ssh -L 8083:127.0.0.1:8083 user@server with the matching local URL, or publish through an HTTPS LAN/VPN proxy. If you deliberately bind to a private host IP, restrict it with a firewall. Keep authentication on. Attachment URLs remain bearer links even on a private topic.

Domain and HTTPS

Set NTFY_URL=https://notify.example.com and recreate the service. proxy/ includes host-side Caddy, Nginx and Traefik examples for 127.0.0.1:8083. Caddy manages certificates; Nginx needs certificate paths; Traefik needs websecure and a letsencrypt resolver. A containerized proxy needs a shared network and internal port 8080 instead of its own loopback. Keep websocket upgrades and streaming responses enabled; Nginx buffering is disabled and the request cap is 15 MB. Forwarded client-IP trust remains disabled, so visitors share the proxy IP’s rate limit; do not enable header trust until only the actual proxy can reach the service and client-supplied headers are sanitized.

Accounts and retention

The declarative administrator entry is reapplied on every startup. To rotate it, generate a new hash, update the single-quoted NTFY_AUTH_USERS entry, and recreate ntfy. For automation create a regular account and explicit topic permissions; run these interactive commands and create a token in that account’s UI:

docker compose exec ntfy ntfy user add backupjob
docker compose exec ntfy ntfy access backupjob backups rw

Messages and attachments are cached for seven days; attachment storage is capped at 1 GB and individual files at 15 MB. Clearing browser history does not erase the server cache. Attachment URLs can be shared, so treat them as secrets and encrypt sensitive files before upload. Web Push, iOS upstream polling, Firebase, SMTP and calls are not enabled; configure and test background delivery separately using the official documentation.

Backup

Run ./backup.sh. It stops the writer and archives the entire named volume with .env and Compose in one mode-600 backups/ntfy-.tar. The payload contains cache.db, user.db, attachments, account settings, subscriptions, ACLs, tokens, and the administrator hash. It preserves the previous running/stopped state. Encrypt the archive and copy it off-server; a live SQLite file or a browser export alone is not a complete backup.

Restore

Restore replaces the current volume. Use a trusted backup, the matching pinned app version and NTFY_AUTH_USERS from archived configuration.env. Compare the saved environment manually before restore; it is not activated. On a new host initialize the matching recipe first. The script validates the payload and both database names, creates a safety backup, stops the writer and replaces data. A replacement failure leaves ntfy stopped.

docker compose up -d --wait --wait-timeout 180
./restore.sh ./backups/ntfy-TIMESTAMP.tar
curl --fail http://127.0.0.1:8083/v1/health

Sign in, read a saved message, download a sample attachment and publish a new message with a regular account/token. Confirm another topic and anonymous reads remain denied. Original retention timestamps still apply: recovery does not extend an old attachment’s lifetime.

Update

Read upstream release notes, make and export a backup, and change NTFY_VERSION to a reviewed fixed tag. Run docker compose pull and docker compose up -d --wait --wait-timeout 180, then inspect logs, log in, and test publishing/subscribing with a non-admin account. SQLite migrations run at startup. Preserve credentials and review client/server compatibility; do not use latest.

Rollback

Do not run an older server on a database migrated by a newer version. Stop ntfy, set the old fixed version and matching NTFY_AUTH_USERS in .env, pull and recreate without starting, then restore the complete pre-update backup. The safety backup reads the stopped volume before replacement. Keep the failed installation’s archive until recovery is confirmed.

docker compose stop ntfy
# Set the old pinned version and matching NTFY_AUTH_USERS in .env.
docker compose pull ntfy
docker compose create ntfy
./restore.sh ./backups/ntfy-BEFORE-UPDATE.tar

Stop and remove

docker compose down keeps the named data volume. After checking an encrypted off-server backup, docker compose down --volumes permanently removes messages, attachments, accounts, tokens, ACLs and settings. Removing the recipe directory also removes .env and any local backups.

Sources: configuration, Docker installation, publishing, subscription clients, v2.34.0.

Report a recipe problem

Tell us how it went on your server

Opens a prefilled GitHub issue. A GitHub account is required; submitting the issue makes it public.

Troubleshooting

Startup or healthcheck failure

Run docker compose config --quiet, docker compose ps, and docker compose logs --tail=100 ntfy permissions. Confirm the permissions helper exited successfully and UID/GID 1000 can write the volume. /v1/health must return healthy true. An invalid bcrypt entry prevents startup: generate it with the pinned image’s user hash command and single-quote it in .env. Do not paste plaintext or an unquoted dollar-containing hash.

Login, token, or topic permission denied

Sign in as the name before the first colon in NTFY_AUTH_USERS. Its hash is declarative and reapplied at startup; changing an old plaintext password note or using a CLI password command on a provisioned user does not rotate it. Create a normal account for automation, inspect docker compose exec ntfy ntfy user list, and grant only required topics with ntfy access. A regular user’s token inherits that user’s ACLs. A topic name is not an access credential.

Messages arrive in the open page but not in the background

The server smoke proves HTTP delivery, not OS push. Check the client’s server URL, login, network reachability and notification permissions. Browser Web Push needs HTTPS/VAPID and browser push services; self-hosted iOS instant push needs upstream polling; Android direct connections have their own background/battery requirements. Those integrations are not configured by this recipe. Use the upstream client-specific setup instead of disabling authentication.

Broken attachment URL or unexpected download without login

Match NTFY_URL to the exact externally reachable origin, recreate ntfy, and test a newly uploaded sample. Attachment URLs deliberately work as bearer links without login; topic ACLs do not protect a leaked link. Encrypt sensitive files before upload. Both messages and attachments expire after seven days, and restore keeps original timestamps. Check disk space and the 1 GB total / 15 MB per-file limits.

Proxy disconnects or rate limits

Preserve websocket upgrades and streaming HTTP responses, disable proxy buffering and use long read timeouts. With forwarded-IP trust disabled, requests share the proxy IP’s rate limit. Keep host port 8083 private; a containerized proxy cannot use its own 127.0.0.1 to reach a host service. Do not trust arbitrary X-Forwarded-For values to bypass rate limiting.

Missing data after restore

Check that the complete archive includes both SQLite databases and attachments, and compare configuration.env manually before restoring. A mismatched NTFY_AUTH_USERS entry overrides restored administrator credentials. Keep the current volume and safety backup while investigating; expired cache data does not become fresh after recovery. Re-test a message, sample attachment, regular user and scoped token.

Sources: configuration, publishing, subscriptions.

Official sources

Latest release: 2026-10-24 · GitHub Stars: 35,024 · metadata checked Oct 24, 2026, 8:36 AM UTC

Disclaimer

Fossary is an independent informational catalog and is not affiliated with the developers of the listed applications. We collect information from public sources and verify published recipes, but we do not develop, distribute, or control these applications.

A listing or validation status is not a requirement, endorsement, or personal recommendation to use an application. You decide whether it is suitable and use it at your own risk. We do not warrant an application's security, availability, or fitness for purpose and, to the extent permitted by law, are not liable for resulting loss or damage. Review its license, security settings, data processing, and backup procedures before installation.