Gatus: Automated Endpoint, Website, and API Monitoring with Status Page and Telegram Alerts

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

The Problem with Traditional Monitoring

Prometheus + Grafana are powerful for server metrics, but when all you need to know is “is the website up?” or “is the API returning 200?” — spinning up an entire stack like that is overkill. Before using Gatus, I was SSH-ing into each server to check things manually. Now I just open the dashboard and see everything at a glance — the status of every endpoint, uptime history, and response times.

Gatus solves exactly one problem: simple endpoint health checks, a clean Status Page, and Telegram alerts when something goes wrong. No database, no agents to install on servers — the entire configuration is a single YAML file.

What Is Gatus and Why Use It?

Technically, Gatus is a single Go binary — deploy it and it runs immediately, with no runtime or additional dependencies. The footprint is tiny: RAM usage typically stays under 20MB, and startup takes under a second. The tool supports four types of checks:

  • HTTP/HTTPS — check status codes, body content, and response time
  • TCP — check whether a port is open (databases, Redis, etc.)
  • DNS — check whether a domain resolves correctly
  • ICMP (ping) — check whether a host is reachable

Compared to Zabbix or Netdata, Gatus does not monitor CPU/RAM/disk — that’s what other tools are for. Gatus focuses entirely on “is this service working correctly?” — a complementary perspective, not a replacement, for your existing monitoring stack.

What I like most about Gatus: the built-in Status Page, with no extra plugins or services needed. When a client asks “what’s wrong with the website?” — just share the status page link, no lengthy chat explanations required.

Installing Gatus with Docker Compose

Docker is the fastest way to get started. Create a working directory:

mkdir -p ~/gatus && cd ~/gatus
touch config.yaml docker-compose.yml

Contents of docker-compose.yml:

version: "3.8"
services:
  gatus:
    image: twinproduction/gatus:stable
    container_name: gatus
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./config.yaml:/config/config.yaml
    environment:
      - GATUS_CONFIG_PATH=/config/config.yaml

Start the container:

docker compose up -d
docker compose logs -f gatus

If you prefer not to use Docker, you can also build from source:

git clone https://github.com/TwiN/gatus.git
cd gatus
go build -o gatus .
./gatus --config config.yaml

Detailed Configuration

Basic config.yaml Structure

web:
  port: 8080
  # Optional: set base-path if using a reverse proxy subdirectory
  # base-path: /status

storage:
  type: sqlite          # Store uptime history in SQLite (under /data directory)
  path: /data/gatus.db

alerting:
  telegram:
    token: "1234567890:ABCdef..."  # Bot token from @BotFather
    id: "-100123456789"            # Chat ID (groups use a negative number)
    default-alert:
      enabled: true
      failure-threshold: 3         # Alert after 3 consecutive failed checks
      success-threshold: 2         # Notify recovered after 2 consecutive successful checks
      send-on-resolved: true       # Send a message when the service recovers

endpoints:
  - name: "Website itfromzero.com"
    url: "https://itfromzero.com"
    interval: 5m
    conditions:
      - "[STATUS] == 200"
      - "[RESPONSE_TIME] < 3000"   # Response time under 3 seconds
    alerts:
      - type: telegram

Monitoring Multiple Endpoint Types

endpoints:
  # HTTP endpoint with body check
  - name: "API Health Check"
    url: "https://api.example.com/health"
    interval: 2m
    conditions:
      - "[STATUS] == 200"
      - "[BODY] == {\"status\":\"ok\"}"   # Body must contain this JSON
      - "[RESPONSE_TIME] < 1000"
    alerts:
      - type: telegram
        failure-threshold: 2          # Override: alert after 2 failures

  # TCP — check database port
  - name: "PostgreSQL Port"
    url: "tcp://db.internal:5432"
    interval: 1m
    conditions:
      - "[CONNECTED] == true"
    alerts:
      - type: telegram

  # TCP — Redis
  - name: "Redis Cache"
    url: "tcp://redis.internal:6379"
    interval: 1m
    conditions:
      - "[CONNECTED] == true"
    alerts:
      - type: telegram

  # DNS check — verify domain resolves to the correct IP
  - name: "DNS itfromzero.com"
    url: "dns://1.1.1.1"
    dns:
      query-name: "itfromzero.com"
      query-type: "A"
    interval: 10m
    conditions:
      - "[DNS_RCODE] == NOERROR"

  # ICMP ping — check if server is reachable
  - name: "VPS Singapore"
    url: "icmp://103.x.x.x"
    interval: 5m
    conditions:
      - "[CONNECTED] == true"

Getting the Bot Token and Chat ID for Telegram

Open Telegram and message @BotFather — type /newbot, give your bot a name, then save the token in the format 1234567890:ABCdef... that BotFather returns.

To get the Chat ID of a group or channel:

# Add the bot to the group, then call the API:
curl "https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates"
# Find "chat":{"id":-100xxxxxxxxx} in the response

Using Environment Variables for Sensitive Values

Avoid hardcoding tokens in your config. Gatus supports environment variables using the ${VAR_NAME} syntax:

alerting:
  telegram:
    token: "${TELEGRAM_BOT_TOKEN}"
    id: "${TELEGRAM_CHAT_ID}"

Update docker-compose.yml to pass in the variables:

services:
  gatus:
    image: twinproduction/gatus:stable
    container_name: gatus
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./config.yaml:/config/config.yaml
      - gatus_data:/data
    env_file:
      - .env

volumes:
  gatus_data:

The .env file:

TELEGRAM_BOT_TOKEN=1234567890:ABCdef...
TELEGRAM_CHAT_ID=-100123456789

Testing and Using the Monitoring Dashboard

Accessing the Status Page

Once Gatus is running, open http://your-server-ip:8080 and the dashboard appears immediately. Each endpoint displays its current status (green/red), average response time, and a 7-day plus last-24-hour uptime history as a block timeline — it’s immediately obvious whether anything is wrong.

To expose it to the internet via Nginx:

server {
    listen 80;
    server_name status.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Verifying Telegram Alerts Work

Add a test endpoint with a condition guaranteed to fail, then remove it afterwards:

endpoints:
  - name: "Test Alert"
    url: "https://httpstat.us/503"   # Always returns 503
    interval: 30s
    conditions:
      - "[STATUS] == 200"            # Will fail
    alerts:
      - type: telegram
        failure-threshold: 1          # Alert on the very first failure

Restart Gatus and wait 30 seconds — if everything is configured correctly, Telegram will receive an alert message with the endpoint details and the reason for the failure.

Viewing Logs and Debugging

# View logs in real time
docker compose logs -f gatus

# Verify the config is parsed correctly
docker compose exec gatus cat /config/config.yaml

# Restart after editing the config
docker compose restart gatus

Practical Tips for Day-to-Day Operation

  • Set failure-threshold to >= 2 to avoid false alerts caused by temporary network hiccups
  • Use interval: 1m for critical production endpoints and 5m for less critical ones — avoid generating unnecessary requests
  • Mount the /data volume to a host path so uptime history persists across container restarts
  • If Gatus runs on the same server as the services it monitors, add an external check from a separate server to avoid false positives when the entire server goes down

With this setup, I no longer have to worry about my website going down without me knowing. The entire endpoint monitoring stack costs just one YAML file and one container — far simpler than setting up a Prometheus exporter for every individual service.

Share: