Configure advanced Uptime Kuma monitoring with API integrations and automated alerting

Intermediate 45 min May 09, 2026 51 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up Uptime Kuma with advanced API endpoints, custom notification channels, and automated monitoring workflows. Configure health check automation, status page customization, and enterprise alerting rules for production environments.

Prerequisites

  • Running Uptime Kuma instance
  • Admin access to monitoring system
  • Basic understanding of REST APIs
  • Python 3.8 or higher

What this solves

Uptime Kuma's web interface works well for basic monitoring, but production environments need programmatic control, custom integrations, and advanced alerting workflows. This tutorial shows you how to configure Uptime Kuma's API, set up automated monitor management, integrate with external services, and build sophisticated alerting chains that scale with your infrastructure.

Prerequisites

You need a running Uptime Kuma instance with admin access. This tutorial builds on basic monitoring concepts and requires familiarity with REST APIs and webhook integrations. If you need to set up basic monitoring first, check out our Uptime Kuma installation guide.

Step-by-step configuration

Enable API access and authentication

First, enable API access in Uptime Kuma's settings. The API requires authentication tokens for security.

curl -X POST http://localhost:3001/api/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "your_password"}'

Save the returned token. Create a dedicated API user for automation scripts:

curl -X POST http://localhost:3001/api/user \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "username": "api-user",
    "password": "secure_password_123",
    "active": true,
    "role": "user"
  }'

Configure API token management

Generate long-lived API tokens for automation. Store these securely and rotate them regularly.

{
  "api_base_url": "http://localhost:3001/api",
  "auth_token": "your_api_token_here",
  "timeout": 30,
  "retry_attempts": 3,
  "rate_limit": {
    "requests_per_minute": 60,
    "burst_limit": 10
  }
}

Set proper file permissions to protect the API configuration:

sudo chown root:uptime-kuma /etc/uptime-kuma/api-config.json
sudo chmod 640 /etc/uptime-kuma/api-config.json

Create automated monitor management scripts

Build scripts to programmatically create, update, and delete monitors. This enables infrastructure-as-code workflows.

#!/usr/bin/env python3
import requests
import json
import sys
import os
from datetime import datetime

class UptimeKumaAPI:
    def __init__(self, config_file='/etc/uptime-kuma/api-config.json'):
        with open(config_file) as f:
            self.config = json.load(f)
        self.base_url = self.config['api_base_url']
        self.headers = {
            'Authorization': f"Bearer {self.config['auth_token']}",
            'Content-Type': 'application/json'
        }
    
    def create_monitor(self, monitor_config):
        """Create a new monitor with advanced configuration"""
        response = requests.post(
            f"{self.base_url}/monitor",
            headers=self.headers,
            json=monitor_config,
            timeout=self.config['timeout']
        )
        response.raise_for_status()
        return response.json()
    
    def bulk_create_monitors(self, monitors_file):
        """Create multiple monitors from configuration file"""
        with open(monitors_file) as f:
            monitors = json.load(f)
        
        results = []
        for monitor in monitors:
            try:
                result = self.create_monitor(monitor)
                results.append({'status': 'success', 'monitor': result})
                print(f"Created monitor: {monitor.get('name', 'unnamed')}")
            except Exception as e:
                results.append({
                    'status': 'error', 
                    'monitor': monitor.get('name', 'unnamed'),
                    'error': str(e)
                })
                print(f"Failed to create {monitor.get('name')}: {e}")
        
        return results

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: uk-monitor-manager.py  ")
        sys.exit(1)
    
    api = UptimeKumaAPI()
    action, config_file = sys.argv[1], sys.argv[2]
    
    if action == 'create-bulk':
        results = api.bulk_create_monitors(config_file)
        print(f"Processed {len(results)} monitors")
    else:
        print(f"Unknown action: {action}")
        sys.exit(1)

Make the script executable and secure:

sudo chmod 750 /usr/local/bin/uk-monitor-manager.py
sudo chown root:uptime-kuma /usr/local/bin/uk-monitor-manager.py

Set up advanced notification integrations

Configure custom webhook notifications that integrate with your existing alerting infrastructure.

