Skip to main content

Self-Host Gotify: Real-Time Push Notifications 2026

·OSSAlt Team
gotifynotificationspushself-hostingdockerwebsocket2026

TL;DR

Gotify (MIT, ~10K GitHub stars, Go) is a self-hosted push notification server with a web UI, Android app, and REST API. Unlike ntfy (topic-based pub/sub), Gotify uses an application/client model — create an app, get a token, send messages. Gotify keeps a persistent message history you can browse in the web UI or query via API. Pushover charges $5 one-time per platform; Gotify is free with no limits.

Key Takeaways

  • Gotify: MIT, ~10K stars, Go — push notifications with persistent message history
  • Application model: Create apps (senders) and clients (receivers) — organized, not anonymous topics
  • Web UI: Browse message history, manage apps, configure settings in a clean interface
  • Android app: Native app with WebSocket real-time delivery — no polling delay
  • REST API: Simple token-based API for sending messages from any script or service
  • vs ntfy: Gotify has persistent history and web UI; ntfy has simpler sending syntax and iOS app

Gotify vs ntfy vs Pushover

FeatureGotifyntfyPushover
Sending modelApp tokensTopic URLsAPI keys
Message historyYes (persistent)Brief cacheNo
Web UIYesNoNo
iOS appNo (web only)YesYes
Android appYes (native)Yes (native)Yes
Self-hostedYesYesNo (cloud)
WebSocketYesYesNo
PriceFreeFree$5 one-time

Part 1: Docker Setup

# docker-compose.yml
services:
  gotify:
    image: gotify/server:latest
    container_name: gotify
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - gotify_data:/app/data
    environment:
      GOTIFY_DEFAULTUSER_NAME: admin
      GOTIFY_DEFAULTUSER_PASS: "${ADMIN_PASSWORD}"
      GOTIFY_SERVER_PORT: 80
      GOTIFY_SERVER_KEEPALIVEPERIODSECONDS: 0
      GOTIFY_SERVER_LISTENADDR: ""
      GOTIFY_SERVER_SSL_ENABLED: "false"
      GOTIFY_DATABASE_DIALECT: sqlite3
      GOTIFY_DATABASE_CONNECTION: "data/gotify.db"
      GOTIFY_PASSSTRENGTH: 10
      GOTIFY_UPLOADEDIMAGESDIR: "data/images"
      GOTIFY_PLUGINSDIR: "data/plugins"
      GOTIFY_REGISTRATION: "false"   # Disable public signup
      TZ: America/Los_Angeles

volumes:
  gotify_data:
docker compose up -d

Visit http://your-server:8080 → log in with admin credentials.


Part 2: HTTPS with Caddy

gotify.yourdomain.com {
    reverse_proxy localhost:8080
}

Part 3: Create Apps and Send Messages

Create an application

  1. Apps → Create Application
  2. Name: Home Server, Monitoring, CI Pipeline
  3. Description: optional
  4. Create → copy the App Token

Send a notification

APP_TOKEN="your-app-token"
BASE="https://gotify.yourdomain.com"

# Simple message:
curl -X POST "$BASE/message" \
  -H "X-Gotify-Key: $APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Backup Complete",
    "message": "nightly backup finished successfully",
    "priority": 5
  }'

# Or via form data (simpler):
curl -X POST "$BASE/message?token=$APP_TOKEN" \
  -F "title=Backup Complete" \
  -F "message=Finished at $(date)" \
  -F "priority=5"

Priority levels

PriorityBehavior (Android)
0Silently update badge
1-3Low — quiet notification
4-7Normal — default notification
8-10High — loud, stays visible

Part 4: Android App

  1. Install Gotify Android from F-Droid or GitHub releases
  2. + Add server:
    • URL: https://gotify.yourdomain.com
    • Username + password
  3. Real-time WebSocket connection — notifications arrive instantly

Client token vs App token

  • App token: Used by senders to POST messages
  • Client token: Used by the Android app to receive messages (auto-created when you log in)

Part 5: REST API

BASE="https://gotify.yourdomain.com"

# Authenticate (get client token):
curl -X POST "$BASE/client" \
  -u admin:password \
  -H "Content-Type: application/json" \
  -d '{"name": "my-script"}'

# List all messages:
curl "$BASE/message" \
  -u admin:password | jq '.messages[].message'

