HTTP to HTTPS Redirection for API Gateway: Why CloudFront Is Required
API Gateway regional endpoints only listen on port 443. HTTP requests to port 80 get ERR_CONNECTION_REFUSED because nothing responds. CloudFront solves this by listening on both ports and redirecting HTTP to HTTPS before the request reaches API Gateway.
Why API Gateway doesn't serve HTTP
API Gateway regional endpoints only accept connections on port 443 (HTTPS). Port 80 is not open — there is no listener. A client connecting to http://api.example.com (port 80) receives ERR_CONNECTION_REFUSED because nothing answers on that port.
This is by design. API Gateway manages TLS termination at the edge and has no facility for plain HTTP traffic. The same is true for API Gateway's default execute-api URL and for custom domain endpoints.
The HTTP redirect problem and CloudFront's role
ConceptAWS API Gateway / CloudFrontHTTP-to-HTTPS redirection requires something to listen on port 80 and return a 301/302 redirect response. API Gateway won't do this. CloudFront listens on both port 80 and 443. Its Viewer Protocol Policy can be set to redirect HTTP requests to HTTPS before forwarding to the origin.
Prerequisites
- HTTP vs HTTPS
- TCP ports
- CloudFront distributions
- API Gateway custom domains
Key Points
- API Gateway (both REST and HTTP API) only listens on port 443. Port 80 is closed.
- ERR_CONNECTION_REFUSED on port 80 is not a configuration error — it's the expected behavior.
- CloudFront distributions listen on both ports and handle the HTTP→HTTPS redirect in the Viewer Protocol Policy.
- Users typing 'example.com' in browsers default to HTTP first; without a redirect they get a connection error.
The solution: CloudFront in front of API Gateway
Place a CloudFront distribution between the client and API Gateway. CloudFront handles port 80, issues the redirect, and forwards HTTPS traffic to API Gateway over port 443.
Client (HTTP port 80) → CloudFront → 301 redirect to HTTPS → client retries HTTPS
Client (HTTPS port 443) → CloudFront → forwards to API Gateway (HTTPS)
In Terraform:
resource "aws_cloudfront_distribution" "api" {
origin {
domain_name = aws_apigatewayv2_domain_name.api.domain_name_configuration[0].target_domain_name
origin_id = "api-gateway"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only" # CloudFront→API GW always HTTPS
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "api-gateway"
viewer_protocol_policy = "redirect-to-https" # HTTP clients get 301 to HTTPS
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = true
headers = ["Authorization", "Origin", "Accept"]
cookies {
forward = "all"
}
}
min_ttl = 0
default_ttl = 0 # Don't cache API responses by default
max_ttl = 0
}
aliases = ["api.example.com"]
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.api.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
enabled = true
}
viewer_protocol_policy = "redirect-to-https" is the key setting. CloudFront returns HTTP 301 to HTTPS for any HTTP request, then processes the HTTPS request normally.
The origin_protocol_policy = "https-only" ensures CloudFront never connects to API Gateway over HTTP — traffic is encrypted on both the client→CloudFront and CloudFront→API Gateway legs.
Route 53 configuration
Point your custom domain to the CloudFront distribution, not directly to API Gateway:
resource "aws_route53_record" "api" {
zone_id = data.aws_route53_zone.main.zone_id
name = "api.example.com"
type = "A"
alias {
name = aws_cloudfront_distribution.api.domain_name
zone_id = aws_cloudfront_distribution.api.hosted_zone_id
evaluate_target_health = false
}
}
With this setup, traffic flows: DNS → CloudFront → API Gateway. Clients connecting via HTTP get redirected before reaching API Gateway.
💡HSTS: preventing future HTTP requests
After adding the HTTP→HTTPS redirect, consider adding HSTS (HTTP Strict Transport Security) to tell browsers to never attempt HTTP connections to your domain in the future.
Add HSTS via a CloudFront Response Headers Policy:
resource "aws_cloudfront_response_headers_policy" "security" {
name = "api-security-headers"
security_headers_config {
strict_transport_security {
access_control_max_age_sec = 31536000 # 1 year
include_subdomains = true
preload = true
override = true
}
}
}
Attach it to the CloudFront behavior:
default_cache_behavior {
response_headers_policy_id = aws_cloudfront_response_headers_policy.security.id
# ... other settings
}
With HSTS in place and max-age=31536000, browsers cache the HTTPS-only requirement for one year. The next visit, even if the user types http://, the browser upgrades to HTTPS before making any network request — eliminating the redirect round-trip entirely.
Warning: adding HSTS with a long max-age is hard to undo. If you need to serve HTTP for any reason within the max-age period, browsers that cached the HSTS header will refuse HTTP connections. Test with a short max-age first.
Viewer Protocol Policy options
CloudFront's viewer_protocol_policy has three options:
allow-all: accept both HTTP and HTTPS from clients. No redirect. Use only if you have a specific reason to accept HTTP.redirect-to-https: return HTTP 301 to HTTPS for HTTP requests. HTTPS requests pass through. The standard choice for public APIs.https-only: reject HTTP requests entirely (HTTP 403). No redirect. More strict than redirect — clients must already be using HTTPS.
For APIs, redirect-to-https is the standard. https-only is appropriate for internal APIs where you control all clients and can guarantee HTTPS usage.
After adding CloudFront in front of API Gateway with viewer_protocol_policy = 'redirect-to-https', a mobile app that hardcodes 'http://api.example.com' starts failing. The app team says they're getting a redirect but the subsequent HTTPS request fails with a 403. What is the likely cause?
mediumThe mobile app was previously connecting directly to API Gateway. CloudFront is now in between. The app follows the 301 redirect to HTTPS. The HTTPS connection to CloudFront succeeds. The 403 comes from API Gateway, not CloudFront.
ACloudFront is not forwarding the Authorization header to API Gateway
Correct!CloudFront does not forward headers to the origin by default — they must be explicitly listed in the cache behavior's forwarded_values or origin request policy. If the mobile app sends Authorization headers and CloudFront strips them, API Gateway receives requests without credentials and returns 403. Fix: add Authorization to the forwarded headers in the CloudFront cache behavior.BAPI Gateway rejects requests that were previously HTTP
Incorrect.API Gateway doesn't know or care whether the original client request was HTTP. By the time the request reaches API Gateway, it's already HTTPS from CloudFront.CThe ACM certificate is in the wrong region
Incorrect.An ACM certificate issue would prevent the TLS handshake from completing, not produce a 403. A 403 from API Gateway is an authorization failure.DThe mobile app should use POST instead of GET after following the redirect
Incorrect.HTTP 301 redirects preserve the request method in modern clients. This is not a method issue.
Hint:Think about what headers the mobile app sends and whether CloudFront forwards all of them to the origin.