Bulk update all your Uptime Kuma monitors with a single script

Uptime Kuma has no bulk edit button. Clicking through dozens of monitors one by one is nobody's idea of a good time. This script updates the heartbeat interval, retries and retry interval across all your monitors in one shot.

If you are running Uptime Kuma with more than a handful of monitors, you have probably hit that wall. You want to change the heartbeat interval across the board and there is no bulk edit button in sight. Clicking through 30 monitors one by one is not a workflow, it is a punishment.

So I wrote a small Python script that does it in one shot.

What it does

The script connects to your Uptime Kuma instance via its internal Socket.IO API, pulls every monitor, and updates three values across all of them:

  • Heartbeat interval → 120 seconds
  • Max retries → 2
  • Retry interval → 60 seconds

Monitors that are already set correctly get a clean Already up to date message and are left alone. Everything is configured through a .env file so nothing sensitive ends up hardcoded.

There is one gotcha worth mentioning: the Python wrapper used to talk to Uptime Kuma's API throws an error when a monitor has a null dns_resolve_type, which happens on non-DNS monitors. The script patches that validation silently before it runs, so you do not have to touch your monitor setup at all.

The .env file

Create a .env file in the same folder as the script:

UPTIME_KUMA_URL=http://10.10.10.10:3001
UPTIME_KUMA_USERNAME=admin
UPTIME_KUMA_PASSWORD=your_password_here

NEW_INTERVAL=120
NEW_MAX_RETRIES=2
NEW_RETRY_INTERVAL=60

Change the values to match your setup. If you leave UPTIME_KUMA_PASSWORD empty, the script will prompt you for it at runtime instead.

The script

Save this as bulk_update_kuma.py:

#!/usr/bin/env python3

# --- Patch BEFORE importing UptimeKumaApi ---
import uptime_kuma_api.api as _api

_orig_check = _api._check_arguments_monitor
def _patched_check(kwargs):
    if kwargs.get("dns_resolve_type") is None:
        kwargs["dns_resolve_type"] = "A"
    _orig_check(kwargs)
_api._check_arguments_monitor = _patched_check

_orig_convert = _api._convert_monitor_input
def _patched_convert(data):
    if data.get("dns_resolve_type") is None:
        data["dns_resolve_type"] = "A"
    return _orig_convert(data)
_api._convert_monitor_input = _patched_convert
# --- End patch ---

import os
from pathlib import Path
from getpass import getpass
from uptime_kuma_api import UptimeKumaApi

def load_env(filepath=".env"):
    env_path = Path(filepath)
    if env_path.exists():
        with open(env_path) as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#") or "=" not in line:
                    continue
                key, _, value = line.partition("=")
                os.environ.setdefault(key.strip(), value.strip())

load_env()

UPTIME_KUMA_URL    = os.environ.get("UPTIME_KUMA_URL", "").strip()
USERNAME           = os.environ.get("UPTIME_KUMA_USERNAME", "").strip()
PASSWORD           = os.environ.get("UPTIME_KUMA_PASSWORD", "").strip()
NEW_INTERVAL       = int(os.environ.get("NEW_INTERVAL", 120))
NEW_MAX_RETRIES    = int(os.environ.get("NEW_MAX_RETRIES", 2))
NEW_RETRY_INTERVAL = int(os.environ.get("NEW_RETRY_INTERVAL", 60))

if not UPTIME_KUMA_URL:
    UPTIME_KUMA_URL = input("Uptime Kuma URL: ").strip()
if not USERNAME:
    USERNAME = input("Uptime Kuma username: ").strip()
if not PASSWORD:
    PASSWORD = getpass("Uptime Kuma password: ")

def main():
    updated = 0
    already = 0
    failed  = 0

    with UptimeKumaApi(UPTIME_KUMA_URL) as api:
        api.login(USERNAME, PASSWORD)
        monitors = api.get_monitors()

        for monitor in monitors:
            monitor_id   = monitor["id"]
            name         = monitor.get("name", f"monitor-{monitor_id}")
            monitor_type = monitor.get("type", "unknown")

            if (
                monitor.get("interval")      == NEW_INTERVAL
                and monitor.get("maxretries")    == NEW_MAX_RETRIES
                and monitor.get("retryInterval") == NEW_RETRY_INTERVAL
            ):
                already += 1
                print(f"ALREADY UP TO DATE  {monitor_id:>4}  {name}")
                continue

            try:
                api.edit_monitor(
                    monitor_id,
                    interval=NEW_INTERVAL,
                    maxretries=NEW_MAX_RETRIES,
                    retryInterval=NEW_RETRY_INTERVAL,
                )
                updated += 1
                print(f"UPDATE  {monitor_id:>4}  {name}  type={monitor_type}")
            except Exception as e:
                failed += 1
                print(f"ERROR   {monitor_id:>4}  {name}  {e}")

    print()
    print(f"Done. Updated: {updated}, Already up to date: {already}, Failed: {failed}")

if __name__ == "__main__":
    main()

How to run it

You do not need Python installed on your machine. Run it in a throwaway Docker container. It installs the dependency inside the container, runs the script, and disappears cleanly when done:

docker run --rm -it \
  -v "$PWD:/work" \
  -w /work \
  python:3.12-slim \
  sh -c "pip install uptime-kuma-api && python3 bulk_update_kuma.py"

Make sure your .env and bulk_update_kuma.py are in the same directory when you run that command. You will see a live log of every monitor being updated or skipped, and a summary at the end.

Note: Uptime Kuma's API is an unofficial internal Socket.IO interface. This script was written and tested against Uptime Kuma v1.x. If something breaks after an upgrade, the wrapper version (uptime-kuma-api on PyPI) is usually the first thing to check.
Looking for help?
If you are looking for some help or want a quick chat, please head over to the Discord Community!