openstatus logoDashboard

Terraform Provider Reference

Overview

The openstatus Terraform provider enables you to manage your monitoring infrastructure programmatically using HashiCorp Terraform. Define monitors, notification channels, and status pages as code — with full support for version control, automated deployments, and IaC workflows.

Key capabilities:

  • Manage HTTP, TCP, and DNS monitors with assertions
  • Configure notification channels (Slack, PagerDuty, email, webhooks, and more)
  • Create and manage status pages with component groups
  • Import existing resources into Terraform state

Installation

Declare the provider in your Terraform configuration. Terraform will automatically download it when you run terraform init.

terraform {
  required_providers {
    openstatus = {
      source  = "openstatusHQ/openstatus"
      version = "~> 0.1"
    }
  }
}

For the latest version, refer to the Terraform Registry.

Provider Configuration

provider "openstatus" {
  api_token = "YOUR_OPENSTATUS_API_TOKEN" # or set OPENSTATUS_API_TOKEN env var
}
ArgumentTypeRequiredDescription
api_tokenstringYesYour openstatus API token. Can also be set via the OPENSTATUS_API_TOKEN environment variable.
base_urlstringNoBase URL for the openstatus API. Defaults to https://api.openstatus.dev/rpc.

Resources

openstatus_http_monitor

Manages an HTTP monitor with support for custom headers, request bodies, and response assertions.

Arguments:

ArgumentTypeRequiredDefaultDescription
namestringYesMonitor name (max 256 chars).
urlstringYesURL to monitor (max 2048 chars).
periodicitystringYesCheck frequency: 30s, 1m, 5m, 10m, 30m, 1h.
methodstringNo"GET"HTTP method: GET, POST, HEAD, PUT, PATCH, DELETE, OPTIONS.
bodystringNo""Request body for POST, PUT, PATCH methods.
timeoutnumberNo45000Timeout in milliseconds (0–120000).
degraded_atnumberNocomputedResponse time threshold (ms) after which the monitor is considered degraded.
retrynumberNo3Number of retries on failure (0–10).
follow_redirectsboolNotrueWhether to follow HTTP redirects.
activeboolNofalseWhether the monitor is active.
publicboolNofalseWhether the monitor is visible on your public status page.
descriptionstringNoMonitor description (max 1024 chars).
regionsset(string)NoRegions to monitor from. See Available Regions.

Blocks:

  • headers (max 20) — Custom HTTP headers to include with each request.

    • key (string, required) — Header name.
    • value (string, required) — Header value.
  • status_code_assertions (max 10) — Assert on response status codes.

    • target (number, required) — Expected status code (100–599).
    • comparator (string, required) — One of: eq, neq, gt, gte, lt, lte.
  • body_assertions (max 10) — Assert on response body content.

    • target (string, required) — Expected value.
    • comparator (string, required) — One of: contains, not_contains, eq, neq, empty, not_empty, gt, gte, lt, lte.
  • header_assertions (max 10) — Assert on response headers.

    • key (string, required) — Header name to assert on.
    • target (string, required) — Expected value.
    • comparator (string, required) — Same comparators as body_assertions.

Read-only attributes: id (string), status (string: active, degraded, error, unknown).

Example — Basic health check:

resource "openstatus_http_monitor" "website" {
  name        = "Website Availability"
  url         = "https://www.example.com"
  periodicity = "1m"
  active      = true
  public      = true
  regions     = ["fly-iad", "fly-ams", "fly-syd"]
}

Example — API monitor with assertions and headers:

resource "openstatus_http_monitor" "api" {
  name        = "API Health Check"
  description = "Monitors the /health endpoint with full assertions."
  url         = "https://api.example.com/health"
  periodicity = "5m"
  method      = "GET"
  timeout     = 30000
  active      = true
  regions     = ["fly-iad", "fly-ams", "fly-nrt"]

  headers {
    key   = "Authorization"
    value = "Bearer ${var.api_token}"
  }

  headers {
    key   = "Accept"
    value = "application/json"
  }

  status_code_assertions {
    target     = 200
    comparator = "eq"
  }

  body_assertions {
    target     = "ok"
    comparator = "contains"
  }

  header_assertions {
    key        = "Content-Type"
    target     = "application/json"
    comparator = "contains"
  }
}