{
  "slack_critical": {
    "type": "slack",
    "webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
    "username": "Uptime Kuma",
    "channel": "#alerts-critical",
    "icon_emoji": ":rotating_light:",
    "color": "danger",
    "conditions": {
      "status": ["down"],
      "tags": ["critical", "production"]
    }
  },
  "pagerduty_escalation": {
    "type": "webhook",
    "url": "https://events.pagerduty.com/v2/enqueue",
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Token token=YOUR_PAGERDUTY_TOKEN"
    },
    "body_template": {
      "routing_key": "YOUR_INTEGRATION_KEY",
      "event_action": "trigger",
      "dedup_key": "uptime-kuma-{monitor_id}",
      "payload": {
        "summary": "Monitor {monitor_name} is {status}",
        "source": "uptime-kuma",
        "severity": "critical",
        "component": "infrastructure",
        "group": "monitoring",
        "custom_details": {
          "monitor_id": "{monitor_id}",
          "monitor_url": "{monitor_url}",
          "response_time": "{response_time}",
          "status_code": "{status_code}",
          "timestamp": "{timestamp}"
        }
      }
    },
    "conditions": {
      "consecutive_failures": 3,
      "tags": ["production"]
    }
  }
}

Create advanced monitor configurations

Define comprehensive monitor templates for different service types with proper health check logic.

[
  {
    "name": "Web Service - Production",
    "type": "http",
    "url": "https://api.example.com/health",
    "method": "GET",
    "interval": 60,
    "timeout": 30,
    "maxretries": 3,
    "headers": {
      "User-Agent": "Uptime-Kuma/1.0",
      "Accept": "application/json"
    },
    "body": "",
    "httpBodyEncoding": "json",
    "expectedStatusCodes": ["200", "201"],
    "keyword": "healthy",
    "invertKeyword": false,
    "ignoreTls": false,
    "tags": ["production", "api", "critical"],
    "notification_ids": [1, 2, 3],
    "advanced_settings": {
      "follow_redirect": true,
      "max_redirects": 5,
      "check_certificate": true,
      "certificate_expiry_warning": 30
    }
  },
  {
    "name": "Database Connection",
    "type": "postgres",
    "hostname": "db.example.com",
    "port": 5432,
    "database": "production",
    "username": "monitor_user",
    "password": "secure_password",
    "interval": 120,
    "timeout": 15,
    "maxretries": 2,
    "query": "SELECT 1 as health_check",
    "tags": ["production", "database", "critical"],
    "notification_ids": [1, 3]
  },
  {
    "name": "TCP Service Check",
    "type": "port",
    "hostname": "service.example.com",
    "port": 22,
    "interval": 300,
    "timeout": 10,
    "maxretries": 3,
    "tags": ["infrastructure", "ssh"],
    "notification_ids": [2]
  }
]

Implement automated health check workflows

Create scripts that automatically adjust monitoring based on service discovery and infrastructure changes.

#!/bin/bash

Uptime Kuma Auto-Discovery Script

Automatically creates monitors for discovered services

set -euo pipefail API_CONFIG="/etc/uptime-kuma/api-config.json" DISCOVERY_CONFIG="/etc/uptime-kuma/discovery-config.json" LOG_FILE="/var/log/uptime-kuma/auto-discovery.log" TEMP_DIR="/tmp/uk-discovery"

Create required directories

