Observability AWS

Gatus 5.36.0 on AWS User Guide

| Product: Gatus 5.36.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying Gatus 5.36.0 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Uptime Kuma, Gatus & Uptime and Status Pages AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other products in the listing are described on the listing's guide hub.

Gatus is an open source health dashboard and status page. You describe the services you want to watch in one YAML file: each endpoint has a URL (HTTP, HTTPS, TCP, ICMP, DNS, SSH, STARTTLS, WebSocket or gRPC), a check interval and a list of conditions such as [STATUS] == 200, [RESPONSE_TIME] < 300, [BODY].status == UP or [CERTIFICATE_EXPIRATION] > 720h. Gatus runs the checks, keeps the history, and shows a live dashboard with uptime and response-time charts, a JSON API and status badges. It can alert you through Slack, Microsoft Teams, email, PagerDuty, Discord, Telegram, ntfy and many other providers.

The image runs Gatus 5.36.0 compiled by cloudimg from the official 5.36.0 source with Go 1.27.3 and patched golang.org/x/net, golang.org/x/crypto and gRPC modules. Gatus publishes no release binary of its own (only container images), and the 5.36.0 source does not build with the current Go release until gRPC is updated. The build is reproducible, and its record ships with the image. The base is a fully patched Ubuntu 24.04 LTS, and unattended security upgrades stay on for your instance.

What is on the image:

  • Gatus 5.36.0 (gatus.service) running as the unprivileged gatus user, listening on 127.0.0.1:8080 only
  • nginx publishing the dashboard over HTTPS on port 443 with a certificate created on your instance's first boot, behind HTTP Basic Auth with a password unique to your instance; port 80 redirects to HTTPS
  • A starter configuration at /etc/gatus/config.yaml that monitors this instance only (its HTTPS front end and certificate expiry, Gatus itself, and SSH), so the dashboard is green on first boot. Nothing on the internet is probed until you add it.
  • gatus-set-hostname, a helper to serve the dashboard under your own DNS name
  • The build record (BUILD-INFO.txt), go.mod, go.sum, the govulncheck results and the Apache 2.0 licence in /usr/share/doc/gatus/

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3

Security posture

  • Password on everything. Gatus has no login by default, and its own optional login leaves the badge, uptime and history routes open. So nginx protects every path (the dashboard, the JSON API, the badges and Gatus' health route) with HTTP Basic Auth for the user admin. The password is generated on each instance's first boot, stored only as a bcrypt hash in /etc/nginx/.gatus.htpasswd, and written in plain text to /root/gatus-credentials.txt (mode 0600, root only). No password or hash ships in the image.
  • Fails closed. nginx refuses to start until first boot has created the certificate and the password, so the dashboard is never reachable without a login.
  • Loopback only. Gatus listens on 127.0.0.1 only. nginx on 443 is the only way in.
  • HTTPS on first boot. nginx serves the dashboard with a TLS key and self-signed certificate created on your instance (TLS 1.2 and 1.3 only). Port 80 only redirects to HTTPS.
  • Fixed host name. The dashboard is served under a name taken from the instance metadata at first boot (or one you set), never from the request. Requests for any other host name are dropped, and cross-site form posts and WebSocket connections are refused.
  • Real client addresses. nginx passes the connecting address to Gatus and ignores any X-Forwarded-For header a client sends.
  • No metadata access for the checks. Gatus fetches whatever URLs you configure, so gatus.service and nginx.service are blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). A check pointed at the metadata service fails instead of leaking it. Amazon Time Sync keeps working.
  • Least privilege. gatus.service runs with no Linux capabilities, a read-only system (ProtectSystem=strict) except /var/lib/gatus, and its own private /tmp.
  • No surprise traffic. The starter configuration checks only the instance itself; the Prometheus /metrics endpoint is off.
  • IMDSv2 required. The AMI is registered with ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

Known advisory in a bundled library

govulncheck reports one advisory against the binary, GO-2026-5932: the golang.org/x/crypto/openpgp package is deprecated and described by the Go team as unsafe by design, and it has no fixed version. It is compiled in only because the GitHub library behind Gatus' GitHub-issue alerting provider imports it for signing commits, which Gatus never does. Every call path govulncheck finds reaches it only through package initialisation, and the build fails if that ever changes. Every other finding was fixed by the module updates. The full output ships in /usr/share/doc/gatus/govulncheck-binary.txt and govulncheck-source.txt.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A subnet that gives the instance a public IPv4 address (or private connectivity from where you will view the dashboard), with outbound access to the services you want to monitor