# Delete a message:
curl -X DELETE "$BASE/message/42" \
  -u admin:password

# Delete all messages for an app:
curl -X DELETE "$BASE/application/APP_ID/message" \
  -u admin:password

# List applications:
curl "$BASE/application" \
  -u admin:password | jq '.[] | {id, name, token}'

# Real-time stream (WebSocket):
# Connect to: wss://gotify.yourdomain.com/stream?token=CLIENT_TOKEN

Part 6: WebSocket Real-Time Stream

Subscribe to real-time notifications via WebSocket:

// Browser JavaScript:
const ws = new WebSocket(
  "wss://gotify.yourdomain.com/stream?token=CLIENT_TOKEN"
);

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log(`[${msg.appid}] ${msg.title}: ${msg.message}`);
  
  // Show browser notification:
  new Notification(msg.title, { body: msg.message });
};
# Python:
import websocket
import json

def on_message(ws, message):
    msg = json.loads(message)
    print(f"[{msg['appid']}] {msg['title']}: {msg['message']}")

ws = websocket.WebSocketApp(
    "wss://gotify.yourdomain.com/stream?token=CLIENT_TOKEN",
    on_message=on_message
)
ws.run_forever()

Part 7: Integrations

Uptime Kuma

  1. Uptime Kuma → Notifications → Add → Gotify
  2. Server URL: https://gotify.yourdomain.com
  3. Token: your app token
  4. Priority: 8

Grafana

# Grafana → Contact Points → Webhook
URL: https://gotify.yourdomain.com/message?token=APP_TOKEN
Method: POST
Content-Type: application/json
Body template:
{
  "title": "Grafana Alert: {{ .GroupLabels.alertname }}",
  "message": "{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}",
  "priority": 8
}

Cron notifications

#!/bin/bash
# /usr/local/bin/cron-notify.sh
# Usage: cron-notify.sh "App token" "Job name" command [args...]

TOKEN="$1"
JOB="$2"
shift 2

OUTPUT=$("$@" 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
  curl -s -X POST "https://gotify.yourdomain.com/message?token=$TOKEN" \
    -H "Content-Type: application/json" \
    -d "{
      \"title\": \"Cron Failed: $JOB\",
      \"message\": \"Exit code: $EXIT_CODE\n$OUTPUT\",
      \"priority\": 8
    }"
fi

Watchtower Docker updates

services:
  watchtower:
    image: containrrr/watchtower
    environment:
      WATCHTOWER_NOTIFICATIONS: gotify
      WATCHTOWER_NOTIFICATION_GOTIFY_URL: "https://gotify.yourdomain.com/"
      WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN: "your-app-token"

Home Assistant

# configuration.yaml
notify:
  - platform: rest
    name: gotify
    resource: "https://gotify.yourdomain.com/message"
    method: POST_JSON
    headers:
      X-Gotify-Key: "your-app-token"
    message_param_name: message
    title_param_name: title
    data:
      priority: 7

Part 8: Message Markdown

Gotify supports Markdown in message bodies — rendered in the web UI and Android app:

curl -X POST "$BASE/message?token=$APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Deploy Report",
    "message": "## Deployment Successful\n\n**Version**: 2.3.1\n**Environment**: Production\n**Duration**: 45s\n\n```\nBuilt 3 services\nPushed to registry\nDeployed to prod\n```",
    "priority": 5,
    "extras": {
      "client::display": {
        "contentType": "text/markdown"
      }
    }
  }'

Maintenance

# Update:
docker compose pull
docker compose up -d

# Backup (SQLite):
docker cp gotify:/app/data/gotify.db \
  ./gotify-backup-$(date +%Y%m%d).db

# Or full data directory:
tar -czf gotify-data-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect gotify_gotify_data --format '{{.Mountpoint}}')

# Check message counts by app:
docker exec gotify sqlite3 /app/data/gotify.db \
  "SELECT a.name, COUNT(m.id) as msgs FROM application a
   LEFT JOIN message m ON a.id = m.applicationid
   GROUP BY a.id ORDER BY msgs DESC;"

# Logs:
docker compose logs -f gotify

See also: ntfy — simpler pub/sub alternative with native iOS app and no account required

See all open source productivity tools at OSSAlt.com/categories/productivity.

Comments