API Gateway Custom Domains: Why You Can't CNAME Directly to the Execute-API URL
API Gateway's default execute-api URL carries AWS's TLS certificate, not yours. Custom domains work through a CloudFront distribution where API Gateway enforces SNI-based routing — which is why accessing the CloudFront URL directly returns 403 or 404.
Why not just CNAME to the execute-api URL
Every API Gateway deployment gets a default URL:
https://{api-id}.execute-api.{region}.amazonaws.com/{stage}
You can technically create a CNAME in Route 53 pointing to this URL. It will resolve. But when a client connects, the TLS handshake will use AWS's certificate for *.execute-api.{region}.amazonaws.com, not your certificate for api.example.com. Browsers and API clients will reject this with a certificate mismatch error.
Beyond the TLS problem: the default URL exposes your API ID, is not portable across regions, and couples your client-facing URL to your API Gateway deployment ID.
How API Gateway custom domains work
ConceptAWS API GatewayWhen you create a custom domain in API Gateway, AWS provisions a CloudFront distribution behind the scenes. Your domain's TLS certificate (from ACM) is attached to that distribution. API Gateway uses SNI (Server Name Indication) to route requests from that distribution to the correct API based on the domain name.
Prerequisites
- TLS/SNI
- CloudFront distributions
- ACM certificates
- Route 53 alias records
Key Points
- Custom domain setup provisions a CloudFront distribution managed by API Gateway.
- Your ACM certificate must be in us-east-1 for REST APIs and HTTP APIs (CloudFront requirement).
- Route 53 creates an alias (A/AAAA) record pointing to the CloudFront distribution domain.
- Accessing the CloudFront distribution URL directly returns 403/404 — it requires the correct Host header to route.
The setup: ACM certificate + custom domain + Route 53
Step 1: Certificate in ACM (must be us-east-1 for edge-optimized APIs):
aws acm request-certificate \
--domain-name api.example.com \
--validation-method DNS \
--region us-east-1
Add the DNS validation record to Route 53 and wait for the certificate to reach ISSUED status.
Step 2: Create the custom domain in API Gateway:
aws apigatewayv2 create-domain-name \
--domain-name api.example.com \
--domain-name-configurations \
CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc-123,EndpointType=REGIONAL
This returns a ApiGatewayDomainName — the CloudFront or regional distribution hostname AWS created.
Step 3: Map the custom domain to your API:
aws apigatewayv2 create-api-mapping \
--domain-name api.example.com \
--api-id abc123def456 \
--stage '$default'
The API mapping is what API Gateway uses internally to route requests for api.example.com to the correct API ID and stage.
Step 4: Create the Route 53 record:
aws route53 change-resource-record-sets \
--hosted-zone-id ZXXXXXXXXXXXXX \
--change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "api.example.com",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": "d1234567890.execute-api.us-east-1.amazonaws.com",
"EvaluateTargetHealth": false
}
}
}]
}'
Use an alias record, not a CNAME. Alias records work at the zone apex (example.com), don't incur Route 53 query charges, and route correctly to AWS regional endpoints.
In Terraform:
resource "aws_apigatewayv2_domain_name" "api" {
domain_name = "api.example.com"
domain_name_configuration {
certificate_arn = aws_acm_certificate.api.arn
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
}
resource "aws_apigatewayv2_api_mapping" "api" {
api_id = aws_apigatewayv2_api.main.id
domain_name = aws_apigatewayv2_domain_name.api.id
stage = aws_apigatewayv2_stage.default.id
}
resource "aws_route53_record" "api" {
zone_id = data.aws_route53_zone.main.zone_id
name = "api.example.com"
type = "A"
alias {
name = aws_apigatewayv2_domain_name.api.domain_name_configuration[0].target_domain_name
zone_id = aws_apigatewayv2_domain_name.api.domain_name_configuration[0].hosted_zone_id
evaluate_target_health = false
}
}
Why the CloudFront URL returns 403 or 404
After setting up a custom domain, you might try accessing the CloudFront distribution URL directly (the d1234567890.execute-api.us-east-1.amazonaws.com value) out of curiosity or for debugging. It returns 403 (Forbidden) or 404 (Not Found).
This is SNI-based routing. When a request arrives at the CloudFront distribution, API Gateway looks at the Host header to determine which API to route it to. The distribution is shared infrastructure — multiple APIs from multiple customers use it. The Host header identifies which API mapping applies.
When you access the distribution URL directly:
Host: d1234567890.execute-api.us-east-1.amazonaws.com- API Gateway has no mapping for this host — it only has a mapping for
api.example.com - Result: 403 or 404
This is expected behavior and a security property: you cannot bypass custom domain configuration by guessing the underlying distribution URL.
📝Regional vs edge-optimized endpoints
API Gateway offers two endpoint types for custom domains:
Edge-optimized (default for REST APIs): traffic routes through the CloudFront edge network globally. ACM certificate must be in us-east-1. Adds CloudFront latency optimization for geographically distributed clients.
Regional: traffic routes directly to the API Gateway regional endpoint. ACM certificate must be in the same region as the API. Better for clients within the same region (lower latency). Required if you want to put your own CloudFront distribution or WAF in front.
# Regional endpoint — certificate in same region as API
domain_name_configuration {
certificate_arn = aws_acm_certificate.api.arn # must be in var.region
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
If you need WAF, custom caching, or geographic restrictions, use a regional endpoint and put CloudFront in front manually. With edge-optimized, you don't control the CloudFront distribution and can't attach WAF to it.
Base path mappings: multiple APIs on one domain
A single custom domain can serve multiple APIs using base path mappings:
api.example.com/v1 → API v1 (stage: prod)
api.example.com/v2 → API v2 (stage: prod)
api.example.com/admin → Admin API (stage: prod)
aws apigatewayv2 create-api-mapping \
--domain-name api.example.com \
--api-id v1-api-id \
--stage prod \
--api-mapping-key v1
aws apigatewayv2 create-api-mapping \
--domain-name api.example.com \
--api-id v2-api-id \
--stage prod \
--api-mapping-key v2
The api-mapping-key becomes the path prefix. Requests to api.example.com/v1/users route to v1-api-id with path /users.
You create a custom domain 'api.example.com' in API Gateway and configure an ACM certificate for it. The certificate is in us-west-2. You select an edge-optimized endpoint type. The custom domain creation fails. Why?
easyThe API Gateway REST API is deployed in us-west-2. The ACM certificate was issued in us-west-2. Edge-optimized endpoint type is selected.
AThe API Gateway API must be in us-east-1 to use edge-optimized endpoints
Incorrect.The API itself can be in any region. The constraint is on the ACM certificate for edge-optimized endpoints, not the API's region.BEdge-optimized endpoints require the ACM certificate to be in us-east-1, because the underlying CloudFront distribution is globally managed from us-east-1
Correct!CloudFront distributions require ACM certificates to be in us-east-1, regardless of where the origin (API Gateway) is located. The edge-optimized endpoint in API Gateway provisions a CloudFront distribution, inheriting this requirement. Fix: request the ACM certificate in us-east-1, or switch to a regional endpoint type (which uses the certificate from the API's own region).CAPI Gateway does not support custom domains in us-west-2
Incorrect.API Gateway supports custom domains in all commercial AWS regions.DThe certificate must be a wildcard certificate to work with API Gateway custom domains
Incorrect.Wildcard certificates work, but exact-match certificates (for 'api.example.com') work too. Certificate type is not the issue here.
Hint:Think about where CloudFront certificates must live, and what edge-optimized endpoints use under the hood.