## 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: ```bash 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::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: ```bash 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: ```bash 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: ```bash 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. ```bash 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. ```bash 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](https://docs.ntfy.sh/config/), [Docker installation](https://docs.ntfy.sh/install/#docker), [publishing](https://docs.ntfy.sh/publish/), [subscription clients](https://docs.ntfy.sh/subscribe/), [v2.34.0](https://github.com/binwiederhier/ntfy/releases/tag/v2.34.0).