Vigil Product tour Buy Vigil

Migration

Import monitors from Uptime Kuma.

Settings, Import, and a copy of kuma.db. The preview is a real import that Postgres rolls back. This page is the Kuma path. The other sources are in the migration docs.

The Kuma file import is in Core. The fourteen API importers are Commercial. Nothing here deletes the Kuma database. Vigil only reads the copy you give it.

Which Kuma this understands

The importer is pinned to Kuma 2.4.0, commit 9f3b837c8c7f359ec1acee80b3c0430451986a03, published 31 May 2026. The schema it was written against is database_version = 10, with 111 columns on monitor. The constants live in src/modules/importers/kuma/pinned.ts and are checked against a database file Kuma itself wrote.

That pin is not the current stable release. The current stable release is 2.5.0. A newer Kuma that changes the schema version, the column count or the type list fails the test rather than importing quietly wrong. If your file is from a release the pin does not name, read the report before you trust it, and do not assume every new column came across.

Steps

  1. Copy kuma.db. If Kuma is running, stop it first, or take the copy with SQLite's own backup, so the file is not half written.
  2. In Vigil, open Settings, then Import, and choose the file. Importing requires the owner or admin role.
  3. Read the preview. It is not an estimate. Vigil builds every monitor, validates it, inserts it, lets Postgres check the constraints, and rolls the transaction back. The summary is that run.
  4. Confirm. The file is hashed on the way in and the hash is checked on confirm, so the second click applies the file you were shown. Vigil reads the source again rather than storing your configuration between the two clicks.
  5. Keep the report. Every skipped monitor lists the reasons, not only the first one.

What has a Vigil check

Kuma 2.4.0 has 31 monitor types in its selector, 29 excluding the Group and Manual entries. Each of the 31 has a Vigil type. Group becomes a group monitor. Manual becomes a monitor whose status you set. Keyword becomes an HTTP monitor with a body check. Port becomes TCP. The column-by-column table is docs/KUMA-IMPORT.md in the repository. The public summary is the migration doc.

A few restatements matter more than the type names:

  • Kuma's maxretries times retry_interval becomes Vigil's failure window: how long the check may fail before an incident opens. Zero retries becomes a window of 0, which is down on the first failure.
  • A Kuma accepted-status list of 200-299 becomes "any 2xx or 3xx", which is Vigil's default. One exact code is kept. A mixed list is reported and not approximated.
  • JSONPath is rewritten as a dotted path. $.status.state becomes status.state. Wildcards and filters are refused.
  • HTTP monitors keep the certificate-expiry flag as Vigil's TLS check. Kuma's domain-expiry flag does not create a second monitor. Add a domain-expiry check yourself if you used it. The report names the monitors that had the flag.

Rows that are refused

A matching type is not a promise that the row imports. These are the refusals worth knowing before you start. Each one is named on the report.

  1. A URL with a bare IP is refused. Vigil's target has to be a hostname. That is an SSRF boundary. Give the host a DNS name, or recreate the monitor by hand.
  2. Only GET and HEAD import. A monitor that POSTs is refused rather than turned into a GET, because that would be a different check and a manufactured outage.
  3. A Docker host reached only over a local socket can have no hostname for Vigil's target. A host reached with tcp:// imports. The container name and the daemon address are copied onto the monitor. Vigil has no separate Docker-host registry.
  4. A Kafka monitor that does not use SASL/PLAIN is refused. Importing SCRAM credentials as PLAIN would create a check that can never authenticate.
  5. A GameDig id that does not resolve to Source or GoldSrc, Minecraft, or id Tech 3 is refused, rather than queried with the wrong packet.

On the pinned fixture, one monitor of each type, four of the 31 rows are refused for those reasons: an HTTP monitor and a keyword monitor aimed at a loopback address, a Docker monitor on a unix socket with no hostname, and a Kafka monitor on SCRAM-SHA-256. Your file will not have those names. It will hit the same rules.

What does not come across

Kuma record What Vigil does
Notification providers and their links Not read. Recreate the channel under Settings, then Notifications. No provider credential is copied out of the file.
Tags Not carried. Vigil has no monitor tags. A group is the nearest thing, and it is not the same thing.
Maintenance windows Not carried. Commercial has windows of its own. Core has none. Until you declare them again, imported monitors alert during a window Kuma would have suppressed.
Heartbeats and uptime statistics Not carried, except the last push beat on an imported push monitor. Vigil's uptime is computed from checks it made itself.
Status page password Not carried. The page is imported private. Kuma's hash cannot be turned into Vigil's, and a password-protected page must not become public by accident.
Users, API keys, incidents Not carried. They are Kuma's own accounts and history.
Proxies Not carried. Vigil probes the target directly.

After the import

  • Paused state is carried. A monitor you had disabled in Kuma arrives paused.
  • If a push token was regenerated, the report says so. Point the job at /api/push/<token> before you judge that monitor.
  • A Docker monitor imported over tcp:// still needs the Vigil worker to be able to reach that daemon. A local socket row that was refused has to be recreated with a hostname target and a socket path.
  • Recreate notification channels and, if you use them, maintenance windows, before you rely on paging.
  • Let a few checks run and read the results before you enable recovery or an escalation policy. Both of those are Commercial and off until you turn them on.

Vigil and Uptime Kuma is the comparison, including when to stay on Kuma. Restart on failure is the page for the webhook you may have been using as a restart button.