Example — POST monitor with request body:

resource "openstatus_http_monitor" "webhook" {
  name        = "Webhook Endpoint"
  url         = "https://api.example.com/webhooks/health"
  periodicity = "10m"
  method      = "POST"
  active      = true
  regions     = ["fly-iad"]

  headers {
    key   = "Content-Type"
    value = "application/json"
  }

  body = jsonencode({
    type = "health_check"
  })

  status_code_assertions {
    target     = 202
    comparator = "eq"
  }
}

Import:

terraform import openstatus_http_monitor.website <monitor_id>

openstatus_tcp_monitor

Manages a TCP connection monitor to verify that a port is open and reachable.

Arguments:

ArgumentTypeRequiredDefaultDescription
namestringYesMonitor name (max 256 chars).
uristringYesTarget in host:port format (max 2048 chars).
periodicitystringYesCheck frequency: 30s, 1m, 5m, 10m, 30m, 1h.
timeoutnumberNo45000Timeout in milliseconds (0–120000).
degraded_atnumberNocomputedDegradation threshold in milliseconds.
retrynumberNo3Number of retries on failure (0–10).
activeboolNofalseWhether the monitor is active.
publicboolNofalseWhether the monitor is publicly visible.
descriptionstringNoMonitor description (max 1024 chars).
regionsset(string)NoRegions to monitor from. See Available Regions.

Read-only attributes: id (string), status (string).

Example — Database port check:

resource "openstatus_tcp_monitor" "database" {
  name        = "PostgreSQL Port Check"
  description = "Ensures the database port is open and reachable."
  uri         = "db.example.com:5432"
  periodicity = "1m"
  timeout     = 10000
  active      = true
  regions     = ["fly-iad", "fly-fra"]
}

Example — Redis monitor:

resource "openstatus_tcp_monitor" "redis" {
  name        = "Redis Connection"
  uri         = "redis.example.com:6379"
  periodicity = "30s"
  active      = true
  regions     = ["fly-iad", "fly-ams", "fly-nrt"]
}

Import:

terraform import openstatus_tcp_monitor.database <monitor_id>

openstatus_dns_monitor

Manages a DNS monitor with support for record type assertions.

Arguments:

ArgumentTypeRequiredDefaultDescription
namestringYesMonitor name (max 256 chars).
uristringYesDomain name to monitor (max 2048 chars).
periodicitystringYesCheck frequency: 30s, 1m, 5m, 10m, 30m, 1h.
timeoutnumberNo45000Timeout in milliseconds (0–120000).
degraded_atnumberNocomputedDegradation threshold in milliseconds.
retrynumberNo3Number of retries on failure (0–10).
activeboolNofalseWhether the monitor is active.
publicboolNofalseWhether the monitor is publicly visible.
descriptionstringNoMonitor description (max 1024 chars).
regionsset(string)NoRegions to monitor from. See Available Regions.

Blocks:

  • record_assertions (max 10) — Assert on DNS record values.
    • record (string, required) — DNS record type: A, AAAA, CNAME, MX, TXT.
    • target (string, required) — Expected value.
    • comparator (string, required) — One of: eq, neq, contains, not_contains.

Read-only attributes: id (string), status (string).

Example — A record validation:

resource "openstatus_dns_monitor" "main_domain" {
  name        = "DNS A Record Check"
  description = "Verifies that example.com resolves to the correct IP."
  uri         = "example.com"
  periodicity = "10m"
  active      = true
  regions     = ["fly-iad", "fly-ams"]

  record_assertions {
    record     = "A"
    comparator = "eq"
    target     = "93.184.216.34"
  }
}

Example — MX record validation:

resource "openstatus_dns_monitor" "email" {
  name        = "Email MX Record Check"
  uri         = "example.com"
  periodicity = "30m"
  active      = true
  regions     = ["fly-iad"]

  record_assertions {
    record     = "MX"
    comparator = "contains"
    target     = "mail.example.com"
  }
}

Import:

terraform import openstatus_dns_monitor.main_domain <monitor_id>

