# ntfy turns a topic name into a phone notification, and the server is one Go binary

> ntfy is an HTTP-based pub-sub notification service: a script PUTs to a topic and a phone app subscribes to it, with no account and no fee. The repository is the Go server, and its own README carries no install command at all, so a self-hoster has to start from the compose file, the Dockerfiles and the docs site.

**binwiederhier/ntfy** — Send push notifications to your phone or desktop using PUT/POST

- Repository: https://github.com/binwiederhier/ntfy
- Website: https://ntfy.sh
- Stars: 34,568 · Forks: 1,626
- Language: Go
- License: Apache-2.0
- Published: 2026-08-17 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/binwiederhier-ntfy

## The topic name is the address, and HTTP is the transport

ntfy, pronounced notify, is described as a simple HTTP-based pub-sub notification service. Publishing means sending to a topic, subscribing means listening to one, and the topic name is the only address either side needs to agree on. That is the whole design: no account, no sign-up, no fee, and notifications reach a phone or a desktop from scripts on any computer. The delivery side exists as separate open source apps, an Android app on Google Play and F-Droid and an iOS app on the App Store, so the client is not locked to the hosted instance.

The Go module behind all of this is published as heckel.io/ntfy/v2, and pkg.go.dev hosts its documentation, which means the client code is something a Go program imports rather than something it shells out to. The module declares go 1.26.0, and a .go-version file at the repository root pins the same toolchain for contributors, with .goreleaser.yml handling packaged releases.

## The README answers no install question, so the docs site carries that work

Worth knowing before you start: this README contains no install command, no config keys and no example of a request. What it offers is a documentation site with five named sections, Getting started, Android/iOS, API, Install / Self-hosting, and Building, and each is a separate page. Everything a self-hoster needs, from the config file format to the auth options, lives behind those links rather than in the repository front page.

The docs are themselves a build artifact in this tree. A mkdocs.yml at the root plus requirements.txt pinning mkdocs-material and mkdocs-minify-plugin means the site is MkDocs Material with minification, and the same requirement file carries a comment explaining that mkdocs is written in Python. So the documentation toolchain is Python on a project whose server is Go, and anyone regenerating the site needs both. A .gitpod.yml at the root and a Gitpod badge in the README point at the hosted alternative to building that environment locally.

## The compose file publishes host port 80 and mounts config as a file

What the repository does ship for running the server is a compose file, and it is the fastest accurate picture of a self-hosted instance:

```yaml
services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy
    command:
      - serve
    environment:
      - TZ=UTC    # optional: Change to your desired timezone
    user: UID:GID # optional: Set custom user/group or uid/gid
    volumes:
      - /var/cache/ntfy:/var/cache/ntfy
      - /etc/ntfy:/etc/ntfy
    ports:
      - 80:80
    restart: unless-stopped
```

Three details decide how much work an install is. Configuration arrives as a bind mount of /etc/ntfy rather than as environment variables, so the config file is the artifact you version and back up. Authentication and topics are not configured in this file at all, which tells you a fresh container starts with whatever the mounted file says and nothing more. And the process runs as UID:GID, written as a placeholder you have to replace with real values, since a container left on its default user cannot write the /var/cache/ntfy mount it is given.

## The image is one binary copied into Alpine, listening on port 80

The Dockerfile is short enough to read in full, and it explains the shape of the deployment:

```dockerfile
FROM alpine

RUN apk add --no-cache tzdata
COPY ntfy /usr/bin

EXPOSE 80/tcp
ENTRYPOINT ["ntfy"]
```

There is no package manager, no shell tooling and no runtime installed beyond Alpine's base, and no source in the image. The ntfy binary is built elsewhere and copied in as a single file at /usr/bin, which is why the build stage is a separate concern: the tree carries Dockerfile-arm and Dockerfile-build alongside this one. The tzdata package is installed for one reason, so that the TZ environment variable the compose file sets has real zone information to resolve against.

The image labels state the licenses as Apache-2.0, GPL-2.0, matching the LICENSE and LICENSE.GPLv2 files at the root, and name the vendor and documentation site. Port 80 is both exposed and published, so a stock deployment listens on port 80 of the host, which is a decision to revisit before putting it behind anything.

## The module graph names every backend the server talks to

go.mod is the most information-dense file in the tree, because each dependency is a place the server reaches outside itself. Two SQL drivers are compiled in: github.com/mattn/go-sqlite3 for a file-backed database and github.com/jackc/pgx/v5 for Postgres, so a single binary serves either and a migration is a config change rather than a rebuild. Delivery is a second cluster: firebase.google.com/go/v4 for Android push, github.com/SherClockHolmes/webpush-go for browser web push, github.com/emersion/go-smtp for the mail path, and github.com/stripe-go/v74 plus a payments/ directory for the paid tier.