Recommended instance type: m5.large. Gatus is a single small Go process with an embedded SQLite database, so a smaller burstable type also works for a modest number of endpoints.

Connecting to your instance

Version Operating system SSH user
Gatus 5.36.0 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened Uptime Kuma, Gatus & Uptime and Status Pages AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Gatus 5.36.0 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. Security group: the listing's default group allows SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address, and 80/443 to the people who need the status page.
  6. Choose your VPC and a subnet that gives the instance a public IP, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH and the dashboard from your management address only, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:

SG=$(aws ec2 create-security-group --group-name gatus \
  --description "Gatus" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr <your-mgmt-cidr>
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=gatus}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required.

Step 2: Connect and read your login

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It creates the HTTPS certificate, generates the password and starts nginx, and then Gatus starts its checks. Your login is in a root-only file:

sudo cat /root/gatus-credentials.txt
# Gatus - generated on first boot by gatus-firstboot.service
# These credentials are unique to this instance. Store them somewhere safe.

GATUS_URL=https://ec2-3-93-242-11.compute-1.amazonaws.com/
GATUS_USERNAME=admin
GATUS_PASSWORD=<GATUS_PASSWORD>
#
# Open GATUS_URL in a browser (self-signed certificate minted on this instance's first boot; port 80
# redirects to https) and sign in with the username and password above. The same credentials protect
# the JSON API (/api/v1/endpoints/statuses) and the status badges.
# Change the name it is served as:  sudo gatus-set-hostname <your-dns-name>
#
# The default endpoints monitor this instance only. Add your own services to /etc/gatus/config.yaml
# (Gatus reloads it automatically). Restrict 80/443 in the security group to the people who need the page.
# Full deployment guide: https://www.cloudimg.co.uk/guides/gatus-5-36-0-on-ubuntu-24-04-aws/

GATUS_PASSWORD holds your instance's own password.

Step 3: Confirm the services are running

First boot, Gatus and nginx are active, Gatus runs as the unprivileged gatus user, and the open health endpoint answers:

systemctl is-active gatus-firstboot.service gatus.service nginx.service
ps -o user= -C gatus
test "$(curl -sk https://localhost/healthz)" = ok && echo "healthz ok"
active
active
active
gatus
healthz ok

/healthz is served by nginx without a login, so you can use it for a load balancer health check over HTTPS. Everything else needs the password.

These are the only ports the instance listens on:

sudo ss -Hltnu | awk '{print $1, $5}' | sort -u
tcp 0.0.0.0:22
tcp 0.0.0.0:443
tcp 0.0.0.0:80
tcp 127.0.0.1:8080
tcp 127.0.0.53%lo:53
tcp 127.0.0.54:53
tcp [::]:22
tcp [::]:443
tcp [::]:80
udp 127.0.0.1:323
udp 127.0.0.53%lo:53
udp 127.0.0.54:53
udp 172.31.83.83%ens5:68
udp [::1]:323

Gatus (8080) listens on loopback only; nginx publishes it on 443. The loopback port 53 entries are Ubuntu's local stub resolver (systemd-resolved), 323 is chrony's local control socket and 68 is the DHCP client.

The binary is the reproducible cloudimg build of the 5.36.0 source. Its record ships with the image:

sha256sum /usr/local/bin/gatus
grep -E '^(source|toolchain |binary|govulncheck)' /usr/share/doc/gatus/BUILD-INFO.txt
07676fb59df06f69deb9d1c155c21eff7da6d0bb4e32585ed82d06cc7f982a66  /usr/local/bin/gatus
source            https://github.com/TwiN/gatus/archive/refs/tags/v5.36.0.tar.gz
source sha256     b5543af591e602281406049ee2f822a6529a8f14be0cd54df5a31c210520159a (tag commit ed1107b41a30e22047eecfb6dbc3be5e39829d5a)
toolchain         go1.27.3 linux/amd64, official tarball https://go.dev/dl/go1.27.3.linux-amd64.tar.gz
toolchain sha256  65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40
binary sha256     07676fb59df06f69deb9d1c155c21eff7da6d0bb4e32585ed82d06cc7f982a66 (reproducible)
govulncheck       v1.9.0 -mode=binary and source mode: only GO-2026-5932 (DB updated: 2026-10-21 00:27:29 +0000 UTC)