openstatus_notification

Manages a notification channel. Supports 12 provider types: Discord, Email, Slack, PagerDuty, OpsGenie, Webhook, Telegram, SMS, WhatsApp, Google Chat, Grafana OnCall, and ntfy.

Arguments:

ArgumentTypeRequiredDescription
namestringNoNotification channel name.
provider_typestringYesProvider type (see supported values below).
monitor_idsset(string)NoSet of monitor IDs to associate with this notification.

Supported provider_type values: discord, email, slack, pagerduty, opsgenie, webhook, telegram, sms, whatsapp, google_chat, grafana_oncall, ntfy.

Provider-specific blocks — use exactly one block matching your provider_type:

BlockArguments
discordwebhook_url (string, required, sensitive)
emailemail (string, required)
slackwebhook_url (string, required, sensitive)
pagerdutyintegration_key (string, required, sensitive)
opsgenieapi_key (string, required, sensitive), region (string, required: us or eu)
webhookendpoint (string, required), headers (optional list of key/value objects)
telegramchat_id (string, required)
smsphone_number (string, required)
whatsappphone_number (string, required)
google_chatwebhook_url (string, required, sensitive)
grafana_oncallwebhook_url (string, required, sensitive)
ntfytopic (string, required), server_url (string, optional), token (string, optional, sensitive)

Read-only attributes: id (string), created_at (string), updated_at (string).

Example — Slack notification:

resource "openstatus_notification" "slack_alerts" {
  name          = "Slack Alerts"
  provider_type = "slack"
  monitor_ids   = [openstatus_http_monitor.api.id]

  slack {
    webhook_url = var.slack_webhook_url
  }
}

Example — PagerDuty notification:

resource "openstatus_notification" "pagerduty" {
  name          = "PagerDuty Escalation"
  provider_type = "pagerduty"
  monitor_ids   = [
    openstatus_http_monitor.api.id,
    openstatus_tcp_monitor.database.id,
  ]

  pagerduty {
    integration_key = var.pagerduty_key
  }
}

Example — Email notification:

resource "openstatus_notification" "email" {
  name          = "On-Call Email"
  provider_type = "email"
  monitor_ids   = [openstatus_http_monitor.api.id]

  email {
    email = "oncall@example.com"
  }
}

Example — Custom webhook:

resource "openstatus_notification" "custom_webhook" {
  name          = "Custom Webhook"
  provider_type = "webhook"

  webhook {
    endpoint = "https://api.example.com/alerts"

    headers {
      key   = "Authorization"
      value = "Bearer ${var.webhook_token}"
    }

    headers {
      key   = "Content-Type"
      value = "application/json"
    }
  }
}

Example — Discord notification:

resource "openstatus_notification" "discord" {
  name          = "Discord Alerts"
  provider_type = "discord"

  discord {
    webhook_url = var.discord_webhook_url
  }
}

Import:

terraform import openstatus_notification.slack_alerts <notification_id>

openstatus_status_page

Manages a status page with access control and branding.

Arguments:

ArgumentTypeRequiredDescription
titlestringYesPage title (1–256 chars).
slugstringYesURL slug (1–256 chars). Used in the status page URL.
descriptionstringNoPage description (max 1024 chars).
homepage_urlstringNoLink to your homepage.
contact_urlstringNoLink to your contact page.
iconstringNoURL of the icon to display.
custom_domainstringNoCustom domain (DNS must point to openstatus first).
access_typestringNoAccess control: public, password, or email-domain.
passwordstringNoRequired when access_type is password. Sensitive.
auth_email_domainslist(string)NoRequired when access_type is email-domain.

Read-only attributes: id (string), published (bool), theme (string: system, light, dark), created_at (string), updated_at (string).

Example — Public status page:

resource "openstatus_status_page" "main" {
  title        = "Example Inc. Status"
  slug         = "example-status"
  description  = "Real-time status for all Example Inc. services."
  homepage_url = "https://example.com"
  contact_url  = "https://example.com/support"
}

Example — Password-protected status page:

resource "openstatus_status_page" "internal" {
  title       = "Internal Status"
  slug        = "internal-status"
  description = "Status page for internal services."
  access_type = "password"
  password    = var.status_page_password
}

