Skip to content

Configuration

Arian Omrani edited this page Oct 11, 2026 · 6 revisions

Configuration

Language: English · فارسی

Every key with its type and default is in the YAML reference. Default file name: pulsecheck.yml (change with -c). Any ${NAME} inside a value is replaced with the environment variable NAME. A missing variable is an error, so keep secrets out of the file.

Inside inline { ... } mappings, quote values that contain ${VAR}: {name: api, url: "https://x.example.com/${TOKEN}"}.

state_file: pulsecheck.db
shoutrrr_bin: shoutrrr
notify:
  urls: ["telegram://${TG_TOKEN}@telegram?chats=${TG_CHAT}"]
  events: [down, up, degraded]
defaults: {timeout: 10, down_after: 2, interval: 5, slow_ms: 3000}
maintenance:
  - {name: weekly, start: "SUN 02:00", duration: 60, sites: [api]}
sites:
  - {name: web, url: https://example.com}

Top level

Key Default Meaning
state_file pulsecheck.db SQLite file holding current state and check history (kept 400 days)
shoutrrr_bin auto auto (docker image if Docker works, else the shoutrrr binary), docker, or a path to a binary
shoutrrr_image nickfedor/shoutrrr:0.21.2 Docker image used in auto/docker mode
notify none Global alert targets, see below
defaults {} Any site key; merged into every site (a site's own value wins)
messages built-in Alert texts by key, see Messages
otel none Push results as OpenTelemetry, see OpenTelemetry
maintenance [] Windows where alerts are suppressed
sites required List of checks; name must be unique

Site keys

Key Default Applies to Meaning
name required all Unique name, used in alerts and reports
type http all http, tcp or ping
url – http Full URL (required)
host – tcp, ping Host name or IP (required)
port – tcp Port (required)
method GET http HTTP method
headers {} http Request headers
body – http Request body (string)
expected_status any 2xx http List such as [200, 301]
max_redirects 5 http Redirects are followed up to this number
max_response_ms off all Slower than this counts as a failure
slow_ms off all Slower than this (but successful) is degraded
timeout 10 all Seconds
down_after 2 all Consecutive failures before the site is marked down
interval 5 all Minutes between checks (see below)
tags [] all Free labels, e.g. [prod, db]. Used by --tag, and as {tags} in messages
ssl_warn_days off http Warn when the https certificate expires in fewer days; an expired certificate is a failure. Not available on Cloudflare
remind_every 0 (off) all Minutes between repeated "still down" alerts while a site stays down (also paces repeated certificate warnings)
notify global all Overrides the global notify for this site
messages global all Overrides message texts for this site only

Status and alerts

  • A successful check is up, or degraded if it took at least slow_ms.
  • A failed check only marks the site down after down_after failures in a row. Shorter blips never alert.
  • Alerts are sent only when the status changes: down, degraded, or up (recovery; the message includes how many minutes the site was affected).
  • events filters which of down, up, degraded, ssl_warn are sent (a repeated "still down" reminder counts as down). A site-level notify replaces the global one completely, including events.
  • Alert failures (missing binary, bad URL) are printed to stderr and never stop the run.

Reminders and certificates

  • remind_every: 60 sends the reminder message every 60 minutes while the site stays down. 0 (default) sends the first down alert only.
  • ssl_warn_days: 14 checks the https certificate on every check. When fewer than 14 days remain you get an ssl_warn alert, at most once a day (or every remind_every minutes if set). If the certificate has expired the check fails with TLS certificate has expired, which is a normal down.
  • Unknown keys anywhere in the file are errors, so typos are caught by validate.

IPv4 and IPv6

By default the operating system picks the address family. To check one family on purpose, set ip_version on the site:

sites:
  - {name: web-v4, url: "https://example.com", ip_version: 4}
  - {name: web-v6, url: "https://example.com", ip_version: 6}   # down if the host has no AAAA record or IPv6 is unreachable
  - {name: dns-v6, type: tcp, host: "2001:4860:4860::8888", port: 53}   # an IPv6 literal needs no ip_version
  - {name: gateway-v6, type: ping, host: gw.example.com, ip_version: 6}  # runs `ping -6`

It applies to http, tcp, ping and the certificate check (ssl_warn_days). Giving the same host two entries, one per family, tells you which side broke. If the machine running pulsecheck has no IPv6 route, an ip_version: 6 site is reported down; the error says why (Network is unreachable, Address family for hostname not supported, or Name or service not known). Cloudflare Workers cannot choose a family, so those sites are skipped there.

Tags and status

Give sites tags: [prod] and limit commands with --tag prod (run, status, report). pulsecheck status prints the current state of every site (--json for scripts, --md for a markdown table, for example to append to $GITHUB_STEP_SUMMARY). There is no dashboard; this is the status view. On Vercel it is GET /api/status, on Cloudflare GET /status (both need the token, see Serverless).

Intervals

Your scheduler decides how often pulsecheck runs; interval only skips sites whose last check is too recent. Example: run every 5 minutes, give a heavy check interval: 60 and it runs hourly. A one-minute margin absorbs scheduler jitter, so interval: 5 with a 5-minute schedule checks every run.

Maintenance windows

Times are UTC. start is "HH:MM" (every day) or "DAY HH:MM" with MON TUE WED THU FRI SAT SUN. duration is in minutes and may cross midnight. Empty sites means all sites. Checks still run and are recorded, but alerts raised during the window are dropped, not delivered later.

Messages

Every alert text has a key. The code uses only these keys, and you can change any of them under messages: (globally, or inside a single site). Keys you don't set keep the built-in text.

Key Sent when Built-in text
down site went down 🔴 {site} is DOWN: {error}
degraded site is up but slow 🟡 {site} is slow ({ms}ms)
up site recovered 🟢 {site} is UP ({ms}ms) after {minutes}m
reminder site still down after remind_every 🔴 {site} is still DOWN after {minutes}m: {error}
ssl_warn certificate expires soon 🔒 {site}: TLS certificate expires in {days} days
test pulsecheck test-notify pulsecheck test notification

Placeholders: {site} name, {event} (down/degraded/up), {error} failure reason (empty on success), {ms} response time, {minutes} minutes since the last status change, {status} HTTP status code (empty for tcp/ping), {tags} the site's tags joined by commas, {days} days until certificate expiry (only in ssl_warn). Use {{ and }} for literal braces. Unknown keys or placeholders are rejected by validate. Order of precedence: site messages > global messages > built-in.

messages:
  down: "ALERT {site} unreachable ({error})"
  up: "{site} recovered after {minutes} min"
sites:
  - name: payments
    url: https://pay.example.com
    messages: {down: "💳 payments DOWN, page on-call"}

OpenTelemetry

Set otel: to push every run to an OpenTelemetry collector (or any service that accepts OTLP/HTTP JSON, which makes it work like a webhook). No OpenTelemetry package is needed, and it works on every platform.

otel:
  endpoint: https://otel-collector.example.com:4318   # /v1/metrics and /v1/logs are appended
  headers: {Authorization: "Bearer ${OTEL_TOKEN}"}
  resource: {env: prod}        # extra resource attributes; service.name defaults to "pulsecheck"
  # service_name: pulsecheck
  # metrics: true              # per check: pulsecheck.up (1/0), .response_time (ms), .http.status_code, .ssl.days_remaining
  # logs: true                 # one log record per alert (ERROR for down, WARN for slow/ssl, INFO for recovery)

Metric attributes: site, type, state (up/down/degraded) and tags. A failed export is printed to stderr and never fails the run. pulsecheck test-notify sends a test log record to the endpoint.

Reports

pulsecheck report shows, for day / week / month / year: uptime %, average and p95 response time of successful checks, and the number of checks. --json prints the same data for scripts. On Vercel/Cloudflare the state store keeps daily totals only, so report there has no p95 and covers the last 90 days.

Clone this wiki locally