Step 4: Sign in to the dashboard

Browse to the GATUS_URL from Step 2, for example https://ec2-3-93-242-11.compute-1.amazonaws.com/. The certificate is self-signed, so your browser warns once; accept it, or install your own certificate (Step 8). Without a login nginx answers 401, and your browser asks for a username and password:

nginx answering 401 Authorization Required to a browser request without credentials

Sign in as admin with the password from Step 2. The Gatus health dashboard lists the three starter endpoints, each with its latest result and a bar per check:

The Gatus health dashboard showing the gatus, nginx-https and ssh endpoints of this instance, all Healthy

Select an endpoint for its detail page: current status, average and range of response times, the recent checks and a response-time chart:

The Gatus detail page for the nginx-https endpoint showing Operational status, response times and recent checks

Port 80 only redirects to the HTTPS address:

curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://localhost/
301 https://ec2-3-93-242-11.compute-1.amazonaws.com/

You can check the login from the command line too. These requests go to nginx on the instance itself: the API refuses a request with no password or a wrong one, and the instance's own password (read from the credentials file and passed to curl on standard input, never on the command line) returns the endpoint statuses. The block stops with an error if any answer is not the expected one:

H=$(sudo sed -n 's/^set $gatus_host "\(.*\)";$/\1/p' /etc/nginx/gatus-host.conf)
AUTH() { sudo sed -n 's/^GATUS_PASSWORD=\(.*\)$/user = "admin:\1"/p' /root/gatus-credentials.txt; }
U=$(curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}' "https://$H/api/v1/endpoints/statuses")
W=$(printf 'user = "admin:not-the-password"\n' | curl -K - -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}' "https://$H/api/v1/endpoints/statuses")
A=$(AUTH | curl -K - -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}' "https://$H/api/v1/endpoints/statuses")
echo "unauth  : $U"; echo "wrongpw : $W"; echo "authed  : $A"
[ "$U" = 401 ] && [ "$W" = 401 ] && [ "$A" = 200 ]
unauth  : 401
wrongpw : 401
authed  : 200

Step 5: Read the results from the API

The dashboard is built on a JSON API you can use from scripts. This lists every endpoint with how many results Gatus holds and whether the latest check passed, and fails unless all three starter endpoints are healthy:

H=$(sudo sed -n 's/^set $gatus_host "\(.*\)";$/\1/p' /etc/nginx/gatus-host.conf)
AUTH() { sudo sed -n 's/^GATUS_PASSWORD=\(.*\)$/user = "admin:\1"/p' /root/gatus-credentials.txt; }
J=$(AUTH | curl -K - -sk --resolve "$H:443:127.0.0.1" "https://$H/api/v1/endpoints/statuses")
echo "$J" | jq -r '.[] | "\(.key)  results=\(.results|length)  last_success=\(.results[-1].success)"'
test "$(echo "$J" | jq -r '[.[] | select(.results[-1].success == true) | .name] | sort | join(",")')" = "gatus,nginx-https,ssh"
this-instance_gatus  results=8  last_success=true
this-instance_nginx-https  results=8  last_success=true
this-instance_ssh  results=8  last_success=true

Each endpoint's key is <group>_<name>. The detail of one check shows each condition and its result:

H=$(sudo sed -n 's/^set $gatus_host "\(.*\)";$/\1/p' /etc/nginx/gatus-host.conf)
AUTH() { sudo sed -n 's/^GATUS_PASSWORD=\(.*\)$/user = "admin:\1"/p' /root/gatus-credentials.txt; }
AUTH | curl -K - -sk --resolve "$H:443:127.0.0.1" "https://$H/api/v1/endpoints/this-instance_nginx-https/statuses" \
  | jq -e '.results[-1] | {status, success, conditionResults}'
{
  "status": 200,
  "success": true,
  "conditionResults": [
    {
      "condition": "[STATUS] == 200",
      "success": true
    },
    {
      "condition": "[BODY] == pat(ok*)",
      "success": true
    },
    {
      "condition": "[CERTIFICATE_EXPIRATION] > 720h",
      "success": true
    }
  ]
}