Example — Email-domain restricted status page:

resource "openstatus_status_page" "company" {
  title              = "Company Status"
  slug               = "company-status"
  access_type        = "email-domain"
  auth_email_domains = ["example.com", "subsidiary.com"]
}

Import:

terraform import openstatus_status_page.main <page_id>

openstatus_status_page_component

Manages a component on a status page. Components can be linked to a monitor or be static.

Arguments:

ArgumentTypeRequiredDescription
page_idstringYesStatus page ID this component belongs to. Forces replacement if changed.
typestringYesComponent type: monitor or static. Forces replacement if changed.
monitor_idstringNoRequired when type is monitor. The monitor ID to display.
namestringNoComponent display name (max 256 chars).
descriptionstringNoComponent description (max 1024 chars).
ordernumberNoDisplay order on the status page.
group_idstringNoComponent group ID this belongs to.
group_ordernumberNoDisplay order within its group.

Read-only attributes: id (string), created_at (string), updated_at (string).

Example — Monitor component:

resource "openstatus_status_page_component" "api" {
  page_id    = openstatus_status_page.main.id
  type       = "monitor"
  monitor_id = openstatus_http_monitor.api.id
  name       = "API"
  order      = 1
}

Example — Static component:

resource "openstatus_status_page_component" "third_party" {
  page_id     = openstatus_status_page.main.id
  type        = "static"
  name        = "Third-party Services"
  description = "Status of external dependencies."
  order       = 2
}

Import:

terraform import openstatus_status_page_component.api <page_id>/<component_id>

openstatus_status_page_component_group

Manages a component group on a status page, allowing you to organize components visually.

Arguments:

ArgumentTypeRequiredDescription
page_idstringYesStatus page ID this group belongs to. Forces replacement if changed.
namestringYesGroup name (1–256 chars).

Read-only attributes: id (string), created_at (string), updated_at (string).

Example:

resource "openstatus_status_page_component_group" "infrastructure" {
  page_id = openstatus_status_page.main.id
  name    = "Infrastructure"
}

resource "openstatus_status_page_component_group" "applications" {
  page_id = openstatus_status_page.main.id
  name    = "Applications"
}

Import:

terraform import openstatus_status_page_component_group.infrastructure <page_id>/<group_id>

Data Sources

openstatus_monitor

Look up a single monitor by ID. Works for HTTP, TCP, and DNS monitors.

data "openstatus_monitor" "existing" {
  id = "123"
}

output "monitor_name" {
  value = data.openstatus_monitor.existing.name
}

output "monitor_type" {
  value = data.openstatus_monitor.existing.type
}

Computed attributes: type (http, tcp, dns), name, url (HTTP only), uri (TCP/DNS only), periodicity, method (HTTP only), active, public, description, timeout, status.


openstatus_monitors

List all monitors with pagination.

data "openstatus_monitors" "all" {
  limit  = 100
  offset = 0
}

output "total_monitors" {
  value = length(data.openstatus_monitors.all.monitors)
}
ArgumentTypeRequiredDefaultDescription
limitnumberNo50Max results (1–100).
offsetnumberNo0Pagination offset.

Computed: monitors — list of objects with id, name, type.


openstatus_notification

Look up a notification channel by ID.

data "openstatus_notification" "existing" {
  id = "456"
}

output "notification_provider" {
  value = data.openstatus_notification.existing.provider_type
}

Computed attributes: name, provider_type, monitor_ids, created_at, updated_at.


openstatus_status_page

Look up a status page by ID.

data "openstatus_status_page" "existing" {
  id = "789"
}

output "status_page_url" {
  value = data.openstatus_status_page.existing.slug
}

Computed attributes: title, slug, description, homepage_url, contact_url, icon, custom_domain, published, access_type, password (sensitive), auth_email_domains, theme, created_at, updated_at.

Available Regions

Monitors can run from any of the following 28 regions:

Fly.io: fly-ams, fly-arn, fly-bom, fly-cdg, fly-dfw, fly-ewr, fly-fra, fly-gru, fly-iad, fly-jnb, fly-lax, fly-lhr, fly-nrt, fly-ord, fly-sjc, fly-sin, fly-syd, fly-yyz

