Skip to content
GitHubXDiscordRSS

Health Check

Monitor origin server availability with Cloudflare Health Checks for automatic traffic routing and high availability.

Stop guessing if your origins are up. Cloudflare Health Checks ping your servers and report back. Pair them with Load Balancers to auto-route traffic away from unhealthy origins.

Get a basic health check running:

import { HealthCheck } from "alchemy/cloudflare";
const healthCheck = await HealthCheck("api-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "api.example.com",
name: "api-server-check"
});

This creates a health check that:

  • Performs HTTP checks every 60 seconds (default)
  • Marks the origin unhealthy after 1 consecutive failure (default)
  • Marks the origin healthy after 1 consecutive success (default)

Dial in your monitoring protocol; timing, response & validation:

import { HealthCheck } from "alchemy/cloudflare";
const healthCheck = await HealthCheck("backend-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353", // Zone ID or Zone resource
address: "backend.example.com", // Origin server hostname or IP
name: "backend-server-check", // Health check identifier
type: "HTTPS", // Protocol: "HTTP", "HTTPS", or "TCP"
// Timing configuration
interval: 30, // Check every 30 seconds
timeout: 10, // Timeout after 10 seconds
retries: 3, // Retry 3 times on timeout
// Threshold configuration
consecutiveFails: 2, // Mark unhealthy after 2 failures
consecutiveSuccesses: 2, // Mark healthy after 2 successes
// HTTP/HTTPS specific configuration
httpConfig: {
path: "/health", // Health check endpoint
method: "GET", // HTTP method: "GET" or "HEAD"
expectedCodes: ["200", "201"], // Expected status codes
expectedBody: "OK", // Expected response body substring
followRedirects: true, // Follow 3xx redirects
allowInsecure: false, // Validate SSL certificates
port: 443, // Port (defaults: 80 for HTTP, 443 for HTTPS)
header: { // Custom headers
"Host": ["backend.example.com"],
"X-Health-Check": ["true"]
}
},
// Monitoring configuration
checkRegions: ["WNAM", "ENAM", "WEU"], // Regions to check from
suspended: false, // Enable/disable checks
description: "Backend server health monitoring"
});

Monitor web servers and APIs with HTTP or HTTPS health checks:

const webHealthCheck = await HealthCheck("web-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "web.example.com",
name: "web-server-check",
type: "HTTP",
httpConfig: {
path: "/",
expectedCodes: ["200"]
}
});
const apiHealthCheck = await HealthCheck("api-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "api.example.com",
name: "api-endpoint-check",
type: "HTTPS",
httpConfig: {
path: "/api/health",
method: "GET",
expectedCodes: ["200", "201"],
expectedBody: "healthy",
header: {
"Host": ["api.example.com"],
"User-Agent": ["Cloudflare-Health-Check"],
"X-API-Key": [alchemy.secret.env.HEALTH_CHECK_API_KEY]
},
port: 443,
allowInsecure: false
}
});

Use ranges to match multiple status codes:

const flexibleHealthCheck = await HealthCheck("flexible-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "app.example.com",
name: "app-health-check",
httpConfig: {
path: "/health",
expectedCodes: ["2xx", "3xx"] // Accept any 2xx or 3xx response
}
});

For databases, mail servers, or anything that speaks TCP:

const databaseHealthCheck = await HealthCheck("db-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "db.example.com",
name: "database-connection-check",
type: "TCP",
tcpConfig: {
port: 5432, // PostgreSQL port
method: "connection_established"
},
interval: 60,
timeout: 5,
retries: 2
});

Common TCP ports:

  • PostgreSQL: 5432
  • MySQL: 3306
  • Redis: 6379
  • MongoDB: 27017
  • SMTP: 25, 587, 465
  • SSH: 22

Configure which regions run health checks for geographic distribution:

const globalHealthCheck = await HealthCheck("global-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "global.example.com",
name: "global-origin-check",
checkRegions: [
"WNAM", // Western North America
"ENAM", // Eastern North America
"WEU", // Western Europe
"EEU", // Eastern Europe
"SEAS", // South East Asia
"OC" // Oceania
]
});