Status badges live under the same API, for example https://<your-host>/api/v1/endpoints/this-instance_ssh/health/badge.svg, and also need the password.

Step 6: Add your own endpoints

The configuration is /etc/gatus/config.yaml, readable only by root and the gatus service account, because alerting tokens you add will live there. The starter file:

sudo stat -c '%a %U:%G %n' /etc/gatus/config.yaml
sudo grep -vE '^\s*#|^$' /etc/gatus/config.yaml
640 root:gatus /etc/gatus/config.yaml
storage:
  type: sqlite
  path: /var/lib/gatus/data.db
web:
  address: 127.0.0.1
  port: 8080
metrics: false
ui:
  title: "Status | Gatus"
  header: "Service Health"
  description: "Live health and uptime of the services monitored by this instance."
endpoints:
  - name: nginx-https
    group: this-instance
    url: "https://127.0.0.1/healthz"
    interval: 60s
    client:
      insecure: true
    conditions:
      - "[STATUS] == 200"
      - "[BODY] == pat(ok*)"
      - "[CERTIFICATE_EXPIRATION] > 720h"
  - name: gatus
    group: this-instance
    url: "http://127.0.0.1:8080/health"
    interval: 60s
    conditions:
      - "[STATUS] == 200"
      - "[BODY].status == UP"
  - name: ssh
    group: this-instance
    url: "tcp://127.0.0.1:22"
    interval: 60s
    conditions:
      - "[CONNECTED] == true"

Edit it with sudoedit (or your editor of choice through sudo) and append your services under endpoints:. A website with a response-time and certificate check, a database port, and a DNS record:

sudoedit /etc/gatus/config.yaml
  - name: website
    group: production
    url: "https://www.example.com/"
    interval: 60s
    conditions:
      - "[STATUS] == 200"
      - "[RESPONSE_TIME] < 800"
      - "[CERTIFICATE_EXPIRATION] > 336h"

  - name: postgres
    group: production
    url: "tcp://10.0.1.20:5432"
    interval: 30s
    conditions:
      - "[CONNECTED] == true"

  - name: dns-www
    group: production
    url: "1.1.1.1"
    dns:
      query-name: "www.example.com"
      query-type: "A"
    conditions:
      - "[DNS_RCODE] == NOERROR"

Gatus checks the file for changes every 30 seconds and reloads it on its own; you do not need to restart it. If an edit does not parse, Gatus logs the error and exits, and systemd keeps restarting it every few seconds, so the dashboard is down until you correct the file; it then comes back by itself. Check the service and the log after each change:

systemctl is-active gatus
sudo journalctl -u gatus -n 20 --no-pager -o cat

To be notified when a check fails, add an alerting: block (for example Slack, email or PagerDuty) and an alerts: list on each endpoint; the Gatus alerting documentation has an example for every provider. Keep the file 0640 root:gatus.

The Gatus log shows each check as it runs:

sudo journalctl -u gatus -n 3 --no-pager -o cat | grep -E 'success=(true|false)'
2026/10/21 13:24:55 [watchdog.executeEndpoint] Monitored group=this-instance; endpoint=nginx-https; key=this-instance_nginx-https; success=true; errors=0; duration=0s
2026/10/21 13:24:55 [watchdog.executeEndpoint] Monitored group=this-instance; endpoint=gatus; key=this-instance_gatus; success=true; errors=0; duration=1ms
2026/10/21 13:24:55 [watchdog.executeEndpoint] Monitored group=this-instance; endpoint=ssh; key=this-instance_ssh; success=true; errors=0; duration=0s

Step 7: Monitoring from a private subnet

Gatus checks run from this instance, so it can watch services that are not on the internet at all: databases, internal APIs and load balancers in the same VPC or in peered VPCs. Make sure the security groups of those services allow traffic from this instance's security group. ICMP (icmp://) checks work too, without giving Gatus any capability: the image allows unprivileged ICMP sockets for the gatus group only (/etc/sysctl.d/60-cloudimg-gatus-ping.conf).

Step 8: HTTPS certificate and host name