Koyeb: koyeb-fra, koyeb-par, koyeb-sfo, koyeb-sin, koyeb-tyo, koyeb-was

Railway: railway-us-west2, railway-us-east4, railway-europe-west4, railway-asia-southeast1

Full End-to-End Example

This example sets up a complete monitoring stack: HTTP and TCP monitors, Slack notifications, and a public status page with grouped components.

terraform {
  required_providers {
    openstatus = {
      source  = "openstatusHQ/openstatus"
      version = "~> 0.1"
    }
  }
}

provider "openstatus" {
  api_token = var.openstatus_api_token
}

# --- Variables ---

variable "openstatus_api_token" {
  type      = string
  sensitive = true
}

variable "slack_webhook_url" {
  type      = string
  sensitive = true
}

# --- Monitors ---

resource "openstatus_http_monitor" "api" {
  name        = "API Health"
  description = "Monitors the main API health endpoint."
  url         = "https://api.example.com/health"
  periodicity = "5m"
  method      = "GET"
  timeout     = 30000
  active      = true
  public      = true
  regions     = ["fly-iad", "fly-ams", "fly-nrt"]

  status_code_assertions {
    target     = 200
    comparator = "eq"
  }

  body_assertions {
    target     = "ok"
    comparator = "contains"
  }
}

resource "openstatus_http_monitor" "website" {
  name        = "Website"
  url         = "https://www.example.com"
  periodicity = "1m"
  active      = true
  public      = true
  regions     = ["fly-iad", "fly-ams", "fly-syd"]

  status_code_assertions {
    target     = 200
    comparator = "eq"
  }
}

resource "openstatus_tcp_monitor" "database" {
  name        = "PostgreSQL"
  uri         = "db.example.com:5432"
  periodicity = "1m"
  timeout     = 10000
  active      = true
  regions     = ["fly-iad"]
}

resource "openstatus_dns_monitor" "domain" {
  name        = "DNS Resolution"
  uri         = "example.com"
  periodicity = "10m"
  active      = true
  regions     = ["fly-iad", "fly-ams"]

  record_assertions {
    record     = "A"
    comparator = "eq"
    target     = "93.184.216.34"
  }
}

# --- Notifications ---

resource "openstatus_notification" "slack" {
  name          = "Slack Alerts"
  provider_type = "slack"
  monitor_ids   = [
    openstatus_http_monitor.api.id,
    openstatus_http_monitor.website.id,
    openstatus_tcp_monitor.database.id,
  ]

  slack {
    webhook_url = var.slack_webhook_url
  }
}

# --- Status Page ---

resource "openstatus_status_page" "main" {
  title        = "Example Inc. Status"
  slug         = "example-status"
  description  = "Real-time status for all Example Inc. services."
  homepage_url = "https://example.com"
  contact_url  = "https://example.com/support"
}

resource "openstatus_status_page_component_group" "web" {
  page_id = openstatus_status_page.main.id
  name    = "Web Services"
}

resource "openstatus_status_page_component_group" "infra" {
  page_id = openstatus_status_page.main.id
  name    = "Infrastructure"
}

resource "openstatus_status_page_component" "api_component" {
  page_id     = openstatus_status_page.main.id
  type        = "monitor"
  monitor_id  = openstatus_http_monitor.api.id
  name        = "API"
  group_id    = openstatus_status_page_component_group.web.id
  order       = 1
  group_order = 1
}

resource "openstatus_status_page_component" "website_component" {
  page_id     = openstatus_status_page.main.id
  type        = "monitor"
  monitor_id  = openstatus_http_monitor.website.id
  name        = "Website"
  group_id    = openstatus_status_page_component_group.web.id
  order       = 1
  group_order = 2
}

resource "openstatus_status_page_component" "db_component" {
  page_id     = openstatus_status_page.main.id
  type        = "monitor"
  monitor_id  = openstatus_tcp_monitor.database.id
  name        = "Database"
  group_id    = openstatus_status_page_component_group.infra.id
  order       = 2
  group_order = 1
}