Self-Hosted Alerts

Stop Relying on Paid Alerts: Deploy Gotify for Instant Phone Notifications

Gotify Self-Hosted Push Notification Server Architecture and Mobile Alerts
Self-hosted Gotify push notification server routing real-time REST API alerts from Proxmox and Uptime Kuma.

If you are running a home lab filled with critical self-hosted services, data pipelines, and scheduled backups, visibility into the health of your system is absolutely crucial. For years, I stubbornly relied on an incredibly messy, duct-taped chain of external services to tell me when things went wrong. I used Pushover (which requires paid licenses), unreliable Telegram bots, and complex SMTP email relays to know when my Proxmox cluster dropped a node or Nextcloud simply stopped syncing.

The massive problem with this approach? The subscription costs for proprietary notification APIs slowly add up over time, and automated alert emails inevitably get buried under massive mountains of daily spam in my Gmail inbox. When my primary ZFS backup disk physically failed last year, I didn't even find out about the catastrophe until I went actively looking for a restored file two days later. The critical email alert had been silently routed straight to my junk folder by an overzealous algorithm.

That frustrating outage is precisely when I decided to rip out all the paid, cloud-dependent services and deployed Gotify into my infrastructure.

Gotify is a completely free, open-source, entirely self-hosted notification server written in the highly efficient Go programming language. It runs quietly in the background of your server using almost zero memory resources, and it pushes real-time, instantaneous alerts straight to your smartphone via persistent WebSockets. Because you own and control the server software natively, you maintain absolute control over the data privacy of your alerts, and there are absolutely no artificial usage limits, subscription tiers, or API request fees. It is liberating.

In this comprehensive, deep-dive guide, I will show you exactly how I architect and deploy Gotify via Docker Compose, set up segmented application tokens for security, hook the API natively into Proxmox VE for cluster health monitoring, and write highly custom bash scripts that alert you to system degradation before a minor hiccup becomes a full-blown weekend outage.

Prerequisites and Environment Setup

Before deploying the containers, ensure your home lab environment currently has the following foundational pieces in place:

  • A Compute Environment (Home Server or VM): You need a machine running a modern Linux distribution (like Debian 12, Ubuntu 24.04, or Alpine) with both Docker and the Docker Compose plugin correctly installed and running.
  • A Functional Reverse Proxy (Highly Recommended): If you want to receive these push notifications while you are physically away from your house (e.g., at the office or on cellular data) without having to stay permanently connected to a battery-draining VPN, you must securely expose Gotify to the internet. I strongly recommend using a Cloudflare Zero Trust Tunnel, Nginx Proxy Manager, or Caddy to handle the routing and SSL certificate termination securely.
  • An Android Device for the Client: The official, natively supported Gotify client application is built specifically for Android and is available on the Google Play Store and F-Droid. If you are deeply entrenched in the Apple ecosystem and use iOS, you will need to utilize a third-party client workaround app (like Forwarder) because iOS background process limitations severely restrict native WebSocket push notifications. This guide strictly focuses on optimizing the native Android experience.

Step-by-Step Implementation and Deployment

1. Architecting the Docker Compose Stack

As a systems engineer, I heavily prefer to keep all my Docker container data cleanly organized in a strict, unified directory structure rather than scattering volumes across the filesystem. Deploying Gotify via Docker Compose is the cleanest, most resilient, and most easily reproducible method. First, open your terminal and create the dedicated directory structure on your server:

sudo mkdir -p /opt/docker/gotify && cd /opt/docker/gotify

Next, use your favorite text editor (like nano or vim) to create a file named precisely docker-compose.yml and paste in the following production-ready configuration:

services:
  gotify:
    image: gotify/server:latest
    container_name: gotify-server
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      - GOTIFY_DEFAULTUSER_PASS=ChangeThisHighlySecurePasswordImmediately123!
      - TZ=America/New_York
    volumes:
      - ./data:/app/data

Why did we configure the stack exactly like this?

  • image: By specifically defining gotify/server:latest, I ensure the container automatically pulls the most stable, up-to-date branch when initialized, rather than risking an outdated, potentially vulnerable build.
  • environment: The default administrative password for the web GUI desperately needs to be overwritten immediately upon boot. Do not, under any circumstances, leave this as the default "admin". Furthermore, I explicitly hardcode the Timezone variable (TZ) so that all incoming message timestamps accurately and natively reflect my local physical time zone. There is absolutely nothing more frustrating than auditing critical server crash logs and trying to calculate UTC offsets in your head while exhausted at 2:00 AM.
  • volumes: Gotify elegantly uses a lightweight SQLite database file stored internally at /app/data. By firmly binding this specific path to a local ./data directory on the host filesystem, I guarantee that my configuration settings, user accounts, application tokens, and entire historical message database perfectly survive container updates, crashes, and server migrations.

