Skip to content

Authentication

The ntfy engine supports three first-class authentication modes for the outbound publish request, modeled by AuthMode, a sealed type with three variants: BearerToken, BasicAuth, and None. Which variant is active is derived automatically from which of token / username / password you configure — there is no separate "auth mode" switch. This is engine-wide: it applies identically whether you configure via env/sysprop/ntfy.properties, Logback XML, the Log4j2 <Ntfy> attributes, Spring ntfy.*, or Quarkus quarkus.ntfy.*. (For the full list of where each key lives, see configuration.md.)

The three modes

BearerToken

Set token. The request is sent with an Authorization: Bearer <token> header. This is the recommended mode for a private ntfy topic protected by an access token.

# Spring application.yml
ntfy:
  url: https://ntfy.example.com
  topic: my-app-alerts
  token: tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Quarkus application.properties
quarkus.ntfy.url=https://ntfy.example.com
quarkus.ntfy.topic=my-app-alerts
quarkus.ntfy.token=tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
<!-- Log4j2 log4j2.xml -->
<Ntfy name="ntfy" url="https://ntfy.example.com" topic="my-app-alerts"
      token="tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
# Core / plain Logback / plain Log4j2 via environment
export NTFY_URL=https://ntfy.example.com NTFY_TOPIC=my-app-alerts
export NTFY_TOKEN=tk_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

BasicAuth

Set both username and password. The request uses HTTP Basic authentication. Use this when the ntfy server (or a reverse proxy in front of it) is gated by a username/password pair rather than a token.

Both username and password must be non-blank. If only one of the two is set, the pair is ignored and the engine falls back to None mode: every publish goes out with no Authorization header. The engine emits a one-time startup warning for this half-configured state (username or password set but not both — basic auth is incomplete; publishing WITHOUT an Authorization header) — unless a token is also configured, in which case the token supersedes basic auth entirely and only the token-plus-basic overlap warning below can apply. Activation still proceeds. Against a protected server the observable symptom is ntfy publish failed for topic '<topic>' (HTTP 401) (or 403) diagnostics — see troubleshooting.md. Against a permissive server the publishes may even succeed, unauthenticated, while you believe auth is in effect. If Basic Auth appears not to apply, first check that both values are actually set and non-blank (a ${VAR} that resolves to an empty string counts as blank).

Verifying credentials at startup

Every credential problem above shares the same failure mode: nothing looks wrong until the first real error alert fails to deliver, which is the worst possible moment to discover a revoked token. The opt-in startup self-test closes that gap by making one round-trip at boot and reporting the result:

ntfy.startup-ping=publish

A revoked, expired or insufficiently-scoped credential then surfaces immediately as ntfy startup self-test FAILED (HTTP 401) — the server rejected the credentials; the token may be revoked or expired, or it may not grant access to this topic.

Prefer publish over probe when it is credentials you want to verify. ntfy grants read and write permissions separately, so probe — a read-only request — is passed by a read-only token that would still fail every real alert, and on an open server anonymous reads make it prove nothing at all. No self-test diagnostic ever echoes the token, username or password.

None

Set neither token nor username/password. Publishes go out with no Authorization header at all. None is a valid, first-class configuration — not a misconfiguration — and is the correct mode for a public ntfy.sh topic or a self-hosted server whose publish endpoint is deliberately open.

Precedence: token wins over Basic Auth

If you configure both a token and a username/password pair, the engine does not fail and does not refuse to start. Instead:

  • BearerToken takes precedence — every request uses the token, and the configured username/password are ignored for the Authorization header.
  • A one-time warning is emitted at startup: both token and username/password configured — token takes precedence.
  • Startup still proceeds and alerting still activates normally. Authentication configuration is never a reason to block activation.

This lets you leave stale username/password values in place (for example while migrating from Basic Auth to a token) without breaking anything — the token simply wins, and the one-time warning tells you the overlap exists so you can clean it up.

Credentials over plain http://

If credentials would traverse a cleartext http:// endpoint, the Authorization header (or the URL's embedded userinfo) and every alert body are readable by any on-path observer. The engine detects this at startup for a configured token, a username/password pair, or userinfo embedded in the URL itself (http://user:pass@host — flagged even when no separate credential is configured).

By default (since 2.0, require-https-for-credentials = true) this is a hard failure: the engine refuses activation, emitting a fixed, credential-safe diagnostic, so a secret never traverses the network unencrypted. A credential-free http:// endpoint is unaffected.

For a deliberate self-hosted plain-HTTP setup, set require-https-for-credentials = false to restore the pre-2.0 warn-and-activate behavior: the engine then emits a one-time warning — credentials configured with a plain http:// URL — the token/password and alert content are sent unencrypted; use https:// — but still activates. The key is available on every configuration surface: Logback XML <requireHttpsForCredentials>, Log4j2 <Ntfy requireHttpsForCredentials="false">, Spring ntfy.require-https-for-credentials, Quarkus quarkus.ntfy.require-https-for-credentials, env NTFY_REQUIRE_HTTPS_FOR_CREDENTIALS, sysprop ntfy.require-https-for-credentials, or NtfyConfig.Builder.requireHttpsForCredentials(false) for programmatic core users.

The token is never surfaced in diagnostics

The engine's self-diagnostics (its ACTIVE line, publish-failure warnings, error messages) never include the token, username, or password value. The only credential-adjacent information that can appear is the url and topic — and even then, if you supply a URL with embedded userinfo (https://user:pass@host/…), the userinfo is stripped before the URL is logged. No diagnostic output ever echoes a secret. Where those diagnostics appear differs per framework (Logback's StatusManager for the Logback/Spring paths, Log4j2's StatusLogger, or System.err for the JUL/Quarkus paths) — see troubleshooting.md.

See also

  • configuration.md — where url, topic, token, username, and password live in each framework.
  • troubleshooting.md — the exact wording of the token-wins warning and every other diagnostic the engine can produce.