Deploy Gatus on a RamNode VPS
Developer-oriented health checks and status pages. HTTP, TCP, DNS, ICMP — all configured through a single YAML file, using 10–30 MB of RAM.
Part of a series: This standalone guide covers Gatus deployment end-to-end. For GitOps workflows and advanced condition syntax, see Part 3 of the VPS Monitoring & Observability Series.
At a Glance
| Application | Gatus v5.x (Apache 2.0) |
| Deployment | Docker + Docker Compose |
| Minimum VPS | 1 vCPU / 512 MB RAM / 10 GB SSD |
| Recommended VPS | 1 vCPU / 1 GB RAM / 25 GB SSD |
| OS | Ubuntu 22.04 LTS or 24.04 LTS |
| Ports | 8080 (app), 80 (HTTP), 443 (HTTPS) |
Prerequisites
- A RamNode VPS running Ubuntu 22.04 or 24.04 LTS
- SSH access with a non-root sudo user
- Docker and Docker Compose installed (see Step 1 if not)
- A domain name pointed at your VPS IP (for public status page with HTTPS)
- Ports 80 and 443 open in your firewall
Note: If you only need private monitoring, skip the Nginx and SSL sections and access the dashboard over port 8080 directly.
Install Docker and Docker Compose
Skip this step if Docker is already installed. To verify:
docker --version && docker compose versionIf not installed, use the official Docker repository:
sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER
newgrp dockerCreate the Project Directory
mkdir -p ~/gatus/{config,data}
cd ~/gatusgatus/
├── docker-compose.yml
├── config/
│ └── config.yaml # monitoring rules and alert settings
└── data/
└── data.db # SQLite database (auto-created)Write the Configuration File
Gatus is configured entirely through a single YAML file. Customize the endpoints and alert settings for your environment.
# --- Storage ---
# SQLite keeps uptime history across container restarts.
storage:
type: sqlite
path: /data/data.db
# --- Web UI settings ---
web:
port: 8080
# --- Optional: Protect the dashboard with a password ---
# Generate a bcrypt hash: htpasswd -bnBC 10 '' yourpassword | tr -d ':\n' | base64
# security:
# basic:
# username: admin
# password-bcrypt-base64: "<your-bcrypt-base64-hash>"
# --- Alerting ---
# Remove or comment out providers you do not need.
alerting:
slack:
webhook-url: "https://hooks.slack.com/services/REPLACE/ME"
default-alert:
failure-threshold: 2
success-threshold: 1
send-on-resolved: true
discord:
webhook-url: "https://discord.com/api/webhooks/REPLACE/ME"
default-alert:
failure-threshold: 2
send-on-resolved: true
email:
from: gatus@example.com
username: gatus@example.com
password: "${EMAIL_PASSWORD}"
host: smtp.example.com
port: 587
to: oncall@example.com
# --- Endpoints to monitor ---
endpoints:
- name: Main Website
group: Web
url: "https://example.com"
interval: 60s
conditions:
- "[STATUS] == 200"
- "[RESPONSE_TIME] < 1500"
- "[CERTIFICATE_EXPIRATION] > 168h" # Alert if cert expires < 7 days
alerts:
- type: slack
- type: email
- name: API Health
group: API
url: "https://api.example.com/health"
interval: 30s
conditions:
- "[STATUS] == 200"
- "[RESPONSE_TIME] < 500"
- '[BODY] == pat(*"status"*"ok"*)'
alerts:
- type: slack
description: "API health check failed"
- name: SSH Access
group: Infrastructure
url: "tcp://yourserver.example.com:22"
interval: 60s
conditions:
- "[CONNECTED] == true"
alerts:
- type: slack
- name: DNS Resolution
group: Infrastructure
url: "dns://1.1.1.1"
dns:
query-name: "example.com"
query-type: "A"
interval: 120s
conditions:
- "[DNS_RCODE] == NOERROR"
alerts:
- type: slackCreate the Docker Compose File
services:
gatus:
image: twinproduction/gatus:stable
container_name: gatus
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080" # Bind to localhost only - Nginx handles public traffic
volumes:
- ./config:/config
- ./data:/data
- /etc/localtime:/etc/localtime:ro
environment:
- GATUS_CONFIG_PATH=/config
# Uncomment and fill in if using email alerting:
# - EMAIL_PASSWORD=your_smtp_password
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10scd ~/gatus
docker compose up -d
# Check that the container started correctly
docker compose ps
docker compose logs -fGatus should now be accessible locally at http://localhost:8080. If you don't need a public URL, you can stop here and access the dashboard over SSH port-forwarding.
Configure the Firewall
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw statusInstall Nginx and Configure a Reverse Proxy
sudo apt install -y nginxCreate a server block. Replace status.example.com with your actual domain:
server {
listen 80;
server_name status.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
}sudo ln -s /etc/nginx/sites-available/gatus /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxEnable HTTPS with Let's Encrypt
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d status.example.comCertbot will automatically update your Nginx config with SSL directives and set up a cron job for auto-renewal. Verify renewal works:
sudo certbot renew --dry-runProtect the Dashboard (Optional)
If your status page is public-facing but you want to prevent unauthorized access, Gatus supports basic authentication with bcrypt-hashed passwords.
sudo apt install -y apache2-utils
# Generate a bcrypt hash (cost factor 10)
htpasswd -bnBC 10 '' yourpassword | tr -d ':\n' | base64Paste the output into the security block in your config.yaml:
security:
basic:
username: admin
password-bcrypt-base64: "JDJ5JDEwJHh4eHh4..." # paste your hash heredocker compose restartVerify Uptime History and Persistence
The SQLite configuration from Step 3 preserves endpoint history across container restarts. Verify it's working:
# Confirm the database file exists after a few minutes
ls -lh ~/gatus/data/
# Restart and confirm history is preserved
docker compose restartTest Alerting
Add a temporary endpoint that will intentionally fail to verify your alerting integration:
- name: Intentional Failure Test
group: Testing
url: "https://httpstat.us/500"
interval: 30s
conditions:
- "[STATUS] == 200"
alerts:
- type: slack
failure-threshold: 1
description: "This is an intentional test alert"Within 30–60 seconds you should receive an alert on Slack (or whichever provider you configured). Remove the test endpoint once verified.
Useful Management Commands
| Task | Command |
|---|---|
| View logs | docker compose logs -f |
| Restart | docker compose restart |
| Stop | docker compose down |
| Pull latest image | docker compose pull && docker compose up -d |
| Reload config (hot) | docker compose kill -s SIGHUP gatus |
| Backup database | cp ~/gatus/data/data.db ~/gatus-backup-$(date +%F).db |
| Check disk usage | du -sh ~/gatus/data/ |
Troubleshooting
Container exits immediately after starting
This almost always means a YAML syntax error in config.yaml. Run docker compose logs gatus to see the exact error. YAML is whitespace-sensitive — ensure you are using spaces, not tabs.
Dashboard is not accessible over HTTPS
- Confirm the Nginx config is symlinked in
sites-enabled sudo nginx -tshould return no errors- Confirm ports 80 and 443 are open in
ufwand in the RamNode control panel firewall - Check that the domain DNS A record points to your VPS IP
Alerts not being delivered
- Verify the webhook URL or SMTP credentials in
config.yaml - Check container logs for alert delivery errors
- Ensure
failure-thresholdis set to a low value (1 or 2) when testing
High disk usage over time
Gatus retains all historical check data in SQLite. For long-running instances with many endpoints and short intervals, the data.db file can grow. Monitor the size with du -sh ~/gatus/data/ and back it up before it grows too large.
Upgrading Gatus
cd ~/gatus
docker compose pull
docker compose up -d
# Verify the new version is running
docker compose logs --tail=20