Once the configuration file is saved, instruct Docker to pull the image and run the container in detached background mode:

docker compose up -d

2. Initial GUI Setup and Generating Application Tokens

With the container running happily, open your favorite web browser and navigate to the web interface at http://YOUR-SERVER-IP:8080 (or your domain name, if you have already put it behind your reverse proxy). Log into the dashboard using the username admin and the highly secure password you defined in the environment variables of the Compose file.

The fundamental architecture of Gotify revolves around the concept of "Applications." Think of an Application as an isolated, individual sender identity. You absolutely do not want your massive Proxmox hypervisor cluster and your lightweight Uptime Kuma monitoring instance sharing the exact same sender token. By aggressively isolating them into separate Applications, you gain the ability to instantly revoke a single specific token if it ever gets compromised, without breaking your entire, lab-wide alerting pipeline. It also crucially allows you to upload custom, recognizable thumbnail icons to different services so you can visually identify the source of the alert on your phone's lock screen in a fraction of a second.

  1. Navigate to the Apps tab in the top navigation bar.
  2. Click the blue Create Application button.
  3. Name this specific application "Proxmox Cluster Health" and hit Create.
  4. Gotify will instantly generate a highly complex cryptographic Token (it will always start with the letter A). Copy this entire token string; we absolutely need it for the next critical integration steps.
Gotify web interface Applications tab showing the Create Application button for generating isolated API tokens
Creating isolated sender identities and cryptographic API tokens inside the Gotify web interface.

3. Native Alert Integration with Proxmox VE

One of the most massive, workflow-changing wins for deploying Gotify is its native, built-in integration in modern versions of Proxmox VE (version 8.1 and higher). I no longer need to write fragile, custom Python wrapper scripts just to parse system logs and find out if a critical VM backup snapshot failed to execute during the night. Proxmox handles it natively.