The directory listing confirms the same split at a coarser level, with mail/, twilio/, webpush/, attachment/, s3/ and ban/ each covering one integration. The consequence for an operator is that this is a hub, not a pipe, and every one of those paths is a third-party account you supply credentials for. A twilio/ directory that exists in the source does not mean SMS is configured, and the compose file shows no environment variables for any of them.

## One SMTP dependency is pinned to an old version on purpose

Near the bottom of go.mod sits a replace directive that contradicts the version requested above it:

```go
module heckel.io/ntfy/v2

go 1.26.0
```

```yaml
replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pin version due to breaking changes, see #839
```

The require block asks for go-smtp v0.25.0, and the replace rewrites it to v0.17.0 with the comment naming breaking changes and issue 839. That is a maintainer choosing an older release of a mail library over adapting to a newer one, and it is the kind of decision that belongs in a server project rather than in a library, since anyone importing heckel.io/ntfy/v2 inherits the pin. The second consequence is about the docs and the server: the mail path is maintained against an API two minor versions behind, so a change in that library will not arrive here until the pin is revisited.

## Eleven example directories are the closest thing to an interface spec

The examples/ directory is where the protocol becomes concrete, and it is arranged as matching pairs. Publishing and subscribing are both shown in Go, PHP and Python, under publish-go, subscribe-go, publish-php, subscribe-php, publish-python and subscribe-python, so the same flow can be read in whichever language a reader already uses. Two web examples, web-example-eventsource and web-example-websocket, show the two subscription styles a browser can use.

The rest are whole use cases rather than fragments: ssh-login-alert, linux-desktop-notifications and a grafana-dashboard. That last one is the most informative for a team, because a dashboard example implies a metrics endpoint exists, and the tree carries a metrics/ directory along with github.com/prometheus/client_golang in the module. Nothing in the README explains the request format, so these directories are where a reader has to go, and a topic name that collides with a reserved one is a mistake the examples may or may not warn about.

## The hosted instance and ntfy Pro sit on top of the same open source server

The business model is stated in the README directly. ntfy.sh is free to use with no sign-up, and the maintainer offers paid ntfy Pro plans for anyone who does not want to self-host, at a floor of $5 per month, with donations also accepted through GitHub Sponsors and Liberapay. The payments/ directory and the Stripe dependency are how that plan is charged for, which means the paid tier and the self-hosted server are the same codebase with a billing path attached rather than two products.

On movement, the last push to the main branch was on 2026-09-15, and the recent releases are v2.28.0 on 2026-08-27, v2.27.0 on 2026-08-04 and v2.26.3 on 2026-07-20, so tags land every few weeks with patch releases between them. The repository is not archived, and it carries SECURITY.md and CODE_OF_CONDUCT.md. For a self-hoster the practical split is that the $5 plan buys someone else running the container, while running it yourself costs the host and leaves the payments path switched off.

## Conclusion

ntfy fits a homelab, a small team, or a script that has to page a human without provisioning a notification service first, and self-hosting is the better choice once the topic names carry operational meaning. Before you commit, check two things: that the /etc/ntfy mount in the compose file holds the configuration you intend to use, and that the delivery path you depend on, Firebase for Android, web push, SMTP through mail/, or Twilio through twilio/, is one you can supply credentials for. Anyone who needs an audited SLA, a vendor relationship, or an Android build outside the project should look elsewhere, because ntfy.sh is a single maintainer's hosted instance and the paid floor on ntfy Pro is $5 a month.

## FAQ

### What is ntfy and what does it do?

ntfy, pronounced notify, is a simple HTTP-based pub-sub notification service. You can send notifications to your phone or desktop via scripts from any computer, without having to sign up or pay fees, and a free version is available at ntfy.sh with open source Android and iOS apps.

### How much does ntfy cost to use?

The free version at ntfy.sh has no sign-up and no fees. The maintainer also offers paid ntfy Pro plans for anyone who would rather not self-host, for as low as $5 per month, and accepts donations through GitHub Sponsors and Liberapay.

### Can ntfy be self-hosted?

Yes, the project is open source and the repository ships a Dockerfile, a Dockerfile-arm, a Dockerfile-build and a docker-compose.yml that runs the image binwiederhier/ntfy with the command serve, mounting /etc/ntfy for configuration and /var/cache/ntfy for its cache. Installation and self-hosting have their own section in the documentation at ntfy.sh/docs.

### How do I get notifications from ntfy on iOS?

There is an open source iOS app published on the App Store, with beta builds distributed through TestFlight. The documentation has a dedicated Android/iOS section for subscribing on a phone, and release announcements go to the ntfy.sh/announcements topic on the hosted instance.

## Sources

- [Official documentation](https://ntfy.sh)
- [Official README](https://github.com/binwiederhier/ntfy#readme)
- [Project repository](https://github.com/binwiederhier/ntfy)
- [Release notes](https://github.com/binwiederhier/ntfy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/binwiederhier-ntfy
