# IsItDown — Light edition configuration. # # Copy this file to config.yml and edit it. In the container it is mounted at # /app/config/config.yml, so it can be changed without rebuilding the image. # # Secrets are NEVER written here. Use ${ENV_VAR} references; the values come # from the environment (see .env.example). pollIntervalMinutes: 3 # how often to poll every provider requestTimeoutSeconds: 8 # per-request timeout maxRetries: 3 # attempts per provider per cycle, with backoff failureThreshold: 5 # consecutive failures before a "monitoring degraded" warning adaptivePolling: true # while a provider has an open incident, poll it on the cadence below adaptiveIntervalMinutes: 1 # that cadence; never slower than the provider's own interval confirmSamples: 1 # consecutive polls that must agree before a change notifies (1 = off) # Correlated-outage detection (roadmap 2.7): when this many providers go bad # inside the window below, one "this looks like a shared failure" alert replaces # the individual ones. 0 and 1 are off, which is the default — it is the only # setting here that replaces alerts rather than adding them. correlationThreshold: 0 correlationWindowMinutes: 10 locale: en # language for notification messages: en | it (falls back to en) services: - name: GitHub id: github adapter: statuspage baseUrl: https://www.githubstatus.com # Optional: poll this provider on its own cadence instead of the global # pollIntervalMinutes above. A page that publishes every few minutes and one # that changes twice a year do not deserve the same one. Omit to follow the # global interval. intervalMinutes: 2 # Optional: the group this provider belongs to — "my stack". Providers in # the same group get a combined status (worst member wins), and one routing # rule below can cover all of them with `provider: group:deploy-path`. A # lowercase slug; omit it to leave the provider ungrouped. group: deploy-path # Optional: the monthly uptime this provider is supposed to deliver, as a # percentage. The UI edition turns it into an error budget, reports what the # month has spent of it, and sends one alert when the rate says the month # will miss. Omit it when nobody promised anything — most providers. slaTarget: 99.9 # Optional: what an hour of this provider being down costs you, in your own # currency. Shown beside each incident, in the reliability table and in the # monthly report. Omit it and no figure is shown — an unpriced outage is not # a free one. outageCostPerHour: 1200 # Optional: what breaks here when this provider does, and who answers for # it. Both are added under every message about it, so an alert at 3 a.m. # reads as a handover note. impact: deploys blocked, CI stopped owner: "@alice" # Optional: what to do when it is down, in Markdown. Shown on the provider's # page; every alert ends with its first link, or its first line. runbook: | Switch CI to the GitLab mirror: https://wiki.example.com/runbooks/github # Optional: free labels beside the group. A routing rule can name one as # `provider: tag:critical`. tags: [paid, critical] # Optional: planned mutes — a weekly window (may wrap midnight) or one span. # While one runs the provider is muted exactly like the dashboard's Mute. muteSchedules: - { days: [sun], start: "03:00", end: "04:00", timeZone: Europe/Rome } # Optional: maintenance you declare because the provider does not. It # silences, is announced and is drawn on the timeline like the provider's # own; it always needs an end. # maintenanceWindows: # - { title: Our deploy, startsAt: "2026-12-01T22:00:00Z", endsAt: "2026-12-01T23:00:00Z" } - name: Cloudflare id: cloudflare adapter: statuspage baseUrl: https://www.cloudflarestatus.com group: deploy-path # Optional: watch only part of a provider. Component ids come from # /api/v2/summary.json, where Cloudflare groups its data centres by # region. With scopeToComponents an incident attributed only to components # outside this list is ignored, and the reported status is the worst of these # components rather than the page-wide indicator. A notice the provider # attaches to no component at all is page-wide and always reported. scopeToComponents: true components: - id: 57ctn3f2qsyj name: Amsterdam, Netherlands - (AMS) - id: k8q1kmz4gfms name: Milan, Italy - (MXP) - name: Anthropic id: anthropic adapter: statuspage # status.anthropic.com 301-redirects here; the canonical host avoids the hop baseUrl: https://status.claude.com # A page that publishes neither JSON nor a feed: read one element's text and # map the words onto a severity. Fragile by nature — a selector that stops # matching is reported as a failing provider, never as "operational" — so use # it only where there is nothing better. # # - name: Example # id: example # adapter: html # baseUrl: https://status.example.com/ # options: # selector: ".status-banner" # # Optional. Each severity keeps its built-in words unless you give it # # your own; on the page, the longest match wins. # operational: "all systems operational" # major_outage: "outage, down" # A probe of the service itself rather than of a page about it. Here it is # also the second opinion on GitHub's own status page: when this probe cannot # reach the API and githubstatus.com still reports operational with no # incident open, the disagreement is itself an alert — the silent-outage # cross-check. # # - name: GitHub API # id: github-api # adapter: http # baseUrl: https://api.github.com # crossChecks: github # options: # path: /meta # expectStatus: "200-299" # # Which source is the record for a provider — `declared` (its own status page) # or `observed` (our own reading), roadmap 9.1. Left out, which is the normal # case, the adapter decides: a probe is `observed` because it *is* the # reading, and anything that parses somebody's page is `declared`. Set it only # to disagree with that default. # # - name: Acme # id: acme # adapter: statuspage # baseUrl: https://status.acme.example # authority: observed notifications: telegram: enabled: true botToken: "${TELEGRAM_BOT_TOKEN}" chatId: "${TELEGRAM_CHAT_ID}" # Optional, per channel (roadmap 3.20 and 3.15). `locale` overrides the # notification language above for this channel alone — so this chat can read # Italian while the webhook payload below stays English. `template` replaces # the default message: substitution only, no conditions and no loops, and an # unknown token stops the container with the token named. See # docs/configuration.md#311-per-channel-locale-and-template for the tokens. # locale: it # template: "[{{severity}}] {{provider}} — {{title}}\n{{url}}" webhook: enabled: false url: "${WEBHOOK_URL}" # Optional. With a secret set, every request carries X-IsItDown-Timestamp # and X-IsItDown-Signature (HMAC-SHA256 over "."), so a # receiver can verify the payload came from here. Unset means unsigned. secret: "${WEBHOOK_SECRET}" discord: enabled: false # Server settings → Integrations → Webhooks → Copy Webhook URL webhookUrl: "${DISCORD_WEBHOOK_URL}" slack: enabled: false # An incoming webhook of a Slack app installed in the target channel webhookUrl: "${SLACK_WEBHOOK_URL}" ntfy: enabled: false # The topic URL, server included — ntfy.sh or an instance of your own topicUrl: "${NTFY_TOPIC_URL}" # Optional: only a server with access control needs a token token: "${NTFY_TOKEN}" gotify: enabled: false # The server itself, not a message path serverUrl: "${GOTIFY_URL}" # An application token, from the Gotify app you created for IsItDown token: "${GOTIFY_TOKEN}" pushover: enabled: false # The API token of the Pushover application you created for IsItDown token: "${PUSHOVER_TOKEN}" # Your user key, or a group key, from the Pushover dashboard userKey: "${PUSHOVER_USER_KEY}" # Optional: one registered device; empty delivers to every device device: "${PUSHOVER_DEVICE}" matrix: enabled: false # The homeserver's root, not a room path homeserverUrl: "${MATRIX_HOMESERVER_URL}" # The internal room id, not an alias: !abc:example.org roomId: "${MATRIX_ROOM_ID}" # An access token for the user that posts accessToken: "${MATRIX_ACCESS_TOKEN}" pagerduty: enabled: false # The integration key of an Events API v2 integration on a service. An # incident that resolves closes the alert it opened, keyed on the change. routingKey: "${PAGERDUTY_ROUTING_KEY}" # Optional: "eu" for an account in PagerDuty's EU service region region: "${PAGERDUTY_REGION}" opsgenie: enabled: false # An API key from an Opsgenie API integration apiKey: "${OPSGENIE_API_KEY}" # Optional: "eu" for an account on Opsgenie's EU instance region: "${OPSGENIE_REGION}" apprise: enabled: false # An Apprise API server, which reaches the ~80 services Apprise knows serverUrl: "${APPRISE_SERVER_URL}" # Either the key of a configuration the server stores (preferred — the # service URLs behind it are credentials) ... configKey: "${APPRISE_CONFIG_KEY}" # ... or the Apprise URLs themselves, comma-separated, for a stateless # server. One of the two is required. urls: "${APPRISE_URLS}" teams: enabled: false # A Teams channel webhook: Workflows → "Post to a channel when a webhook # request is received", or an Office 365 connector on an older tenant webhookUrl: "${TEAMS_WEBHOOK_URL}" email: enabled: false # An SMTP submission server. STARTTLS is used whenever the server offers it. host: "${SMTP_HOST}" # Optional, 587 by default. 465 is implicit TLS and is treated as such. port: "${SMTP_PORT}" # Optional: "true" forces TLS from the first byte, as on port 465 secure: "${SMTP_SECURE}" # Optional. Credentials are never sent over an unencrypted connection # unless this says "true" — meant for a relay on this machine. allowInsecureAuth: "${SMTP_ALLOW_INSECURE_AUTH}" # Optional: "true" accepts a certificate no public CA signed allowSelfSigned: "${SMTP_ALLOW_SELF_SIGNED}" # Optional: a relay that trusts this network needs no credentials username: "${SMTP_USERNAME}" password: "${SMTP_PASSWORD}" from: "${SMTP_FROM}" # One address, or several separated by commas to: "${SMTP_TO}" # On-call chains (roadmap 3.9). Like PagerDuty and Opsgenie, each keys its # alert on the change, so a provider's own "resolved" closes what it opened. splunk-oncall: enabled: false # The REST endpoint integration's API key, and the routing key that picks # the escalation policy apiKey: "${SPLUNK_ONCALL_API_KEY}" routingKey: "${SPLUNK_ONCALL_ROUTING_KEY}" squadcast: enabled: false # An Incident Webhook integration's URL webhookUrl: "${SQUADCAST_WEBHOOK_URL}" zenduty: enabled: false # An API integration's key integrationKey: "${ZENDUTY_INTEGRATION_KEY}" incident-io: enabled: false # An HTTP alert source: its config id and its bearer token alertSourceId: "${INCIDENT_IO_ALERT_SOURCE_ID}" token: "${INCIDENT_IO_TOKEN}" grafana-oncall: enabled: false # A Formatted Webhook integration's URL webhookUrl: "${GRAFANA_ONCALL_WEBHOOK_URL}" # Team chat through each server's API, so an incident's updates edit the # message they belong to instead of adding one each. mattermost: enabled: false serverUrl: "${MATTERMOST_URL}" # A bot account's access token, and the channel's id (not its name) token: "${MATTERMOST_TOKEN}" channelId: "${MATTERMOST_CHANNEL_ID}" rocketchat: enabled: false serverUrl: "${ROCKETCHAT_URL}" # The bot user's id and a personal access token of it, and the room's id userId: "${ROCKETCHAT_USER_ID}" token: "${ROCKETCHAT_TOKEN}" roomId: "${ROCKETCHAT_ROOM_ID}" zulip: enabled: false siteUrl: "${ZULIP_SITE_URL}" botEmail: "${ZULIP_BOT_EMAIL}" apiKey: "${ZULIP_API_KEY}" # The channel ("stream") to post in stream: "${ZULIP_STREAM}" # Optional: one fixed topic. Unset, each provider gets a topic of its own. topic: "${ZULIP_TOPIC}" google-chat: enabled: false # A space's incoming webhook. An incident's messages share one thread. webhookUrl: "${GOOGLE_CHAT_WEBHOOK_URL}" # Phones. Numbers are E.164 (+39...). Route only what is worth waking up # for to the call — a rule with minSeverity: major_outage. twilio-sms: enabled: false accountSid: "${TWILIO_SMS_ACCOUNT_SID}" authToken: "${TWILIO_SMS_AUTH_TOKEN}" from: "${TWILIO_SMS_FROM}" to: "${TWILIO_SMS_TO}" twilio-call: enabled: false accountSid: "${TWILIO_CALL_ACCOUNT_SID}" authToken: "${TWILIO_CALL_AUTH_TOKEN}" from: "${TWILIO_CALL_FROM}" to: "${TWILIO_CALL_TO}" bark: enabled: false # The device key the Bark iOS app shows deviceKey: "${BARK_DEVICE_KEY}" # Optional: a self-hosted bark-server. Unset means https://api.day.app serverUrl: "${BARK_SERVER_URL}" signal: enabled: false # A signal-cli-rest-api server, which holds the account's keys serverUrl: "${SIGNAL_API_URL}" # The registered number to send from, and who receives: numbers or # group.… ids, comma-separated number: "${SIGNAL_NUMBER}" recipients: "${SIGNAL_RECIPIENTS}" # Machines. syslog: enabled: false # udp://host:514, tcp://host:601 or tls://host:6514 — RFC 5424 messages url: "${SYSLOG_URL}" # Optional: local0 (the default) to local7 facility: "${SYSLOG_FACILITY}" mqtt: enabled: false # mqtt://broker:1883, or mqtts:// for TLS brokerUrl: "${MQTT_BROKER_URL}" # Optional: the topic prefix, "isitdown" by default. Messages go to # /; a provider's state is published retained. topic: "${MQTT_TOPIC}" # Optional: only for a broker that wants credentials username: "${MQTT_USERNAME}" password: "${MQTT_PASSWORD}" # Public posts. The operator's impact and owner notes are never posted. mastodon: enabled: false instanceUrl: "${MASTODON_INSTANCE_URL}" # An application's access token with write:statuses accessToken: "${MASTODON_ACCESS_TOKEN}" # Optional: public (the default), unlisted or private visibility: "${MASTODON_VISIBILITY}" bluesky: enabled: false handle: "${BLUESKY_HANDLE}" # An app password (Settings → App passwords), never the account's own appPassword: "${BLUESKY_APP_PASSWORD}" # Optional: the account's PDS, when it is not bsky.social serviceUrl: "${BLUESKY_SERVICE_URL}" # Which channels hear about what. The first rule that matches a change decides, # rules below it are not consulted — put specific ones on top. Leave this # section out entirely and every change goes to every enabled channel, which is # what happens without it. # # classes: status | incident | maintenance | monitoring (default: all) # minSeverity: any | degraded | partial_outage | major_outage (default: any) # channels: channel names from `notifications` above, ["*"] for all of # them, or [] to stay silent # # A rule's severity is the worse of the status a change came from and the one # it went to, so a recovery from a major outage reaches the same channels the # outage did. # # routing: # # A provider watched out of curiosity: on the dashboard, never in a message. # - provider: anthropic # channels: [] # # The whole deploy path, in one rule: it stays right when the stack gains a # # fifth provider, which four provider ids never would. # - provider: group:deploy-path # classes: [status, incident] # minSeverity: degraded # channels: [telegram] # # Real trouble, anywhere: every channel gets attention. # - provider: "*" # classes: [status, incident] # minSeverity: major_outage # channels: [telegram] # # Everything else, including scheduled maintenance and IsItDown's own # # polling trouble: somewhere quieter. # - provider: "*" # channels: [slack] # How much of what the rules above admit actually goes out, and in how many # messages. Leave this section out and all four controls are off, which is what # every installation before them was doing. # # delivery: # # Quiet hours: inside the window, only changes at or above the floor are # # sent. Anything under it is dropped, not deferred — the digest below is the # # control that defers. The window may wrap midnight, and is read in # # `timeZone` ("auto" means the zone the container runs in). # quietHours: # enabled: true # start: "23:00" # end: "07:00" # timeZone: "Europe/Rome" # minSeverity: major_outage # # Digest mode: collect changes under `immediateFloor` and send them as one # # message per window. Anything at or above the floor still goes out the # # moment it happens, so the window's length only ever delays the news you # # said was not urgent. A batch collected when the container stops is lost # # rather than sent an hour late. # digest: # enabled: true # windowMinutes: 15 # immediateFloor: major_outage # # A ceiling per provider, per rolling hour. What the cap holds back is # # counted, and the next message that gets through says how much it hid. # cap: # enabled: true # maxPerHour: 6 # # One incident becomes one message that is rewritten as the incident moves, # # on channels that can edit one (telegram, discord). The others still get a # # message per update. # updateInPlace: true # # # A heads-up before a maintenance window the provider has declared opens # # (not while it runs): "GitHub maintenance in 2 h, expected 30 min". # maintenanceNotice: # enabled: true # leadMinutes: 120 # # # A major change nobody took in charge is said again on another channel # # after this long. UI edition only: it needs the "I'm on it" button to know # # whether anybody answered. # escalation: # enabled: true # afterMinutes: 15 # channel: pushover # minSeverity: major_outage # # # When a channel refuses this many messages in a row, say so on the # # others instead of leaving it to a badge in the delivery log. # channelHealth: # enabled: true # afterFailures: 3 # # # When several providers fail together and then recover, send one message # # ("6 providers, 47 minutes, probable cause: cloudflare") instead of one # # "resolved" per provider. The members' own recoveries are held back until # # the last one is back. # incidentSummary: # enabled: true # # # A provider that posts several updates of the same incident in a short # # time produces one message: the last state, sent once nothing newer has # # arrived for this long. # coalesce: # enabled: true # windowMinutes: 2 # # # While a provider is already in major outage, further updates of its # # incident go to the digest window instead of interrupting. A change of # # severity or the resolution still goes out at once. # knownOutage: # enabled: true # # # UI edition only. Attach the provider's last 24 hours, as an SVG, to status # # and incident messages. Only the email and webhook channels can carry it. # attachChart: true # # # UI edition only. The fleet's week or month on one channel: fleet uptime, # # the weakest providers, incidents, error budget spent. It is not an alert, # # so routing rules do not apply, but it waits out quiet hours. # report: # enabled: true # frequency: weekly # weekly | monthly # channel: slack # # # Who is on call this week: a rotation of the recipients defined below, # # one per week, Monday to Sunday UTC. Whoever's week it is also hears every # # change at or above minSeverity, on top of what the routing rules chose. # onCall: # enabled: true # minSeverity: major_outage # startsOn: 2026-10-05 # any day of the first week # rotation: [alice, bob] # # # UI edition only. Once a week every enabled channel gets a message that # # says it is a routine test; a failure raises the failing-channel alert. # deliveryTest: # enabled: true # People reached through more than one channel, preferred first. A change a # rule sends to several of one person's channels goes to the first only, and to # the next only if that one fails — so nobody hears it twice, and nobody has to # mute their second channel to stop it. # # recipients: # - id: alice # name: Alice # channels: [telegram, discord]