Here is the exact process for how I configure it on my bare-metal cluster to ensure maximum visibility:

  1. Log into your Proxmox web GUI. Select Datacenter at the cluster level, navigate down to Notifications, and open the Notification Targets menu.
  2. Click the Add button and choose Gotify from the dropdown list of supported endpoints.
  3. Name the target something logical, like gotify-critical-alerts.
  4. Enter your full Server URL (e.g., https://gotify.myhomelab.com or http://192.168.1.50:8080 if remaining purely local).
  5. Paste the Application Token you generated in the previous step into the authentication field.
  6. Click the Test button. You should instantly, within milliseconds, hear a chime and see a test message hit your Gotify dashboard and your phone.

Next, to prevent notification fatigue, I highly recommend setting up a Notification Matcher in Proxmox. This allows you to aggressively filter the noise and route only truly critical events (such as storage replication failures, backup job errors, or High Availability fencing actions) directly to this Gotify target, while letting trivial informational logs remain silently in the system journal.

Proxmox VE Datacenter Notifications panel showing Notification Targets and Notification Matchers configuration
Configuring native Notification Targets and Notification Matchers at the Proxmox VE Datacenter level.

4. Building Custom Bash Script Alerts

While native integrations are fantastic, the true, raw beauty of Gotify is its spectacularly simple REST API. Literally anything that is capable of sending a basic HTTP POST request over a network can trigger a rich notification on your phone. Here is the exact, modular bash function I drop into the header of all my automated server maintenance and backup scripts:

send_gotify_alert() {
  local TITLE="$1"
  local MESSAGE="$2"
  local PRIORITY="$3"
  local URL="http://192.168.1.50:8080/message"
  # Replace with your actual application token!
  local TOKEN="Axxxxxxxxxxxxxx"

  curl -s -X POST "$URL" \
    -H "X-Gotify-Key: $TOKEN" \
    -F "title=$TITLE" \
    -F "message=$MESSAGE" \
    -F "priority=$PRIORITY" > /dev/null
}

# Real-world implementation example in a nightly backup script:
echo "Starting aggressive rsync data sync task..."
if rsync -avz --delete /data/ /backup/; then
  # Priority 2: Low urgency, silent notification
  send_gotify_alert "Backup Success" "Nightly rsync completed cleanly without errors." 2
else
  # Priority 9: CRITICAL urgency, triggers loud alarms
  send_gotify_alert "CRITICAL BACKUP FAILURE" "Error: rsync encountered a fatal error. Check logs immediately." 9
fi

Pay very close attention to the priority flag in the script above. Gotify uses this integer value to explicitly dictate how the Android operating system on your phone handles the incoming alert. I strictly use 2 (or lower) for successful, routine jobs, they silently drop into my notification drawer without vibrating or making a sound, allowing me to review them at my leisure. However, I use 9 for catastrophic failures. On Android, a Priority 9 Gotify message is designed to aggressively override system-level "Do Not Disturb" settings and trigger a loud, persistent, impossible-to-ignore alarm tone until I physically acknowledge it. It is incredibly effective.

Gotify All Messages stream displaying a real-time Proxmox vzdump backup status log notification
Live Proxmox vzdump snapshot completion log delivered directly into the Gotify Server Monitor stream.

Advanced Troubleshooting & Edge Case Resolution

If you deploy this architecture in a real-world, highly complex home lab, you will eventually hit a networking or OS-level snag. Here are the most common, deeply frustrating pitfalls I have personally run into, and exactly how you fix them:

Fatal WebSocket Disconnections Behind a Reverse Proxy:

If you expose Gotify via Nginx Proxy Manager, Traefik, or Caddy, you might eventually notice that push notifications magically stop arriving on your phone after a few hours of inactivity, requiring you to manually open the app to force a sync. This highly annoying behavior happens because your reverse proxy isn't explicitly configured to pass HTTP WebSocket (WSS) upgrade headers, causing the persistent, long-lived connection between your phone and the server to silently time out and drop.

The Fix: You must ensure that Upgrade $http_upgrade and Connection "upgrade" directives are actively set in your proxy host configuration blocks. If you are using the popular Nginx Proxy Manager GUI, this is incredibly simple: simply edit your proxy host details and aggressively toggle the "WebSockets Support" switch to the ON position.

Android's Aggressive Battery Optimization Killing the Background App:

Modern versions of the Android operating system are notoriously, aggressively ruthless about killing background applications to save marginal amounts of battery life. Since the Gotify client app must maintain a constant, persistent background WebSocket connection to listen for incoming data, Android's power manager will eventually flag it as a battery drain and sever the connection forcefully to save power, breaking your notifications.

The Fix: You must manually whitelist the application. Go into your Android system settings → Apps → Gotify → App Battery Usage → and explicitly set the policy to "Unrestricted" (or turn off battery optimization for the app, depending on your device manufacturer). This critically guarantees that the OS leaves the background socket connection alone, regardless of battery levels.

Database Permissions Lockout After a Container Update:

Occasionally, when migrating the stack to a brand new server host or pulling a major version bump in the Gotify Docker image, you can run into severe filesystem permissions issues where the container user cannot read the SQLite database, effectively locking you out of the web interface and throwing 500 internal server errors.

The Fix: Always thoroughly ensure the mounted ./data directory on your host is owned by the correct user executing the Docker daemon. Running a targeted sudo chown -R 1000:1000 ./data command usually instantly resolves these insidious UID/GID permission mismatches.

Conclusion and Next Steps for Your Architecture

Gotify is an absolutely phenomenal, set-and-forget piece of core infrastructure that pays massive dividends the very first time it successfully alerts you to a failed hard drive array or a downed critical container while you are away from your desk. It gives me absolute, uncompromising control over my telemetry data and permanently severs my dependency on rate-limited, expensive cloud APIs.

Now that Gotify is running, heavily tested locally, and fully integrated into your bash scripts, your absolute next step should be securely exposing it to the outside world so you aren't tied to your Wi-Fi. I highly recommend completely wrapping it in a Cloudflare Zero Trust Tunnel so you can receive alerts securely anywhere in the world without opening a single dangerous inbound port on your router's firewall.

Written by Tech Simpld

MCSA Certified Systems Engineer • 20+ Years Enterprise IT

Tech Simpld is maintained by a Systems Engineer managing virtual machines and enterprise storage architecture across five data centers. Every tutorial and configuration on this site is tested first-hand in our physical Proxmox VE home lab and daily Obsidian production vaults.