Available regions:

  • WNAM - Western North America
  • ENAM - Eastern North America
  • WEU - Western Europe
  • EEU - Eastern Europe
  • NSAM - Northern South America
  • SSAM - Southern South America
  • OC - Oceania
  • ME - Middle East
  • NAF - Northern Africa
  • SAF - Southern Africa
  • IN - India
  • SEAS - South East Asia
  • NEAS - North East Asia
  • ALL_REGIONS - All available regions

Fine-tune health check timing and failure thresholds:

const sensitiveHealthCheck = await HealthCheck("sensitive-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "critical.example.com",
name: "critical-service-check",
// Check frequently for fast detection
interval: 15, // Check every 15 seconds
timeout: 3, // 3 second timeout
retries: 5, // Retry 5 times on timeout
// Conservative thresholds to avoid false positives
consecutiveFails: 3, // 3 failures before marking unhealthy
consecutiveSuccesses: 3, // 3 successes before marking healthy
httpConfig: {
path: "/health",
expectedCodes: ["200"]
}
});

Timing guidelines:

  • High availability services: Use shorter intervals (15-30s) with higher thresholds (2-3)
  • Normal services: Use default intervals (60s) with default thresholds (1-2)
  • Low priority services: Use longer intervals (120s+) with lower thresholds (1)

Calculation example: With interval: 30, consecutiveFails: 3:

  • Time to detect failure: 90 seconds (3 × 30s)
  • Time to recover: 90 seconds (3 × 30s)

Use secrets for sensitive header values:

const secureHealthCheck = await HealthCheck("secure-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "api.example.com",
name: "secure-api-check",
httpConfig: {
path: "/internal/health",
header: {
"Host": ["api.example.com"],
"Authorization": [alchemy.secret.env.HEALTH_CHECK_TOKEN],
"X-API-Key": [alchemy.secret.env.API_KEY]
}
}
});

Already have health checks? Adopt them by name instead of failing:

const existingHealthCheck = await HealthCheck("existing-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "existing.example.com",
name: "existing-health-check",
adopt: true // Adopt if health check with this name exists
});

Pause checks during maintenance without deleting:

const suspendedHealthCheck = await HealthCheck("maintenance-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "maintenance.example.com",
name: "maintenance-server-check",
suspended: true // Don't send health checks
});

When to suspend:

  • During planned maintenance
  • When origin is intentionally offline
  • When debugging health check configuration
  • When origin can’t handle health check load

Real power: automatic failover when origins go down.

import { HealthCheck, LoadBalancer, Pool } from "alchemy/cloudflare";
// Create health checks for each origin
const primaryHealthCheck = await HealthCheck("primary-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "primary.example.com",
name: "primary-origin-check",
httpConfig: {
path: "/health",
expectedCodes: ["200"]
}
});
const secondaryHealthCheck = await HealthCheck("secondary-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "secondary.example.com",
name: "secondary-origin-check",
httpConfig: {
path: "/health",
expectedCodes: ["200"]
}
});
// Create a pool with origins and health checks
const pool = await Pool("main-pool", {
name: "main-origin-pool",
origins: [
{
name: "primary",
address: "primary.example.com",
enabled: true
},
{
name: "secondary",
address: "secondary.example.com",
enabled: true
}
],
monitor: primaryHealthCheck // Attach health check to pool
});
// Create load balancer
const loadBalancer = await LoadBalancer("main-lb", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
name: "app.example.com",
defaultPools: [pool]
});

Access health check status programmatically:

const healthCheck = await HealthCheck("monitored-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "app.example.com",
name: "app-health-check"
});
// Health check status is available after creation
console.log(healthCheck.status); // "unknown" | "healthy" | "unhealthy" | "suspended"
console.log(healthCheck.failureReason); // Reason if unhealthy
console.log(healthCheck.createdOn); // Creation timestamp
console.log(healthCheck.modifiedOn); // Last modification timestamp

Status values:

  • unknown - Health check hasn’t run yet
  • healthy - Origin is responding correctly
  • unhealthy - Origin is not responding or failing checks
  • suspended - Health checks are suspended

