Guide · AWS ACM · ALB HTTPS

ACM Certificates with AWS ALB for MCP Servers — HTTPS Termination and Redirects

An Application Load Balancer (ALB) with an ACM certificate is the standard HTTPS termination pattern for MCP servers running on ECS, EC2, or Lambda: the ALB handles TLS, the MCP server container receives plain HTTP on port 80 or 8080 inside the VPC, and ACM auto-renews the certificate without any action on your part. This guide covers attaching an ACM certificate to an ALB HTTPS listener, configuring an HTTP-to-HTTPS redirect on port 80, handling multiple domain names with SNI, and rotating certificates with zero downtime. For certificate provisioning, see the ACM overview. For CloudFront TLS termination, see ACM with CloudFront.

TL;DR

Create an ALB with port 443 HTTPS listener, attach the ACM certificate (must be in same region as ALB), set up a port 80 redirect rule, configure target group with HTTP protocol to backend containers, enable deletion protection on the ALB. Certificate auto-renewal is transparent — ALB picks up the renewed certificate automatically without any listener update.

Create an ALB with HTTPS listener

The ALB must be in the same region as the ACM certificate. The HTTPS listener specifies the default certificate and can include up to 25 additional certificates for SNI-based multi-domain serving.

# Step 1: Create the ALB (internet-facing for public MCP server)
ALB_ARN=$(aws elbv2 create-load-balancer \
  --name mcp-server-alb \
  --subnets subnet-public-1a subnet-public-1b subnet-public-1c \
  --security-groups sg-alb-id \
  --scheme internet-facing \
  --type application \
  --ip-address-type ipv4 \
  --tags Key=Service,Value=mcp-server \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text)

# Enable deletion protection to prevent accidental teardown in production
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn $ALB_ARN \
  --attributes Key=deletion_protection.enabled,Value=true

# Step 2: Create a target group pointing to MCP server containers on HTTP
TG_ARN=$(aws elbv2 create-target-group \
  --name mcp-server-tg \
  --protocol HTTP \
  --port 8080 \
  --vpc-id vpc-id \
  --target-type ip \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3 \
  --query 'TargetGroups[0].TargetGroupArn' --output text)

# Step 3: Create HTTPS listener with ACM certificate
aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTPS \
  --port 443 \
  --certificates CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc-123 \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN

# Step 4: Create HTTP listener with redirect to HTTPS
aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTP \
  --port 80 \
  --default-actions '[{
    "Type": "redirect",
    "RedirectConfig": {
      "Protocol": "HTTPS",
      "Port": "443",
      "StatusCode": "HTTP_301"
    }
  }]'

The security policy ELBSecurityPolicy-TLS13-1-2-2021-06 enforces TLS 1.2 minimum and supports TLS 1.3 where the client supports it. It excludes TLS 1.0 and 1.1 (deprecated in RFC 8996) and disables cipher suites with known weaknesses. For MCP servers where all clients are modern (AI agents, developer SDKs), this policy provides strong security without compatibility concerns. The older ELBSecurityPolicy-2016-08 still supports TLS 1.0 — do not use it for new deployments.

TLS security policies compared

PolicyTLS versionsTLS 1.3Use case
ELBSecurityPolicy-TLS13-1-3-2021-061.3 onlyYesHighest security; incompatible with clients that don't support TLS 1.3 (some legacy Java versions, older .NET)
ELBSecurityPolicy-TLS13-1-2-2021-061.2, 1.3YesRecommended for new MCP server deployments — modern security, broad compatibility
ELBSecurityPolicy-FS-1-2-Res-2020-101.2 onlyNoWhen TLS 1.3 is not allowed by compliance policy; requires forward secrecy cipher suites
ELBSecurityPolicy-2016-081.0, 1.1, 1.2NoLegacy compatibility only — do not use for new deployments
# Check which security policy is on your listener
aws elbv2 describe-listeners \
  --load-balancer-arn $ALB_ARN \
  --query 'Listeners[?Protocol==`HTTPS`].{Port:Port,Policy:SslPolicy,Cert:Certificates[0].CertificateArn}'

# Update security policy on existing HTTPS listener
LISTENER_ARN=$(aws elbv2 describe-listeners \
  --load-balancer-arn $ALB_ARN \
  --query 'Listeners[?Protocol==`HTTPS`].ListenerArn' --output text)

aws elbv2 modify-listener \
  --listener-arn $LISTENER_ARN \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06
# Takes effect immediately — no downtime; in-flight connections use old policy until closed

Adding multiple certificates (SNI)

ALB HTTPS listeners support Server Name Indication (SNI): the client sends the hostname in the TLS ClientHello, and the ALB selects the matching certificate. A single ALB can serve TLS for up to 25 domains using a single HTTPS listener — useful when your MCP server serves api.mcp.example.com, mcp-staging.example.com, and mcp-v2.example.com from the same load balancer.

# Add a second certificate to the HTTPS listener (SNI)
LISTENER_ARN=$(aws elbv2 describe-listeners \
  --load-balancer-arn $ALB_ARN \
  --query 'Listeners[?Protocol==`HTTPS`].ListenerArn' --output text)

aws elbv2 add-listener-certificates \
  --listener-arn $LISTENER_ARN \
  --certificates CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/xyz-456

# List all certificates on the listener
aws elbv2 describe-listener-certificates \
  --listener-arn $LISTENER_ARN \
  --query 'Certificates[].{Arn:CertificateArn,Default:IsDefault}'

# Remove a certificate (does not delete the ACM certificate — only detaches it from the listener)
aws elbv2 remove-listener-certificates \
  --listener-arn $LISTENER_ARN \
  --certificates CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/old-cert