sudo mkdir -p /var/log/uptime-kuma "$TEMP_DIR" log_message() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | sudo tee -a "$LOG_FILE" } discover_docker_services() { log_message "Discovering Docker services..." # Get running containers with health checks docker ps --format "table {{.Names}}\t{{.Ports}}\t{{.Status}}" | \ grep -E "(healthy|Up)" | \ tail -n +2 | \ while IFS=$'\t' read -r name ports status; do if [[ "$ports" =~ ([0-9]+):([0-9]+) ]]; then host_port="${BASH_REMATCH[1]}" container_port="${BASH_REMATCH[2]}" cat > "$TEMP_DIR/docker-${name}.json" << EOF { "name": "Docker - ${name}", "type": "http", "url": "http://localhost:${host_port}/health", "interval": 120, "timeout": 15, "tags": ["docker", "auto-discovered", "${name}"], "auto_managed": true } EOF log_message "Created monitor config for Docker service: $name" fi done } discover_systemd_services() { log_message "Discovering systemd services..." # Get critical systemd services systemctl list-units --type=service --state=active | \ grep -E "(nginx|apache2|postgresql|mysql|redis|mongodb)" | \ awk '{print $1}' | \ while read -r service; do service_name=$(echo "$service" | sed 's/\.service$//') cat > "$TEMP_DIR/systemd-${service_name}.json" << EOF { "name": "SystemD - ${service_name}", "type": "http", "url": "http://localhost/server-status", "interval": 180, "timeout": 10, "tags": ["systemd", "auto-discovered", "${service_name}"], "auto_managed": true } EOF log_message "Created monitor config for systemd service: $service_name" done } create_discovered_monitors() { log_message "Creating monitors from discovered services..." # Combine all discovered monitors into a single file echo "[" > "$TEMP_DIR/all-monitors.json" first=true for config_file in "$TEMP_DIR"/*.json; do if [[ "$config_file" != "$TEMP_DIR/all-monitors.json" ]]; then if [[ "$first" == "false" ]]; then echo "," >> "$TEMP_DIR/all-monitors.json" fi cat "$config_file" >> "$TEMP_DIR/all-monitors.json" first=false fi done echo "]" >> "$TEMP_DIR/all-monitors.json" # Create monitors using our Python script if [[ -s "$TEMP_DIR/all-monitors.json" ]]; then /usr/local/bin/uk-monitor-manager.py create-bulk "$TEMP_DIR/all-monitors.json" log_message "Completed monitor creation from auto-discovery" else log_message "No new services discovered" fi } cleanup() { rm -rf "$TEMP_DIR" } trap cleanup EXIT

Main discovery workflow

log_message "Starting auto-discovery process" discover_docker_services discover_systemd_services create_discovered_monitors log_message "Auto-discovery process completed"

Make the script executable and set up automated execution:

sudo chmod 755 /usr/local/bin/uk-auto-discovery.sh

Create systemd timer for automated discovery

sudo tee /etc/systemd/system/uk-auto-discovery.timer << 'EOF' [Unit] Description=Uptime Kuma Auto-Discovery Timer Requires=uk-auto-discovery.service [Timer] OnCalendar=*:0/15 Persistent=true [Install] WantedBy=timers.target EOF sudo tee /etc/systemd/system/uk-auto-discovery.service << 'EOF' [Unit] Description=Uptime Kuma Auto-Discovery Service After=network.target uptime-kuma.service [Service] Type=oneshot User=root ExecStart=/usr/local/bin/uk-auto-discovery.sh TimeoutSec=300 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now uk-auto-discovery.timer

Configure status page automation

Set up programmatic status page management with custom styling and maintenance scheduling.

#!/usr/bin/env python3
import requests
import json
import sys
from datetime import datetime, timedelta

class StatusPageManager:
    def __init__(self, config_file='/etc/uptime-kuma/api-config.json'):
        with open(config_file) as f:
            self.config = json.load(f)
        self.base_url = self.config['api_base_url']
        self.headers = {
            'Authorization': f"Bearer {self.config['auth_token']}",
            'Content-Type': 'application/json'
        }
    
    def create_status_page(self, config):
        """Create a public status page with custom configuration"""
        status_page_config = {
            'title': config.get('title', 'Service Status'),
            'description': config.get('description', 'Real-time status of our services'),
            'theme': config.get('theme', 'auto'),
            'published': config.get('published', True),
            'show_tags': config.get('show_tags', True),
            'domainNames': config.get('domains', []),
            'customCSS': config.get('custom_css', ''),
            'footerText': config.get('footer', 'Powered by Uptime Kuma'),
            'showPoweredBy': config.get('show_powered_by', False)
        }
        
        response = requests.post(
            f"{self.base_url}/status-page",
            headers=self.headers,
            json=status_page_config
        )
        response.raise_for_status()
        return response.json()
    
    def schedule_maintenance(self, maintenance_config):
        """Schedule maintenance windows with automatic notifications"""
        maintenance = {
            'title': maintenance_config['title'],
            'description': maintenance_config['description'],
            'strategy': maintenance_config.get('strategy', 'single'),
            'active': True,
            'intervalDay': maintenance_config.get('interval_days', 0),
            'dateTime': maintenance_config['start_time'],
            'dateTimeEnd': maintenance_config['end_time'],
            'monitors': maintenance_config.get('monitor_ids', []),
            'statusPages': maintenance_config.get('status_page_ids', [])
        }
        
        response = requests.post(
            f"{self.base_url}/maintenance",
            headers=self.headers,
            json=maintenance
        )
        response.raise_for_status()
        return response.json()
    
    def update_incident_status(self, incident_data):
        """Update incident information on status pages"""
        incident = {
            'title': incident_data['title'],
            'content': incident_data['description'],
            'style': incident_data.get('style', 'warning'),
            'created_date': datetime.utcnow().isoformat(),
            'last_updated_date': datetime.utcnow().isoformat()
        }
        
        response = requests.post(
            f"{self.base_url}/incident",
            headers=self.headers,
            json=incident
        )
        response.raise_for_status()
        return response.json()

if __name__ == '__main__':
    manager = StatusPageManager()
    
    # Example: Create status page
    if len(sys.argv) > 1 and sys.argv[1] == 'create-page':
        config = {
            'title': 'Example.com Service Status',
            'description': 'Real-time status of example.com services',
            'theme': 'dark',
            'domains': ['status.example.com'],
            'custom_css': '.header { background: #2563eb; }'
        }
        result = manager.create_status_page(config)
        print(f"Created status page: {result['slug']}")
    
    # Example: Schedule maintenance
    elif len(sys.argv) > 1 and sys.argv[1] == 'schedule-maintenance':
        config = {
            'title': 'Database Migration',
            'description': 'Upgrading database servers for improved performance',
            'start_time': (datetime.now() + timedelta(days=1)).isoformat(),
            'end_time': (datetime.now() + timedelta(days=1, hours=2)).isoformat(),
            'monitor_ids': [1, 2, 3]
        }
        result = manager.schedule_maintenance(config)
        print(f"Scheduled maintenance: {result['id']}")

Make the status manager executable:

sudo chmod 755 /usr/local/bin/uk-status-manager.py

Set up advanced alerting rules

Configure complex alerting workflows with escalation chains and conditional logic.

{
  "rules": [
    {
      "name": "Critical Service Down",
      "conditions": {
        "monitor_tags": ["critical", "production"],
        "status": "down",
        "consecutive_failures": 2,
        "time_of_day": {
          "business_hours": true,
          "timezone": "UTC"
        }
      },
      "actions": [
        {
          "type": "notification",
          "notification_id": 1,
          "delay_seconds": 0
        },
        {
          "type": "pagerduty",
          "integration_key": "YOUR_PAGERDUTY_KEY",
          "severity": "critical",
          "delay_seconds": 300
        },
        {
          "type": "webhook",
          "url": "https://api.example.com/incidents",
          "method": "POST",
          "delay_seconds": 600
        }
      ]
    },
    {
      "name": "Certificate Expiry Warning",
      "conditions": {
        "monitor_type": "http",
        "certificate_days_remaining": 30,
        "tags": ["production"]
      },
      "actions": [
        {
          "type": "email",
          "recipients": ["ops@example.com"],
          "template": "certificate_expiry"
        },
        {
          "type": "slack",
          "channel": "#infrastructure",
          "severity": "warning"
        }
      ]
    },
    {
      "name": "Performance Degradation",
      "conditions": {
        "response_time_threshold": 5000,
        "response_time_samples": 5,
        "tags": ["api", "production"]
      },
      "actions": [
        {
          "type": "datadog_metric",
          "metric_name": "uptime.response_time.degraded",
          "tags": ["service:api", "env:production"]
        },
        {
          "type": "notification",
          "notification_id": 2
        }
      ]
    }
  ],
  "escalation_policies": [
    {
      "name": "Critical Escalation",
      "levels": [
        {
          "delay_minutes": 0,
          "targets": ["on-call-primary"]
        },
        {
          "delay_minutes": 15,
          "targets": ["on-call-secondary", "team-lead"]
        },
        {
          "delay_minutes": 30,
          "targets": ["engineering-manager"]
        }
      ]
    }
  ]
}

Create monitoring dashboard integration

Build custom dashboards that combine Uptime Kuma data with other monitoring systems.

#!/usr/bin/env python3
import requests
import json
import time
from datetime import datetime, timedelta
from prometheus_client import start_http_server, Gauge, Counter

class UptimeKumaDashboard:
    def __init__(self, config_file='/etc/uptime-kuma/api-config.json'):
        with open(config_file) as f:
            self.config = json.load(f)
        self.base_url = self.config['api_base_url']
        self.headers = {
            'Authorization': f"Bearer {self.config['auth_token']}",
            'Content-Type': 'application/json'
        }
        
        # Prometheus metrics
        self.monitor_up = Gauge('uptime_kuma_monitor_up', 'Monitor status', ['monitor_name', 'monitor_id', 'tags'])
        self.monitor_response_time = Gauge('uptime_kuma_response_time_ms', 'Response time in milliseconds', ['monitor_name', 'monitor_id'])
        self.monitor_uptime = Gauge('uptime_kuma_uptime_percent', 'Monitor uptime percentage', ['monitor_name', 'monitor_id', 'period'])
        self.alert_count = Counter('uptime_kuma_alerts_total', 'Total alerts sent', ['monitor_name', 'alert_type', 'severity'])
    
    def get_monitor_stats(self):
        """Fetch comprehensive monitor statistics"""
        response = requests.get(
            f"{self.base_url}/monitors",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()
    
    def update_prometheus_metrics(self):
        """Update Prometheus metrics with current monitor data"""
        monitors = self.get_monitor_stats()
        
        for monitor in monitors:
            monitor_name = monitor.get('name', 'unknown')
            monitor_id = str(monitor.get('id', 0))
            tags = ','.join(monitor.get('tags', []))
            
            # Update status metric
            status_value = 1 if monitor.get('status') == 'up' else 0
            self.monitor_up.labels(
                monitor_name=monitor_name,
                monitor_id=monitor_id,
                tags=tags
            ).set(status_value)
            
            # Update response time
            if 'avgResponseTime' in monitor:
                self.monitor_response_time.labels(
                    monitor_name=monitor_name,
                    monitor_id=monitor_id
                ).set(monitor['avgResponseTime'])
            
            # Update uptime percentages
            for period in ['24h', '7d', '30d']:
                uptime_key = f'uptime_{period}'
                if uptime_key in monitor:
                    self.monitor_uptime.labels(
                        monitor_name=monitor_name,
                        monitor_id=monitor_id,
                        period=period
                    ).set(monitor[uptime_key])
    
    def generate_status_report(self, output_file='/var/log/uptime-kuma/status-report.json'):
        """Generate comprehensive status report"""
        monitors = self.get_monitor_stats()
        
        report = {
            'generated_at': datetime.utcnow().isoformat(),
            'summary': {
                'total_monitors': len(monitors),
                'monitors_up': sum(1 for m in monitors if m.get('status') == 'up'),
                'monitors_down': sum(1 for m in monitors if m.get('status') == 'down'),
                'average_uptime_24h': sum(m.get('uptime_24h', 0) for m in monitors) / len(monitors) if monitors else 0
            },
            'by_tag': {},
            'by_type': {},
            'incidents_last_24h': self.get_recent_incidents()
        }
        
        # Group by tags
        for monitor in monitors:
            for tag in monitor.get('tags', ['untagged']):
                if tag not in report['by_tag']:
                    report['by_tag'][tag] = {'total': 0, 'up': 0, 'down': 0}
                
                report['by_tag'][tag]['total'] += 1
                if monitor.get('status') == 'up':
                    report['by_tag'][tag]['up'] += 1
                else:
                    report['by_tag'][tag]['down'] += 1
        
        # Group by monitor type
        for monitor in monitors:
            monitor_type = monitor.get('type', 'unknown')
            if monitor_type not in report['by_type']:
                report['by_type'][monitor_type] = {'total': 0, 'up': 0, 'down': 0}
            
            report['by_type'][monitor_type]['total'] += 1
            if monitor.get('status') == 'up':
                report['by_type'][monitor_type]['up'] += 1
            else:
                report['by_type'][monitor_type]['down'] += 1
        
        with open(output_file, 'w') as f:
            json.dump(report, f, indent=2)
        
        return report
    
    def get_recent_incidents(self, hours=24):
        """Get incidents from the last N hours"""
        since = (datetime.utcnow() - timedelta(hours=hours)).isoformat()
        
        try:
            response = requests.get(
                f"{self.base_url}/incidents",
                headers=self.headers,
                params={'since': since}
            )
            response.raise_for_status()
            return response.json()
        except Exception as e:
            print(f"Failed to fetch incidents: {e}")
            return []

def main():
    dashboard = UptimeKumaDashboard()
    
    # Start Prometheus metrics server
    start_http_server(8000)
    print("Prometheus metrics server started on port 8000")
    
    while True:
        try:
            dashboard.update_prometheus_metrics()
            dashboard.generate_status_report()
            print(f"Updated metrics at {datetime.now().isoformat()}")
        except Exception as e:
            print(f"Error updating metrics: {e}")
        
        time.sleep(60)  # Update every minute

if __name__ == '__main__':
    main()

Install required Python packages and create service:

sudo apt install -y python3-pip
sudo pip3 install requests prometheus_client
sudo dnf install -y python3-pip
sudo pip3 install requests prometheus_client
sudo chmod 755 /usr/local/bin/uk-dashboard-exporter.py

Create systemd service for the dashboard exporter

sudo tee /etc/systemd/system/uk-dashboard.service << 'EOF' [Unit] Description=Uptime Kuma Dashboard Exporter After=network.target uptime-kuma.service Requires=uptime-kuma.service [Service] Type=simple User=uptime-kuma Group=uptime-kuma ExecStart=/usr/local/bin/uk-dashboard-exporter.py Restart=always RestartSec=10 TimeoutStopSec=5 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable --now uk-dashboard.service

Verify your setup

Test your advanced Uptime Kuma configuration with these verification commands:

# Test API connectivity
curl -H "Authorization: Bearer YOUR_TOKEN" \
     http://localhost:3001/api/monitors

Verify auto-discovery service

sudo systemctl status uk-auto-discovery.timer sudo journalctl -u uk-auto-discovery.service -n 20

Check dashboard exporter

curl http://localhost:8000/metrics | grep uptime_kuma

Test notification integrations

python3 -c " import requests import json with open('/etc/uptime-kuma/api-config.json') as f: config = json.load(f) print('API config loaded successfully') "

Verify scheduled tasks

sudo systemctl list-timers | grep uk-auto-discovery

Check status report generation

sudo ls -la /var/log/uptime-kuma/status-report.json
Integration tip: Your Uptime Kuma API is now accessible at http://localhost:3001/api. The Prometheus metrics are available at http://localhost:8000/metrics for integration with existing monitoring stacks like our Prometheus and Grafana setup.

Common issues

SymptomCauseFix
API returns 401 Unauthorized Invalid or expired token Regenerate API token in Uptime Kuma settings and update config files
Auto-discovery script fails Missing Docker or systemctl permissions Add uptime-kuma user to docker group: sudo usermod -a -G docker uptime-kuma
Prometheus metrics not updating Dashboard exporter service crashed Check service logs: sudo journalctl -u uk-dashboard.service -f
Notification webhooks failing Network connectivity or auth issues Test webhook URLs manually with curl and verify credentials
Status page not accessible Domain configuration or proxy issues Verify domain DNS and reverse proxy configuration
High API rate limit errors Too many concurrent requests Increase rate limits in api-config.json or add request throttling

Next steps

Running this in production?

Want this handled for you? Setting up advanced monitoring once is straightforward. Keeping it patched, scaled, and integrated with your broader infrastructure is the harder part. See how we run monitoring infrastructure like this for European SaaS and e-commerce teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.