Check all your geographic origins:

const regions = [
{ region: "us-west", address: "us-west.example.com" },
{ region: "us-east", address: "us-east.example.com" },
{ region: "eu-west", address: "eu-west.example.com" },
{ region: "ap-southeast", address: "ap-southeast.example.com" }
];
const healthChecks = await Promise.all(
regions.map(({ region, address }) =>
HealthCheck(`${region}-health`, {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address,
name: `${region}-origin-check`,
httpConfig: {
path: "/health",
expectedCodes: ["200"],
header: {
"X-Region": [region]
}
}
})
)
);

Check more than “is it up”, validate the whole stack:

const comprehensiveHealthCheck = await HealthCheck("comprehensive-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "api.example.com",
name: "comprehensive-check",
httpConfig: {
path: "/health/comprehensive",
method: "GET",
expectedCodes: ["200"],
expectedBody: "all_systems_operational", // Custom validation
header: {
"X-Health-Check-Version": ["v2"]
}
},
interval: 30,
timeout: 10,
consecutiveFails: 2,
consecutiveSuccesses: 2
});

Your health endpoint should validate:

  • Database connectivity
  • External service availability
  • Disk space
  • Memory usage
  • Critical background jobs

Fast to kill, slow to restore - avoid thundering herd:

const cautiousHealthCheck = await HealthCheck("cautious-health", {
zone: "023e105f4ecef8ad9ca31a8372d0c353",
address: "recovering.example.com",
name: "recovering-origin-check",
// Quick to mark unhealthy
consecutiveFails: 1,
// Slow to mark healthy (wait for stability)
consecutiveSuccesses: 5,
interval: 15,
httpConfig: {
path: "/health",
expectedCodes: ["200"]
}
});

This pattern:

  • ✅ Quickly removes unhealthy origins from rotation (1 failure)
  • ✅ Waits for sustained health before restoration (5 successes)
  • ✅ Reduces risk of cascading failures
  • ✅ Protects recovering origins from traffic spikes

Origin is up, but health check says it’s down:

Common causes:

  1. Incorrect path or port:
// ❌ Wrong path
httpConfig: {
path: "/healthcheck" // Origin expects "/health"
}
// ✅ Correct path
httpConfig: {
path: "/health"
}
  1. Missing or incorrect headers:
// ❌ Missing Host header
httpConfig: {
path: "/health"
}
// ✅ Include Host header
httpConfig: {
path: "/health",
header: {
"Host": ["api.example.com"]
}
}
  1. Firewall blocking health checks:
  • Ensure your firewall allows Cloudflare IP ranges
  • Check your origin’s access logs for health check requests
  • Verify security groups allow traffic from Cloudflare
  1. SSL certificate validation:
// ❌ Self-signed certificate with validation enabled
type: "HTTPS",
httpConfig: {
allowInsecure: false
}
// ✅ Allow self-signed for development
type: "HTTPS",
httpConfig: {
allowInsecure: true // Only for development!
}

Health bouncing between healthy/unhealthy?

Fix: Require sustained changes:

// ❌ Too sensitive
consecutiveFails: 1,
consecutiveSuccesses: 1,
// ✅ More stable
consecutiveFails: 3,
consecutiveSuccesses: 3,

Health checks timing out but origin is responding?

Try these:

  1. Increase timeout:
timeout: 10, // Increase from default 5 seconds
  1. Optimize health endpoint:
  • Return cached responses
  • Avoid expensive database queries
  • Use in-memory checks
  • Return early on first successful check
  1. Increase retries:
retries: 5, // Retry more times before marking unhealthy

Getting “expected body not found”?

Debug it:

// Temporarily remove expectedBody to see what's being returned
httpConfig: {
path: "/health",
expectedCodes: ["200"],
// expectedBody: "OK" // Comment out to debug
}

Check your origin’s health endpoint response format:

  • Ensure it returns plain text if using simple string matching
  • Check for leading/trailing whitespace
  • Verify the response isn’t JSON-encoded
  • Use a substring that definitely appears in the response