Skip to content
Open app

Check types

CuliPulse has eight monitor types. This page covers what each one checks and its settings. Which ones run from a checkpoint is noted per type.

Requests a URL and decides health from the response. Runs from a checkpoint.

  • URL and method (GET by default): GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS — in the console, the API and Terraform alike.
  • Expected status — a single code (200), a class (2xx), or a list (200, 301). Any other reply counts as down. If that reply is a success or redirect (below 400), the incident and the alert say what you expected, for example HTTP 200 — expected 3xx. An error reply such as HTTP 503 (service unavailable) is shown as it is, unless you expected an error code yourself.
  • Timeout — how long to wait before the request counts as failed.
  • Request headers, and a request body with a content type. The body field appears for every method except GET and HEAD (and on any monitor that already has a body), so you can send a body with POST, PUT, PATCH, DELETE or OPTIONS.
  • Secret fields — headers, a body, or bearer/basic auth can be marked secret; these are encrypted at rest and never shown back to you. When the URL redirects, secrets are only sent to the same site (or one of its subdomains, such as www.). A redirect to another site gets the request without them, and a check with secrets won’t follow a redirect from https:// to plain http:// — it fails with “Redirected to an unencrypted http:// address”. Secrets are sent only by shared locations: an agent you run yourself never receives them, so its checks run without them. CuliPulse warns you when you save that combination, on every save while it stands. See Your own agent.
  • Size limits — up to 50 headers (plain and secret together), each header name up to 256 characters and each value up to 8,192 characters, a request body up to 64 KB, the whole check configuration up to 128 KB, and a target (URL or host) up to 2,048 characters. Saving something larger is refused, and the error names the limit that was hit.
  • Assertions — go beyond the status code: check that the response body contains text, that a JSON field at a path has a value, or that a response header matches.
  • Page text (body_match) — an API or Terraform setting that marks the monitor down with “Page missing expected text” when the response doesn’t contain this text (case-sensitive, up to 1,024 characters). The console can’t set it, but it shows it as a read-only Page must contain row on the monitor’s Settings tab and under Advanced on the Edit screen. Saving in the console keeps it. Change or clear it through the API or Terraform. In the console, a body assertion gives you the same check.
  • Pasting a health URL — paste a URL whose path has a /health, /healthz, /ping or /status part (such as https://api.example.com/v1/health) on the New monitor screen and the check only expects the endpoint to answer with a success status. It checks the reply text only if you tick Also check the reply says it’s healthy and type the exact word your endpoint sends (capital letters matter — UP is not ok).
  • Response-time SLA — flag the monitor degraded (not down) when it responds slower than a threshold you set.
  • Certificate expiry — warn (degraded) when the TLS certificate is close to expiring. It runs from a shared location or your own agent.
  • Domain-expiry add-on — optionally warn a set number of days before the URL’s domain registration lapses, without a separate monitor.

Opens a TCP connection to a host:port. Runs from a checkpoint.

  • Target — host:port, and nothing else: no https://, no path, no spaces. Put an IPv6 address in square brackets, like [2001:db8::1]:5432. A target in any other shape is turned down with a message when you save. localhost and private addresses can’t be reached from a shared location, so check them from your own agent. The same holds for HTTP monitors: a private IP address (like 192.168.1.10) is accepted only when every location is one of your own agents.
  • Timeout.
  • Banner check (optional) — send a small payload (up to 1 KB, as text or hex) after connecting, and require the reply to contain a string or match a regular expression. With no banner set, a successful connection is enough to count as up. The check keeps reading the reply (up to 4 KB) until it matches, the server closes the connection, the server goes quiet for 3 seconds, or the timeout passes. A greeting that arrives in several pieces (like a multi-line mail greeting) still counts, as long as the pieces follow each other within 3 seconds. A server that pauses longer between lines is checked against what had arrived by then.
  • Typing a line break — in a text payload, type \r for a carriage return, \n for a line feed and \t for a tab, so PING\r\n sends the word PING and a line break (what Redis expects). \xHH sends the character with that code (\x00 to \x7F), and \\ sends one backslash. For any other byte, set the encoding to hex: 0d0a is a line break. In hex nothing is interpreted. The monitor page shows the payload the same way you typed it. These escapes apply to the payload only. The expected reply is matched as typed: in contains mode \r\n means a backslash and an r, not a line break, and in regex mode it is regular-expression syntax.
  • Multi-line replies — the expected text is matched against the whole reply, line breaks included. In regex mode, ^ and $ match the start and end of the whole reply, not of each line, unless you begin the pattern with (?m).

The alert says which step failed, so you can tell a real outage from a check that needs adjusting:

What the alert says What it means What to try
Couldn’t connect — port closed or refused The server answered that nothing is listening on that port Check the service is running and the port is right. Agent running in Docker on the same machine? See Checking services on the same machine.
Couldn’t connect — no answer before the timeout Nothing came back, often a firewall, a wrong address, or a server that’s down Check the address, that the server is up, and that the firewall allows CuliPulse’s addresses. For a private server, run your own agent
Domain not found The name doesn’t exist in public DNS (expired, mistyped, or a private name) Check the spelling. For a private host, run your own agent
Connected, but the server sent nothing The port is open but the server never sent the greeting you asked for Check that your expected text is something the server sends on its own, or add a payload that makes it reply
Connected, but the reply didn’t match — expected “…” The service answered, but not with the text you set. It is reachable; it may be unhealthy, or your expected text may be off Compare the expected text with what the service really sends
No reply to our test message A UDP server stayed silent. UDP has no “connected” step, so silence reads as down If the server is running, it may expect a different payload. Many UDP servers (games, VoIP) ignore an empty message
The server replied, but the reply didn’t match The UDP server answered, but not with the expected text. For a hex expectation the alert says “didn’t match the expected bytes”. On the DNS-resolver template it says the DNS server answered with an error (refused or server failure) Compare the expected text with what the service really sends. For a DNS resolver, check that it accepts queries from the checking location

Shared locations can only reach servers on the public internet. A private name, such as db.internal or a bare redis, can’t be reached from them: the monitor form warns about it when you pick a shared location.

Sends ICMP echo packets and reports whether the host answers, plus latency and loss. Runs from a checkpoint.

  • Target — a host name or an IPv4 address, like db.example.com or 192.0.2.10 (IPv6 addresses aren’t supported for Ping yet). Ping has no port, so leave off any :80, and leave off http://, any path and any login. A Ping monitor saved before this check keeps working until you change its target.
  • Packet count — 1 to 10 per check.
  • Degraded thresholds (optional) — mark the monitor degraded above a packet-loss percentage or a round-trip-time in milliseconds. Set either to off to ignore it.

Sends a datagram and checks for a reply. Runs from a checkpoint.

  • Target — host:port, in the same shape as for a TCP port (no https://, no path, IPv6 in square brackets).
  • Payload / expect — same shape as the TCP banner check (send a text or hex payload, require the reply to contain or match), and the same \r \n \t \xHH \\ escapes in a text payload. With no expect set, any reply counts as up, and no reply in time is always down. So a service that never answers an unauthenticated packet, WireGuard for one, can’t be checked this way.
  • DNS resolver preset — sends a real lookup for example.com and counts only a normal answer as up. A server that replies refused or server failure shows as down, because it answered but is not resolving.

Looks up a domain’s registration once a day and warns before it expires. Checked by CuliPulse (no checkpoint).

Each check also records the domain’s registrar, shown on the monitor page.

  • Domain — the bare domain (e.g. example.com). Its TLD must be supported by RDAP, or the monitor can’t be created.
  • Warn days — how many days before expiry to start warning (1–3650, default 30).
  • The interval is fixed at once a day regardless of what you pick elsewhere.

Watches DNS records for drift. Checked by CuliPulse (no checkpoint).

  • Hostname — the bare name to look up.
  • Records — up to 20 rules, each with a type (A, AAAA, CNAME, MX, TXT, NS, CAA, SRV), a match mode (exact, contains, or present), and the expected value(s) (required unless the mode is present).
  • Discover records fills the rules in for you from a live lookup. Addresses (A/AAAA) are set to present, because they can change by location (CDNs, geo-DNS). A CNAME checks the first name it points to. TXT starts unticked, since other services often add entries there. Switch any rule to exact if you want every change to alert.
  • Cross-check (optional) — compare answers across resolvers to catch inconsistent results.

Watches a supported third-party service’s official status feed, so their outage shows up as theirs, not yours. Checked by CuliPulse (no checkpoint), polled at most once a minute.

  • Vendor — chosen from a supported list (not an arbitrary URL): GitHub, npm, Cloudflare, Discord, Vercel, Netlify, OpenAI, Anthropic, DigitalOcean, Datadog, Twilio, SendGrid, CircleCI, Zoom, Reddit, Atlassian, and AWS.
  • Components — for most vendors you can narrow to specific components of their status page.
  • AWS — picked by service and region (e.g. “S3 in eu-west-1”); you must choose at least one service/region pair. (AWS almost always has some event somewhere, so a watch-everything option isn’t offered.)

When it alerts

  • Down — the vendor’s status page shows its overall status as a major or critical outage.
  • Degraded — the vendor has posted an incident, or some of its components are having problems. If you picked specific components, an incident or outage on those components counts in full (a major one marks the monitor down).
  • If the vendor’s status page can’t be reached, CuliPulse treats it as neither an outage nor an all-clear. The monitor keeps its current state and CuliPulse tries again a minute later. If the page stays unreadable for three checks in a row, the monitor shows unknown and you’re told it can’t be checked right now. The monitor’s last known status is still shown (for example “Major outage” if the vendor was down), labelled last known with how long ago it was read.
  • If every component you picked no longer exists on the vendor’s status page, the monitor shows unknown and asks you to pick different components in Edit. The monitor will recover once you select valid components and it polls the status page again (usually within a minute).
  • “Alert me after N failed checks” works here too: the problem has to show on N checks in a row before you’re alerted.

A dead-man’s-switch for your own scheduled jobs: nothing is probed; your job calls a URL to check in, and CuliPulse alerts you if a check-in is missed. Setup, schedules, failure reporting and run timing have their own page: Heartbeat monitors.

  • Ping URL: a unique URL your job requests after each run, with the run’s exit code added to the end. You can also signal the start of a run (/start) and a failure (/fail).
  • Grace period: how late a check-in may be before it counts as missed. When you create one in the app, it starts at the larger of 10 minutes or 20% of the interval. Via the API or Terraform, a missing grace_seconds defaults to the larger of 60 seconds or 20% of the interval, at most 30 days. It must be a whole number of seconds from 1 to 2,592,000 (30 days); anything else is refused when you save. The interval can be at most 31,536,000 seconds (365 days).
  • Missed check-ins: a heartbeat has no “alert after N failures” setting. It goes down at the first missed check-in (once the grace period has passed) and comes back up at the next one.
  • Changing the schedule: you can change a running heartbeat’s interval or grace period at any time. The next check-in is then due one full new interval plus grace period after you save, so an edit never marks a healthy job down, and a shorter schedule starts counting right away.
  • Not watching until the first check-in: a new heartbeat shows Pending and isn’t watched until its first check-in arrives. That first check-in, and the first one after you resume a paused heartbeat, turn it up without an alert. Run your job once after setting it up.
  • Max run duration (optional): measured from the /start request to the finishing check-in, so it only applies to runs that send /start. Once the heartbeat is being watched (from its first check-in), a run that is still going when the limit passes marks the monitor degraded at that moment, without waiting for it to finish; the run’s finishing check-in ends it, and /fail marks it down as usual. A run that finishes over the limit without having been flagged while it ran is also marked degraded. See Time your runs.

In crontab, a nightly job at 03:00 that reports its exit code:

Terminal window
0 3 * * * /path/to/your-job; curl -fsS -m 10 --retry 3 https://culipulse.dev/ping/<your-token>/$?

The schedule has to match the monitor’s interval: a job that runs once an hour on a 5-minute monitor goes down and back up twice an hour. Your monitor’s page has a ready-to-copy version for crontab, a shell script, Python, Node.js, Kubernetes and GitHub Actions, with the schedule filled in for your interval.

For rotating a heartbeat’s URL, see Managing monitors.