Skip to main content
Photo of Anton ShubinAnton ShubinSenior Full-Stack Engineer & Tech Lead

Self-hosting

zond: a 10 MB probe bridge so Gatus can see through your SSO proxy

Health checks behind Authelia fail because Gatus cannot follow SSO redirects. Zond sits beside your services on the Docker network and answers 200 or 503 — no auth bypass, no internal URLs leaked, one config file.

Anton Shubin6 min read

The tool: ZondCode: github.com/spy4x/zond (opens in a new tab)

TL;DR

  • Gatus can't health-check services behind Authelia or Authentik, because it can't follow SSO redirects, and a TCP check misses a service that answers 500.
  • zond sits on the same Docker network, takes Gatus's probe on a public URL and checks the service by its Docker name, answering 200 or 503.
  • It needs no authentication: it only says whether a service is up, which your status page already shows.
  • I rewrote it from Deno to Go after a 180 ms cold start on a Raspberry Pi; the image shrank from about 80 MB to about 10 MB.

Every self-hosted homelab eventually hits the same monitoring problem: you want Gatus to tell you that your services are healthy, but the services sit behind Authelia or Authentik, and Gatus cannot follow SSO redirects without either accepting 302 as "healthy" (false positive) or standing up a service-specific monitoring user (operational debt).

zond is the 10 MB probe bridge I built to solve this. It runs on the same Docker network as your services, gets probed by Gatus over the public URL, and forwards the probe to the internal service over the Docker DNS name. No auth bypass, no exposed internal URLs.

It feeds the Gatus checks behind my status page; how I run production shows where it sits.

The first instinct is to use a TCP check in Gatus: tcp://hl-metube:8081. That tells you a port is open. It does not tell you the HTTP layer is healthy. A service can listen on its port and still return 500 on every request — and a TCP check will report it as up.

You need a real HTTP probe. The problem is that the service you want to probe is behind SSO, and your monitoring tool does not have credentials.

┌──────────┐     ┌──────────┐     ┌─────────────┐
│  Gatus   │────>│  Zond    │────>│ hl-metube   │
│ (cloud)  │     │ (home)   │     │ :8081       │
└──────────┘     └──────────┘     └─────────────┘

Three containers, one config file:

# zond.yml
port: 8080
targets:
  - name: metube
    url: http://hl-metube:8081/
  - name: ollama
    url: http://hl-ollama:11434/api/tags
    timeout: 10000
  - name: grafana
    url: http://hl-grafana:3000/api/health
    timeout: 3000

One line in Gatus:

- name: Metube
  url: "https://zond.example.com/health/metube"
  conditions:
    - "[STATUS] == 200"

That is the whole setup. Zond returns only ok\n or unreachable\n. No data leaks. No session to steal. No action to perform on the URL.

What I learned rewriting it from Deno to Go

Link to section: What I learned rewriting it from Deno to Go

The first version was Deno + TypeScript on this homelab. It worked. The image was ~80 MB stripped. The single binary was nice. Then I tried to ship it on a friend's lower-end box (Raspberry Pi 4, 1 GB RAM) and the Deno cold start was 180ms — fine for human traffic, ugly for a probe endpoint that fires every 30s.

The Go rewrite:

  • ~10 MB distroless image
  • 8 MB cold start
  • stdlib HTTP server with one external dep (go.yaml.in/yaml/v3)
  • Single static binary, no runtime to install

The rewrite took two evenings. The Deno version had grown some habits I had to break: dynamic Deno.serve, structured logging, async-ergonomic fan-out. The Go version has none of that and is easier to reason about.

I am increasingly convinced that monitoring endpoints belong in Go. The perf ceiling is irrelevant — the predictability matters. Zond will boot in 8 ms on the same Raspberry Pi in five years. The Deno binary might not.

Zond returns ok or ko. There is nothing to protect. No session, no data, no action. Adding auth would reintroduce the exact problem Zond solves: now you have to manage credentials for a monitoring endpoint, and you have an attack surface that did not exist before.

If you really need to hide the existence of an internal service, proxy Zond behind your SSO — but the endpoint itself is safe to expose. The worst case is an attacker learns "metube is up" or "metube is down", which is exactly what your uptime page already says.

Source: github.com/spy4x/zond. The Docker image is at ghcr.io/spy4x/zond:latest. The README has a five-minute setup with Gatus.

If you want this kind of monitoring set up and watched for your product, that is part of Ongoing: server health and performance monitoring, and triage for production incidents.

Need this for your product?

Anton Shubin

Senior Full-Stack Engineer & Tech Lead

I build greenfield SaaS on a modern, lightweight stack, alone or with a team of senior developers from my own pool, and I cover full-stack, DevOps and architecture.

Get the next post by email

New posts on decisions for founders, AI and MCP, and self-hosting, about once a week at most, and only when there is one. Unsubscribe with one click.

Or follow the RSS feed