Deployment guide

Gatus 5.36.0

Overview

Gatus is an open source health dashboard and status page. You list the services you want to watch in one YAML file, and Gatus runs the checks, keeps the history and shows a live dashboard with uptime and response-time charts, a JSON API and status badges. This guide covers launching and verifying Gatus 5.36.0 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened Uptime Kuma, Gatus & Uptime and Status Pages AMIs listing on AWS Marketplace, which you select as the software version when you subscribe and launch. The other products in the listing are described on the listing's guide hub.

What you get:

  • Simple monitoring configuration. 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.
  • Alerting. Gatus can alert you through Slack, Microsoft Teams, email, PagerDuty, Discord, Telegram, ntfy and many other providers.
  • Gatus 5.36.0 (gatus.service) running as the unprivileged gatus user, listening on 127.0.0.1:8080 only.
  • HTTPS and a login in front. nginx publishes 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.
  • A reproducible build. SecureAnyCloud compiles Gatus 5.36.0 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 record ships with the image.
  • Build record and licence files. The build record (BUILD-INFO.txt), go.mod, go.sum, the govulncheck results and the Apache 2.0 licence are in /usr/share/doc/gatus/.
  • A patched base. The base is a fully patched Ubuntu 24.04 LTS, and unattended security upgrades stay on for your instance.
  • 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. nginx therefore 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, and 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, the Go team describes it 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

You need:

  • An AWS account and permission to launch EC2 instances.
  • An EC2 key pair in the Region you launch in.
  • 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

Connect over SSH as ubuntu, the SSH user for Gatus 5.36.0 on Ubuntu 24.04 (Ubuntu 24.04 LTS).

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened Uptime Kuma, Gatus & Uptime and Status Pages AMIs listing on AWS Marketplace. Choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  • Once the subscription is active, choose Launch your software.
  • 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.
  • Pick an instance type (m5.large recommended) and your key pair. The software charge is $0.04 per vCPU per hour.
  • Set the security group. The listing's default group allows SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address, and restrict 80/443 to the people who need the status page.
  • Choose your VPC and a subnet that gives the instance a public IP. Leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.

Option B: AWS CLI

Run this command 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 replace 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.

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. Read your login from 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://secureanycloud.com/stacks/gatus-5-36-0/

GATUS_PASSWORD holds your instance's own password.

3

Confirm the services are running

Confirm that first boot, Gatus and nginx are active, that Gatus runs as the unprivileged gatus user, and that 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, and nginx publishes it on 443. The loopback port 53 entries are Ubuntu's local stub resolver (systemd-resolved). Port 323 is chrony's local control socket, and port 68 is the DHCP client.

The binary is the reproducible SecureAnyCloud 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)
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:

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:

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

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 returns the endpoint statuses.

The command reads the password from the credentials file and passes it to curl on standard input, never on the command line. It 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
5

Read the results from the API

The dashboard is built on a JSON API that you can use from scripts. This command lists every endpoint with how many results Gatus holds and whether the latest check passed. It 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.

6

Add your own endpoints

The configuration is /etc/gatus/config.yaml. Only root and the gatus service account can read it, because the alerting tokens you add will live there. This is 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:. This example adds 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, so 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. The dashboard is down until you correct the file, and then it 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
7

Monitoring from a private subnet

Gatus runs its checks from this instance, so it can watch services that are not on the internet at all: databases, private 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-sac-gatus-ping.conf).

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, except that nginx's group can read the hash. Inspect the certificate:

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 = sac 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, point the name at this instance (for example status.example.com) and 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.

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 points 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 is in /etc/nginx/.gatus.htpasswd. To change it, run sudo htpasswd -B /etc/nginx/.gatus.htpasswd admin, which prompts for the new password. Then run 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. Add an nginx location with auth_basic off; for them in /etc/nginx/sites-available/sac-gatus, then run sudo nginx -t && sudo systemctl reload nginx. 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 open 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 SecureAnyCloud 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/.

SecureAnyCloud provides the packaging, the hardening, first-boot HTTPS and per-instance credentials, and 24/7 support.

SecureAnyCloud 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 sales@secureanycloud.com or visit SecureAnyCloud support.

Deploy on AWS

Launch Gatus 5.36.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS