Skip to main content

How-To: Managing Route53 with Terraform

Introduction

This guide provides step-by-step instructions for adding and modifying Route53 DNS records and hosted zones using Terraform in the Infrastructure repository. It assumes you are starting from zero experience with the infrastructure codebase.

The self-service Route53 workflow is designed for scenarios where you need to:

  • Add an A, CNAME, MX, or TXT record for a new application
  • Point a record at an AWS resource (load balancer, CloudFront, S3 website) using an alias
  • Set up DNS verification records for an ACM certificate or a third-party vendor
  • Roll traffic between two backends with weighted routing
  • Add health-check-driven failover between primary and secondary endpoints
  • Stand up a new hosted zone for a new domain

What This Workflow Does: You edit the relevant configuration files in a merge request — a JSON record file for record changes, terraform.tfvars for hosted zone or health check changes. The CI/CD pipeline runs terraform plan, the Platform team reviews and approves, you merge to main, and an operator presses the manual Apply button on the resulting pipeline.

Prerequisites

Required

  • GitLab Access: Developer (or higher) permissions on the Infrastructure repository so you can create a branch and open a merge request. This is the only hard requirement — everything else in the workflow is done through GitLab. If you don't have access, contact the Platform team via the @platform tag in any public Digital Product Group Teams channel. The @platform group tag only works in public Digital Product Group channels — private channels won't resolve it.

You can complete the entire workflow from the GitLab UI, but most contributors find the tooling below makes the experience much smoother — especially for record-heavy zones.

  • AWS Access (for verification): SSO access to the target AWS account via AWS Identity Center. Useful for confirming a record resolves correctly after merge. Without it, you can still verify with dig or nslookup from your laptop. See Managing Application Secrets for Console access details.
  • Local clone + Git: If you prefer editing in an IDE over the GitLab web UI, clone the repo locally. Not required — the GitLab web editor can do everything described in this guide.

Git ships with macOS (via the Xcode Command Line Tools). If you want a newer version, install Homebrew first — it doesn't come pre-installed:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install git
  • VS Code + HashiCorp Terraform extension: Highly recommended for syntax highlighting, auto-formatting, and inline validation. Install the HashiCorp Terraform extension (official — search HashiCorp.terraform). Requires VS Code v1.86+ and Terraform v0.12+. On Windows it also supports Remote - WSL.
  • Basic understanding: Familiarity with Git workflows (branches, MRs), DNS record types (A, CNAME, MX, TXT, alias), and Norton's AWS accounts — Development (hosts dev, QA, staging) and Production.

Setting Up Editor IntelliSense for Terraform

The HashiCorp Terraform extension can give you full IntelliSense — inline provider docs, attribute autocompletion, and instant validation of resource shapes — but only after a one-time terraform init so the AWS provider schema lands on disk where the language server can read it. This is documented officially under Refresh IntelliSense in the extension repository.

Do not run terraform plan or terraform apply locally. The remote state backend, AWS credentials, and KMS keys are configured inside the CI runners only. Local plan/apply will either fail outright or — worse — appear to succeed against incomplete state and produce a misleading diff. terraform init -backend=false is the only Terraform CLI command you should run locally, and only for editor IntelliSense; let the pipeline do everything else.

One-Time Setup

  1. Install the HashiCorp Terraform VS Code extensionmarketplace listing. Full feature reference: hashicorp/vscode-terraform on GitHub.

  2. Install the Terraform CLI if you don't have it already. Follow the official Terraform installation guide for your platform — package managers (brew, choco, apt) and direct downloads are all covered there. Confirm with:

    terraform -version
  3. Initialize the stack without a backend so providers download but no remote state is touched:

    cd accounts/development/Route53
    terraform init -backend=false

    The -backend=false flag is the important part. It tells Terraform to skip the GitLab-hosted state backend entirely — no credentials are needed, no state is locked, and nothing is read or written remotely. Terraform downloads the AWS provider into .terraform/ and that is the schema the VS Code extension reads from.

  4. Reload VS Code (or close and reopen the workspace). Open any .tf file under the stack and IntelliSense should activate — hover over aws_route53_zone for inline documentation, and type aws_route53_record. to see attribute autocomplete. If completions don't appear, run Terraform: Refresh IntelliSense from the Command Palette as described in the official docs.

  5. Repeat for any other stack you actively edit. init -backend=false is per-directory. Run it once in each accounts/{env}/Route53/ (or s3/, rds/, etc.) directory you work in. You only need to re-run it when the AWS provider version changes or when you wipe the .terraform/ directory.


How to Add a New DNS Record

The most common task. The example below walks through adding a CNAME for a new application; the Common Use Cases section below covers other record types and routing policies in detail.

Step 1: Clone the Infrastructure Repository

If you haven't already, clone the repository locally:

git clone git@gitlab.com:wwnorton/ops/infrastructure.git
cd infrastructure

Create a new branch for your changes:

git checkout -b feat/add-my-app-cname

Step 2: Open the Right Records File

Records live in JSON files under each environment's Route53 directory:

Pick the file that matches the zone you are editing — for example, wwnorton-com.json for wwnorton.com, wwnorton-net.json for wwnorton.net, or one of the author-site zones in production. Public and private views of wwnorton.com live in separate files (wwnorton-com-public.json and wwnorton-com-private.json in production); make sure you are editing the right one.

Not sure which zone? A record for an application that should be reachable from the public internet goes in the public zone. A record that only needs to resolve from inside Norton's VPCs (e.g. service-to-service traffic) goes in the corresponding private zone. If both, add to both.

Step 3: Add the Record Entry

Each file holds an array of record objects. To add a CNAME pointing at the controlled subdomain for the production external ALB:

[
// ... existing records ...

{
"name": "myapp",
"type": "CNAME",
"ttl": 300,
"records": ["alb-external-prod.wwnorton.com"]
}
]

That single entry creates myapp.wwnorton.com as a CNAME pointing at the controlled subdomain. The full menu of record patterns (alias, weighted, MX, TXT, etc.) is in Common Use Cases below.

Use controlled subdomains for ALB targets, not the raw ELB hostname. Records that point at alb-external-prod.wwnorton.com (or one of its siblings) inherit the maintenance-mode and failover machinery for free. Records that point straight at k8s-tfexternalprd-...elb.amazonaws.com do not, and have to be updated individually for any redirect. See Understanding Route53 → Controlled Subdomains for the full registry.

Step 4: Submit Your Changes

  1. Commit your changes:

    git add accounts/development/Route53/route53-records/wwnorton-com.json
    git commit -m "feat: Add myapp.wwnorton.com CNAME"
    git push origin feat/add-my-app-cname
  2. Open a Merge Request in GitLab targeting the main branch.

  3. In your MR description, include:

    • What record is being added, deleted, or modified, in which zone, and in which environment.
    • The team and application this record serves.
    • The target the record points at, and why (alias vs CNAME vs A, controlled subdomain vs raw target).
    • Anything special — TTL choice, failover setup, allow_overwrite for a migration, etc.

Step 5: Review, Merge, and Apply

  1. The CI pipeline runs automatically on your MR:
    • Format and validate — catches HCL and JSON syntax errors.
    • Terraform plan — shows what will be created, deleted, or changed; posted as an MR comment.
  2. The Platform team reviews within 24 hours and may request changes.
  3. Once approved and merged to main, a new pipeline runs on the merge commit.
  4. Open the Apply stage on that pipeline and click the manual job for your stack (e.g. accounts-development-Route53 or accounts-production-Route53).
  5. Records appear in AWS once apply succeeds. DNS will propagate within the TTL of the record (typically 60–300 seconds).

What to expect on your MR

The Terraform plan comment shows exactly the resources that will change — usually one or two aws_route53_record entries. Read it carefully; this is the last automated checkpoint before apply.

Terraform plan output showing a single Route53 record being added


Common Use Cases

The patterns below cover the record shapes you are most likely to need. All examples are in JSON; the same shape works for inline records in terraform.tfvars if you prefer that style.

Use Case 1: Standard Records (A, CNAME, MX, TXT)

Risk level: SAFE for new records — READ-MORE if changing an existing record an application depends on.

The most common shape — a CNAME for an application pointing at a controlled subdomain rather than the raw ELB hostname:

{
"name": "myapp",
"type": "CNAME",
"ttl": 300,
"records": ["alb-external-prod.wwnorton.com"]
}

When to use: Every new application served by an EKS-backed ALB. The controlled subdomain is what makes maintenance mode and ALB swaps possible without touching the application record.

Use Case 2: Alias Records (Apex of a Domain, ALB, CloudFront, S3)

Risk level: SAFE for new records — READ-MORE if rerouting traffic on an existing apex.

Alias records are the right choice when the target is an AWS resource and you want one of:

  • The apex of the zone (@), where CNAMEs are illegal
  • Free DNS queries (alias queries are not billed)
  • Health-aware routing without an explicit health check
{
"name": "@",
"type": "A",
"alias": {
"name": "k8s-tfexternalprd-d2265e5869-324837681.us-east-1.elb.amazonaws.com",
"zone_id": "Z35SXDOTRQ7X7K",
"evaluate_target_health": false
}
}

The zone_id is the hosted zone of the AWS resource, not the Route53 zone you are adding the record into. AWS publishes the canonical zone_id for each service and region:

Target typeWhere to find the zone_id
ALB / NLBEC2 console → Load Balancers → the LB's "Hosted zone" field, or aws elbv2 describe-load-balancers
CloudFrontAlways Z2FDTNDATAQYW2 (a single global value)
S3 static websiteDocumented per region; for us-east-1 it's Z3AQBSTGFYJSTF
API Gateway (regional)The output of aws apigateway get-domain-name for the custom domain

Plug in the right zone_id. A wrong zone_id does not produce a syntax error — it produces a record that resolves to nothing, and the failure mode is "the site is down." Double-check the value against AWS documentation or aws ... describe-* output before merging.

Use Case 3: ACM Certificate Validation Records

Risk level: SAFE — these records are inert until ACM consumes them, and they expire silently after the certificate is issued.

When ACM issues a TLS certificate for a domain in your zone, it asks for a CNAME proving you own the domain. The CNAME is given to you in the ACM Console (Certificate → Domains → "Create record in Route53" or copy the values manually). Add it to the right zone file:

{
"name": "_b124124e4a4ddd4bbf125d09236f571e",
"type": "CNAME",
"ttl": 300,
"records": [
"_a128e69edb4f4daa628d1f4ed21192de.nbnhgqgzdr.acm-validations.aws."
]
}

The leading underscore on the name is intentional. ACM polls the record on a 1-minute interval; once it sees the validation, the certificate moves to Issued within minutes. After issuance, you can leave the record in place — ACM uses it again on every renewal.

Each subject alternative name (SAN) needs its own validation CNAME. A certificate covering app.wwnorton.com, *.app.wwnorton.com, and app.wwnorton.net will need three separate validation records, possibly across two zone files.

Use Case 4: Weighted Routing (Gradual Rollouts)

Risk level: READ-MORE — incorrect weights can route real traffic to an unready backend.

Two records with the same name and type, distinguished by set_identifier, with weights summing to whatever number you like (Route53 uses the ratio, not a fixed total):

[
{
"name": "myapp",
"type": "CNAME",
"ttl": 60,
"records": ["alb-external-prod.wwnorton.com"],
"set_identifier": "current",
"weighted_routing_policy": { "weight": 90 }
},
{
"name": "myapp",
"type": "CNAME",
"ttl": 60,
"records": ["alb-iig-prod.wwnorton.com"],
"set_identifier": "candidate",
"weighted_routing_policy": { "weight": 10 }
}
]

Pattern: Drop the candidate weight to 0 to roll back instantly. Bump the candidate weight up over a series of small MRs. Keep TTL low (60s) during the rollout so client-side DNS caches don't lock anyone onto a single answer for long.

Use Case 5: Failover Routing with a Health Check

Risk level: READ-MORE — failover only works if the health check is well-tuned. A check that flaps will flap your DNS.

Failover routing needs (1) a health check declared in terraform.tfvars and (2) two records — primary and secondary — sharing a name and type but distinguished by set_identifier and the failover role:

# accounts/{env}/Route53/terraform.tfvars
health_checks = {
"myapp-primary" = {
type = "HTTPS"
fqdn = "myapp.wwnorton.com"
resource_path = "/healthz"
failure_threshold = 3
request_interval = 30
}
}
[
{
"name": "myapp",
"type": "A",
"set_identifier": "primary",
"alias": {
"name": "alb-external-prod.wwnorton.com",
"zone_id": "Z35SXDOTRQ7X7K",
"evaluate_target_health": true
},
"failover_routing_policy": { "type": "PRIMARY" },
"health_check_id": "REPLACE_WITH_HEALTH_CHECK_ID"
},
{
"name": "myapp",
"type": "A",
"set_identifier": "secondary",
"alias": {
"name": "alb-dr-prod.wwnorton.com",
"zone_id": "Z35SXDOTRQ7X7K",
"evaluate_target_health": true
},
"failover_routing_policy": { "type": "SECONDARY" }
}
]

Wiring the health check ID into the JSON is the trickiest part of this pattern. The IDs are AWS-generated and are not known until the check exists. Two options:

  1. Two-MR rollout — first MR adds the health check (with a placeholder record), wait for apply, copy the ID from the pipeline output, second MR replaces the placeholder with the real ID.
  2. Inline records in tfvars — declare the records inline in terraform.tfvars instead of in JSON, and reference the health check via aws_route53_health_check.health_checks["myapp-primary"].id directly. This avoids the two-MR dance but loses the JSON readability for that specific record set.

For your first failover setup, the two-MR approach is the right call. Switch to inline only when you are comfortable with the module.

Use Case 6: Adding a New Hosted Zone

Risk level: READ-MORE — a new zone has to have its NS records propagated at the registrar before it answers anything publicly.

Adding a new domain (e.g. for a new author site) requires editing terraform.tfvars rather than just a record JSON. Add the zone and create an empty (or near-empty) records file:

# accounts/production/Route53/terraform.tfvars
hosted_zones = {
# ... existing zones ...

"newauthor-com" = {
name = "newauthor.com"
comment = "Hosted zone for the New Author site"
records_file = "route53-records/newauthor-com.json"
tags = {
Environment = "production"
ManagedBy = "Terraform"
Type = "public"
}
}
}
// accounts/production/Route53/route53-records/newauthor-com.json
[
{
"name": "@",
"type": "A",
"alias": {
"name": "d1234abcd.cloudfront.net",
"zone_id": "Z2FDTNDATAQYW2",
"evaluate_target_health": false
}
},
{
"name": "www",
"type": "CNAME",
"ttl": 300,
"records": ["d1234abcd.cloudfront.net"]
}
]

After apply, grab the four name servers from the pipeline's apply output (or from the AWS Console) and update the registrar's NS records to point at them. The zone is live as soon as the registrar's NS propagates — usually within a few minutes, occasionally up to 48 hours for the most aggressive caches.

Private zones don't need registrar updates. They are reachable only from associated VPCs, so AWS routes queries internally without involving the global DNS hierarchy.

Use Case 7: Maintenance Mode

Risk level: READ-MORE — the change ripples to every application that resolves through the affected controlled subdomain.

Maintenance mode redirects every application behind a controlled subdomain to the static maintenance page in a single record edit. The full procedure — including the prepare-in-advance / merge-at-the-window pattern, the file list to edit, and the revert workflow — lives in docs/aws/route53/controlled-subdomains.md in the Infrastructure repository.

The short version:

  1. Prepare the MR ahead of the maintenance window — change the alb-external-prod (and any other applicable) controlled subdomain CNAME target to maintenance.wwnorton.com in all three files: production public, production private, and development.
  2. Get the MR reviewed and approved before the window starts.
  3. At maintenance time, merge and run the manual Apply jobs.
  4. Roll back via either a revert MR (prepared during step 1) or by re-applying the original CNAME values in a follow-up MR.

Background and rationale for the controlled-subdomain pattern: Understanding Route53 → Controlled Subdomains.


Understanding SAFE vs READ-MORE Properties

Each kind of change carries a different level of risk on an existing record. This reference helps you understand the impact before merging.

ChangeRisk LevelNotes
Add a new record (any type)SAFENo existing resolution is affected
Change a ttlSAFEAffects future caching; in-flight cached answers stay until expiry
Change records value on an A/CNAME pointing at a controlled subdomainSAFEControlled subdomains absorb most target swaps
Change records value on a record pointing at a raw ELBREAD-MOREUntil propagation completes, requests may hit either old or new target
Change a name or typeREAD-MOREForces destroy + recreate; brief outage for the old name
Change an alias.zone_id or alias.nameREAD-MORESame as above; verify the new zone_id is correct for the target type
Add a set_identifier to an existing simple recordREAD-MOREForces destroy + recreate; pair with weighted/latency policy thoughtfully
Set allow_overwrite = trueREAD-MORELets Terraform take over a record that exists in AWS but isn't yet in state — only use during a one-off migration MR, then remove
Add a health_check_id to a recordSAFEHealth check has to exist first
Delete a recordREAD-MOREAffected name stops resolving until any client cache expires
Delete a hosted zoneBLOCKEDprevent_destroy rejects the plan; edit the lifecycle block first if intentional

For deeper context on prevent_destroy, the controlled-subdomain pattern, and the pipeline flow, see Understanding Route53 Infrastructure.


Troubleshooting

Error: zone is required for record or Error: invalid alias zone_id

Symptoms:

  • Pipeline plan or apply fails with a Terraform error referencing a missing or invalid alias zone_id.

Common causes:

  • Pasted the Route53 zone ID instead of the AWS resource's hosted zone ID.
  • Used a CloudFront zone_id for an ALB target (or vice versa).

Resolution: Look up the right zone_id for the target type — see the table in Use Case 2. For ALBs, run aws elbv2 describe-load-balancers --names ... and use the CanonicalHostedZoneId field. For CloudFront, the correct value is always Z2FDTNDATAQYW2.

Error: record already exists During Apply

Symptoms:

  • Apply fails with a "Tried to create resource record set, but it already exists" error.

Common cause: A record with the same name and type was created out-of-band in the AWS Console (or by a previous, broken Terraform run) and is not in the current Terraform state.

Resolution: Two options:

  1. Recommended — delete the orphan record in the Console first, then re-run apply. The plan output will show the record being created cleanly.
  2. One-off migration only — set allow_overwrite = true on the record entry, run apply once to take ownership, and then remove allow_overwrite in the next MR. Leaving allow_overwrite = true long-term hides drift, so always remove it after the migration.

Record Doesn't Resolve After Apply

Symptoms:

  • dig myapp.wwnorton.com returns NXDOMAIN or an old answer well past the TTL.

Common causes:

  1. The record was added to the wrong zone (public vs private).
  2. The TTL on the record is correct but the recursive resolver you are testing from cached the old answer (or a NODATA negative answer) before the record existed.
  3. For a brand-new public hosted zone, the registrar's NS records haven't been updated yet.

Resolution:

  1. Verify the record exists in the right zone via the AWS Console (Route53 → Hosted zones → the zone → search for the name).
  2. Test resolution against the authoritative name servers directly: dig @ns-XXXX.awsdns-XX.com myapp.wwnorton.com. Get the NS list from the zone's output.name_servers or the Console. If the authoritative answer is correct, wait for caches to expire.
  3. For new public zones, confirm the registrar's NS list matches the zone's name_servers.

Error: failure threshold or Health Check Reference Errors

Symptoms:

  • Plan fails referencing an unknown health_check_id, or apply fails because the health check doesn't exist yet.

Common cause: Trying to add the record and the health check in the same MR, with the record referencing a hardcoded ID that doesn't yet exist in AWS.

Resolution: Use the two-MR rollout described in Use Case 5. Apply the health check first, copy the resulting ID from the apply output, then merge the second MR with the real ID wired in.


Quick Reference

New Route53 Record Checklist

Pre-Submission

  • Picked the right zone file — public vs private, prod vs dev
  • Pointing at a controlled subdomain (not a raw ELB) where applicable
  • Used an alias record for AWS targets at the apex of a zone
  • Confirmed the alias zone_id matches the target's service and region
  • Set a TTL appropriate for the change cadence (60s for hot records, 300s+ for stable ones)

Submission

  • Changes committed to a feature branch
  • MR created with clear description of what record, in which zone, why, and what it points at
  • Pipeline plan comment shows exactly the record changes you expected — no more, no less

Post-Merge

  • Manual Apply step run on the merge-commit pipeline
  • Resolved against the authoritative name servers (dig @ns-XXXX...) once apply completes
  • If a new public zone: registrar NS records updated to match the zone's name_servers

Support

Known Gaps

  • No OPA policies for Route53 yet: Other resources (S3, RDS, ElastiCache, MemoryDB, MQ) have OPA policy bundles enforcing organization standards on the plan. Route53 does not, so review and prevent_destroy are doing more of the work today. See Understanding Route53 → OPA Policy Guardrails for the rationale and the likely first checks.
  • No tag enforcement on records: Records cannot be tagged (an AWS limitation). Tags exist on hosted zones and health checks only.

When to Contact Platform Team

  • Adding a new public hosted zone (registrar coordination needed)
  • Anything involving DNSSEC (not currently configured)
  • Migrating an existing record from a non-Terraform-managed source — allow_overwrite use cases
  • Health check or failover setups that need cross-account or cross-region coordination
  • Maintenance-mode windows for production
  • Access requests for the Infrastructure repository

How to Get Help

  1. Check this guide and the troubleshooting section first.
  2. Review existing records in the same zone file for reference — most patterns are well-represented.
  3. Reach out in Microsoft Teams using the @platform group tag. This tag works in any public Digital Product Group channel — you don't need to be in a Platform-owned channel to use it, but it will not resolve in private channels. Include in your message:
    • Your MR link
    • The pipeline job URL (if there's an error)
    • What you've already tried — dig output is especially helpful

Internal (Norton)

External (AWS & HashiCorp)


Appendix

Hosted Zone Configuration Reference

Declared in accounts/{environment}/Route53/terraform.tfvars under hosted_zones.

AttributeTypeDefaultWhat It Does
namestringrequiredDomain name of the zone (e.g. wwnorton.com)
commentstringnoneHuman-readable description shown in the Console
force_destroyboolfalseAllows zone destruction even if records exist — prevent_destroy blocks this regardless
delegation_set_idstringnoneReuse a pre-declared delegation set for stable NS records
vpc_associationslist(object)[]List of { vpc_id, vpc_region } — declares the zone as private and attaches it to those VPCs
records_filestringnoneRelative path to a JSON file holding the zone's records
recordslist(object)[]Inline records declared directly in tfvars
tagsmap(string){}Tags applied to the zone resource

Record Configuration Reference

Each record (whether in a JSON file or inline in tfvars) supports:

AttributeTypeDefaultWhat It Does
namestringrequiredThe record name; @ or "" resolves to the zone apex
typestringrequiredDNS record type — A, AAAA, CNAME, MX, TXT, SRV, NS, PTR, etc.
ttlnumber300TTL in seconds (ignored when alias is set)
recordslist(string)[]Record values; ignored when alias is set
aliasobjectnone{ name, zone_id, evaluate_target_health } — alias to an AWS resource
weighted_routing_policyobjectnone{ weight } — requires set_identifier
latency_routing_policyobjectnone{ region } — requires set_identifier
geolocation_routing_policyobjectnone{ continent, country, subdivision } — requires set_identifier
failover_routing_policyobjectnone{ type }PRIMARY or SECONDARY; requires set_identifier
multivalue_answer_routing_policyboolfalseEnable multivalue answer behavior
health_check_idstringnoneHealth check key from health_checks map; resolved at apply time
set_identifierstringnoneRequired for weighted, latency, geolocation, and failover routing
allow_overwriteboolfalseAllow Terraform to take over a record that exists out-of-band — migration only

Health Check Configuration Reference

Declared in terraform.tfvars under health_checks. Map keys become the lookup names; values configure the check.

AttributeTypeDefaultWhat It Does
typestringrequiredHTTP, HTTPS, HTTP_STR_MATCH, HTTPS_STR_MATCH, TCP, CALCULATED, or CLOUDWATCH_METRIC
resource_pathstring"/"Path checked for HTTP/HTTPS types
failure_thresholdnumber3Consecutive failures before marking unhealthy
request_intervalnumber30Seconds between checks (10 or 30)
portnumberAWS defaultPort to check
fqdnstringnoneHostname to check (HTTP/HTTPS)
search_stringstringnoneRequired substring in response (string-match types)
ip_addressstringnoneIP to check directly (instead of resolving fqdn)
cloudwatch_alarm_namestringnoneAlarm to follow (CLOUDWATCH_METRIC type)
cloudwatch_alarm_regionstringnoneAlarm's region
insufficient_data_health_statusstring"Failure"Status when alarm has no data
enable_snibooltrueSNI on HTTPS checks
measure_latencyboolfalsePublish latency CloudWatch metrics
invert_healthcheckboolfalseTreat success as failure (rare)
tagsmap(string){}Tags on the check resource