Guide · AWS Networking · Global Accelerator
AWS Global Accelerator Health Checks for MCP Server Endpoints
Global Accelerator detects endpoint failures and reroutes traffic without DNS TTL delays — but only if its health checks are configured to catch application-layer failures, not just TCP reachability. An MCP server returning 500s passes a TCP health check and fails silently from the client's perspective. This guide covers GA health check mechanics (check interval, failure threshold, failover timing), the difference between GA health checks and the NLB health checks running below them, how to use HTTP health checks to surface application failures, and how to drive safe blue/green deployments through traffic dial plus health check coordination. For GA overview see Global Accelerator for MCP Servers; for routing and traffic dials see Global Accelerator Routing; for multi-region active-active see Multi-Region MCP Servers with GA.
TL;DR
GA health checks run from multiple AWS edge locations, not a single probe host. Default interval: 30s. Set it to 10s for faster detection. 3 consecutive failures = unhealthy; 3 consecutive successes = healthy again. Total failover window at 10s interval: ~30s from first failure. Use HTTP health checks pointing to /health instead of TCP — TCP checks pass even when your MCP application is returning errors. Layer this with NLB health checks for defense in depth.
GA health check architecture
Unlike Route 53 health checks (which probe from 8–15 fixed IP ranges) or NLB health checks (which probe from NLB nodes in specific AZs), GA health checks are issued from multiple AWS edge locations that are geographically distributed. This means your MCP server endpoint is checked from POPs around the world, not from a single source. The implication: an endpoint that's unreachable from one region but reachable from another will appear healthy in GA's aggregated view.
GA aggregates health check results across its probing locations using a majority-vote mechanism. An endpoint transitions to unhealthy when a majority of probing locations report failure for 3 consecutive intervals. This design prevents transient single-location failures from triggering failover — you need a real, widespread outage to move traffic.
Health check configuration lives at the endpoint group level:
listener.addEndpointGroup('UsEast1Group', {
region: 'us-east-1',
trafficDialPercentage: 100,
// Check type: TCP (port open) or HTTP/HTTPS (200 response)
// TCP: fastest, lowest overhead, but blind to application errors
// HTTP: checks actual endpoint response — catches 500s, hung processes
healthCheckProtocol: ga.HealthCheckProtocol.HTTPS,
// Port for GA health check. Can differ from listener port.
// Use a dedicated /health endpoint on port 8080 if your main
// listener is TCP-only (NLB pass-through) and can't return HTTP.
healthCheckPort: 8080,
// Path for HTTP/HTTPS checks. GA expects 200 response.
// healthCheckPath is a CfnEndpointGroup L1 property — use L1 override:
// (cfnEndpointGroup as ga.CfnEndpointGroup).healthCheckPath = '/health';
// Interval: 10s or 30s. Use 10s for production MCP endpoints.
healthCheckInterval: ga.HealthCheckInterval.HEALTH_CHECK_INTERVAL_10,
// Threshold: consecutive checks before state change (healthy → unhealthy
// or unhealthy → healthy). Default: 3.
healthCheckThreshold: 3,
});
At 10s interval with threshold 3: first failure detected at second 0, second failure at second 10, third failure at second 20 → endpoint marked unhealthy. GA re-routes remaining connections to the next healthy endpoint group. Time from first failure to traffic re-route: ~30s.
TCP vs HTTP health check: why it matters for MCP
MCP servers expose an SSE stream on a TCP port. If you use TCP health checks (GA checks that the port is open), GA reports the endpoint as healthy as long as the TCP handshake succeeds — even if the MCP server process is:
- Responding to new SSE connections with HTTP 500 (process crash or out-of-memory state)
- Accepting connections but hanging indefinitely without sending the initial SSE event
- Returning malformed SSE responses that MCP clients reject
- Running but with a broken database connection (tool calls fail, but the port is open)
HTTP health checks fix this by requiring the endpoint to return a 200 response to a specific path. Your MCP server should expose a /health endpoint that checks:
// Express-style /health endpoint for GA HTTP health check
app.get('/health', async (req, res) => {
// Check database connectivity
const dbOk = await checkDatabase(); // SELECT 1 or ping
// Check tool registry loaded
const toolsOk = toolRegistry.size > 0;
// Check SSE event loop not stalled
const loopOk = Date.now() - lastEventLoopTick < 5000;
if (dbOk && toolsOk && loopOk) {
res.status(200).json({ status: 'ok', uptime: process.uptime() });
} else {
res.status(503).json({
status: 'unhealthy',
db: dbOk,
tools: toolsOk,
loop: loopOk,
});
}
});
GA's HTTP health check sends a GET request to /health and expects a 200. A 503 marks the endpoint unhealthy. The check runs in the background — it does not consume SSE connection capacity.
Sharp edge: if your main listener port (443) is behind an NLB with TLS pass-through, GA cannot perform HTTP health checks on port 443 (it can't terminate TLS). Solution: expose a plaintext /health endpoint on a separate port (8080) that the GA health check uses, while keeping the main SSE stream on 443 with TLS. Configure the NLB to only accept port 8080 health check traffic on plaintext, separate from the TLS listener.
Failover timing: end-to-end walkthrough
Here's what happens when an MCP server behind GA fails, at 10s health check interval:
| T (seconds) | Event |
|---|---|
| 0 | MCP server process crashes; port 8080 /health returns 503 (or times out) |
| 0–10 | GA issues first health check; receives 503 or timeout. Failure count: 1 of 3. |
| 10–20 | GA issues second health check; still failing. Failure count: 2 of 3. |
| 20–30 | GA issues third health check; still failing. Failure count: 3 of 3. Endpoint marked unhealthy. |
| ~30 | GA starts routing new connections to the next healthy endpoint group (second region or second endpoint in same group). |
| 30+ | Existing SSE connections to the failed endpoint are dropped (TCP RST from NLB side as ECS tasks drain). MCP clients reconnect; GA routes them to healthy endpoint. |
| 30–300 | MCP server auto-restarts (ECS service replacement); NLB health check passes; NLB marks new task healthy. ECS task registration into NLB target group: ~20–60s depending on deregistrationDelay. |
| 300–360 | GA health checks new NLB (with healthy task); 3 consecutive successes at 10s interval = 30s to mark healthy. GA re-includes endpoint in routing pool. |
Total mean time to recovery (MTTR) for a single-region failure: ~35s for new connections to be routed away from the failed endpoint, ~5–6 minutes for the failed endpoint to rejoin the pool (ECS replacement + NLB registration + GA re-check). For sub-5-minute recovery, pre-warm a second endpoint or region at 0% traffic dial so GA can fail over immediately rather than waiting for ECS to replace the failed task.
Layering GA health checks with NLB health checks
GA and NLB health checks operate independently. GA checks the NLB (or ALB) endpoint; NLB checks the ECS tasks behind it. This creates two independent detection layers with different sensitivities:
| Failure type | GA detects? | NLB detects? | Outcome |
|---|---|---|---|
| ECS task crash (all tasks) | Yes (NLB returns no healthy targets → GA HTTP check fails) | Yes (task health check fails; deregistered) | GA fails over after 30s; NLB stops sending to crashed tasks within 20s |
| ECS task crash (one of three tasks) | No (other tasks still pass NLB health check; NLB endpoint healthy) | Yes (crashed task deregistered; other tasks still serve) | Reduced capacity but no GA failover; NLB handles gracefully |
| MCP app returning 500s (process alive) | Yes (if using HTTP GA health check pointing to /health) | Yes (if NLB health check also checks /health) | Both detect; NLB drains tasks; GA fails over |
| Region-level AWS outage (AZ failure) | Yes (NLB health checks fail across AZs) | Partially (AZ-specific tasks fail) | GA fails over to second region within 30s if configured |
| NLB misconfiguration (wrong port) | Yes (GA HTTP check to NLB fails immediately) | N/A (NLB config error) | GA failover triggers; fix misconfiguration |
The blind spot: if your MCP server is healthy from GA's perspective (HTTP 200 at /health) but returning errors for specific tool calls (a broken tool handler), neither GA nor NLB will detect this. AliveMCP addresses this at the monitoring layer — we perform live MCP protocol probes (not just TCP/HTTP health checks) against registered endpoints, issuing actual tools/list and sample tool invocations to verify end-to-end correctness.
Blue/green deployments with GA traffic dial
The safest blue/green deployment pattern for MCP servers uses GA traffic dial to control when traffic moves, combined with health checks to prevent routing to an unhealthy green region:
// Step 1: Create green endpoint group at 0% traffic dial
// (Green region is warmed up, GA health checks it, but gets no live traffic)
const greenGroup = listener.addEndpointGroup('GreenGroup', {
region: 'us-east-1',
trafficDialPercentage: 0, // receives zero traffic
healthCheckProtocol: ga.HealthCheckProtocol.HTTPS,
healthCheckPort: 8080,
healthCheckInterval: ga.HealthCheckInterval.HEALTH_CHECK_INTERVAL_10,
healthCheckThreshold: 3,
endpoints: [
new gaEndpoints.NetworkLoadBalancerEndpoint(greenNlb, { weight: 128 }),
],
});
// Step 2: Deploy new MCP version to green ECS service
// Step 3: Wait for GA to show HealthyEndpointCount == 1 for GreenGroup
// Step 4: Run smoke tests against green endpoint directly (bypass GA)
// Step 5: Ramp green to 10% (update trafficDialPercentage via AWS SDK or console)
// Step 6: Monitor error rate for 5 minutes
// Step 7: Ramp green to 100%; set blue to 0%
// Step 8: Delete blue endpoint group after 1 hour (all SSE sessions reconnected)
Critical: before setting blue to 0%, wait for all existing SSE sessions on blue to naturally expire or reconnect. SSE connections can be hours-long for agentic workflows. Setting blue dial to 0% stops new connections but does not terminate existing ones — existing SSE sessions on blue continue until the MCP client or server closes them. Only after you've drained blue (seen ActiveFlowCount approach 0 on the blue NLB) should you decommission the blue ECS service.
How AliveMCP monitors GA-fronted MCP endpoints
When AliveMCP detects that an MCP endpoint is served behind a Global Accelerator (via static anycast IP + low traceroute hop count into AWS backbone), we probe it with two parallel check strategies:
- GA endpoint probe: connects to the GA anycast IP on port 443, performs full MCP SSE handshake, measures first-byte latency and checks
tools/listresponse. This is what an MCP client in the same region sees. - Origin endpoint probe: bypasses GA by connecting directly to the NLB's DNS name (derived from the MCP server's metadata or our registry crawl). This probe measures origin health independently of GA routing.
If origin is unhealthy but GA endpoint is healthy, we flag the server as "GA-masked partial failure" — GA is currently routing around the failure, but recovery depends on the failed origin coming back. We notify the server author (via webhook if they have the Author tier) because users will experience disruption when GA's failover capacity is exhausted.
If GA endpoint is unhealthy but origin is healthy, we flag "GA routing failure" — the routing layer is broken, not the MCP server itself. This is typically a GA configuration issue (wrong health check port, misconfigured endpoint group) that the author can fix without touching their MCP server code.