The certificate was created on your instance's first boot for the instance's public DNS name, public IP, private IP and localhost. The key, the password hash and the credentials file are readable only by root (nginx's group can read the hash):

sudo openssl x509 -in /etc/ssl/gatus/gatus.crt -noout -subject -dates -ext subjectAltName
sudo stat -c '%a %U:%G %n' /etc/ssl/gatus/gatus.key /etc/ssl/gatus/gatus.crt /etc/nginx/.gatus.htpasswd /root/gatus-credentials.txt
subject=CN = ec2-3-93-242-11.compute-1.amazonaws.com, O = cloudimg
notBefore=Oct 21 13:17:50 2026 GMT
notAfter=Jan 23 13:17:50 2029 GMT
X509v3 Subject Alternative Name: 
    DNS:ec2-3-93-242-11.compute-1.amazonaws.com, IP Address:3.93.242.11, IP Address:172.31.83.83, IP Address:127.0.0.1, DNS:localhost
600 root:root /etc/ssl/gatus/gatus.key
644 root:root /etc/ssl/gatus/gatus.crt
640 root:www-data /etc/nginx/.gatus.htpasswd
600 root:root /root/gatus-credentials.txt

To serve the dashboard under your own DNS name (for example status.example.com, pointed at this instance), run the helper. It records the name, creates a new self-signed certificate for it, updates nginx and rewrites the URL in the credentials file:

sudo gatus-set-hostname status.example.com

If the instance's public address changes after a stop and start, run the helper again so the certificate names the new address too. To use a certificate from your own certificate authority, replace /etc/ssl/gatus/gatus.key (keep it 0600 root) and /etc/ssl/gatus/gatus.crt, then run sudo systemctl reload nginx.

Step 9: Metadata access is blocked for Gatus

gatus.service and nginx.service carry a systemd IPAddressDeny rule for the instance metadata addresses, so no check can reach the metadata service, even if an endpoint's URL pointed at it. Each entry is a single address, so the Amazon Time Sync Service at 169.254.169.123 keeps working, as chrony shows:

systemctl show -p IPAddressDeny gatus.service nginx.service
chronyc -n sources | grep 169.254
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
^* 169.254.169.123               3   4   377    10  -4996ns[-5971ns] +/-  619us

Maintenance

  • Password: the per-instance password is in /root/gatus-credentials.txt and its bcrypt hash in /etc/nginx/.gatus.htpasswd. To change it, run sudo htpasswd -B /etc/nginx/.gatus.htpasswd admin (it prompts for the new password), then sudo systemctl reload nginx and update the credentials file. Add more viewers with the same command and a different user name.
  • A public status page: the image keeps the whole page behind the password. If you want a page anyone can read, decide which paths to open and add an nginx location with auth_basic off; for them in /etc/nginx/sites-available/cloudimg-gatus, then sudo nginx -t && sudo systemctl reload nginx. Remember that the API and the badges then show your endpoint names and history to anyone.
  • Keep Gatus on loopback: leave web.address: 127.0.0.1 in the configuration. Gatus has no password of its own, so binding it to a public address would publish your dashboard and API to anyone.
  • History: results are kept in the SQLite database /var/lib/gatus/data.db. Back up that file (with Gatus stopped, or with sqlite3 .backup) together with /etc/gatus/config.yaml.
  • Upgrades: to move to a newer Gatus release, launch the newer version from the listing when it is published and copy your config.yaml across.
  • Security patches: unattended-upgrades remains enabled so the OS keeps receiving security updates.

Security recommendations

  • Keep the security group tight. Open 22 only to your management address, and 80/443 only to the people who need the dashboard.
  • Keep alerting tokens in the config file only, with its 0640 root:gatus permissions, and rotate them if the instance is ever shared.
  • Use your own certificate for the dashboard if other people sign in to it, so browsers trust it without a warning.

Support and licensing

Gatus is developed by TwiN and contributors and distributed under the Apache License 2.0. This image ships a binary that cloudimg compiled from the unmodified 5.36.0 source with Go 1.27.3 and updated Go modules; the build record and licence are in /usr/share/doc/gatus/. cloudimg provides the packaging, the hardening, first-boot HTTPS and per-instance credentials, and 24/7 support.

cloudimg is not affiliated with or endorsed by the Gatus project. Product names are marks of their owners and are used here only to identify the software.

For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.