The default certificate (set during listener creation) is used when the client's SNI hostname does not match any of the additional certificates. For MCP servers, set the default certificate to the primary domain (api.mcp.example.com) and add additional certificates for aliases. If a wildcard certificate covers all subdomains (*.mcp.example.com), a single certificate is sufficient — no SNI configuration needed.

Certificate rotation with zero downtime

ACM auto-renewal is transparent to the ALB — when ACM issues a renewed certificate, the ALB picks it up automatically without any listener modification. This means zero-downtime certificate rotation is the default behavior for ACM certificates on ALBs, as long as DNS validation succeeds.

Manual certificate rotation (e.g., replacing a wildcard with individual domain certificates, or migrating from RSA to ECDSA) follows this pattern:

# Zero-downtime manual certificate rotation:

# Step 1: Request a new certificate (new key algorithm, updated SANs, etc.)
NEW_CERT_ARN=$(aws acm request-certificate \
  --domain-name api.mcp.example.com \
  --subject-alternative-names "*.mcp.example.com" \
  --validation-method DNS \
  --key-algorithm EC_prime256v1 \
  --query 'CertificateArn' --output text)

# Step 2: Wait for the new certificate to validate and be issued
aws acm wait certificate-validated --certificate-arn $NEW_CERT_ARN

# Step 3: Add the new certificate as an additional SNI certificate (not yet default)
aws elbv2 add-listener-certificates \
  --listener-arn $LISTENER_ARN \
  --certificates CertificateArn=$NEW_CERT_ARN
# Both old and new certificates are now active; clients receive the new cert if SNI matches

# Step 4: Update the listener's default certificate to the new one
aws elbv2 modify-listener \
  --listener-arn $LISTENER_ARN \
  --certificates CertificateArn=$NEW_CERT_ARN
# New cert is now the default; both still active for ongoing connections

# Step 5: Remove the old certificate (after confirming clients use new cert)
aws elbv2 remove-listener-certificates \
  --listener-arn $LISTENER_ARN \
  --certificates CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/old-cert

# Step 6: Schedule the old certificate for deletion (optional — ACM certs are free to keep)
aws acm delete-certificate \
  --certificate-arn arn:aws:acm:us-east-1:123456789012:certificate/old-cert

ALB health checks and MCP server availability

The ALB health check must succeed for the target group to forward traffic to your MCP server containers. Configure the health check path to return HTTP 200 without requiring authentication — most MCP servers expose a /health or /status endpoint for this purpose.

# Configure health check for MCP server target group
aws elbv2 modify-target-group \
  --target-group-arn $TG_ARN \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-port traffic-port \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3 \
  --matcher HttpCode=200

# Check current health of all registered targets
aws elbv2 describe-target-health \
  --target-group-arn $TG_ARN \
  --query 'TargetHealthDescriptions[].{Target:Target.Id,Port:Target.Port,State:TargetHealth.State,Reason:TargetHealth.Reason}'

# Common TargetHealth.State values:
#   healthy   — passing health checks; receives traffic
#   unhealthy — failing health checks; removed from rotation; Reason field shows why
#   draining  — registered but currently draining in-flight connections (during deregistration)
#   initial   — just registered; not yet checked
#   unused    — no load balancer rule routes to this target group

For SSE-based MCP servers (Server-Sent Events), configure the ALB idle timeout to be longer than the maximum expected SSE session duration. The default ALB idle timeout is 60 seconds — an SSE connection that sends no data for 60 seconds will be terminated by the ALB mid-session. For MCP tools that run long operations, set idle timeout to at least 300 seconds:

# Increase ALB idle timeout for SSE MCP servers
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn $ALB_ARN \
  --attributes Key=idle_timeout.timeout_seconds,Value=300

# For WebSocket MCP transport (ws:// or wss://)
# WebSocket connections require the ALB to detect the Upgrade header and keep the
# connection open indefinitely — this is automatic when target group protocol is HTTP
# (ALB detects WebSocket upgrades automatically; no special configuration needed)

Failure modes reference

SymptomCauseFix
503 Service Unavailable from ALBAll targets in target group unhealthy; security group on targets blocking ALB health check; health check path returns non-200Check describe-target-health; verify security group allows inbound TCP from ALB security group on health check port; test health endpoint directly from within VPC
Certificate mismatch TLS error (SSL_ERROR_BAD_CERT_DOMAIN)Certificate SANs don't include the hostname the client is connecting to; SNI hostname not matching any attached certificateCheck certificate SANs with describe-certificate SubjectAlternativeNames; add correct domain to SANs (requires requesting a new certificate — SANs cannot be modified)
SSE connections dropping after 60 secondsALB default idle timeout is 60 seconds; SSE connections without data are treated as idleIncrease idle_timeout.timeout_seconds to 300+; alternatively, send periodic keepalive comments (: keepalive\n\n) in the SSE stream
HTTP requests on port 80 not redirecting to HTTPSPort 80 listener not created; or listener has a forward action instead of redirect actionCreate port 80 listener with Type=redirect action pointing to HTTPS 443 with HTTP_301
Certificate attached to ALB but not being renewedDNS validation CNAME record deleted from DNS; domain expiredCheck describe-certificate RenewalSummary; re-add DNS validation CNAME; ALB will pick up the renewed certificate automatically once renewal succeeds
certificate not yet valid or certificate has expired from ALBACM renewal failed and ALB is still serving the expired certificate; or a newly requested certificate was attached before validation completedFor expired: check renewal status and fix DNS validation; for pending: wait for ISSUED status before attaching