This check flags API Gateway custom domains that still accept TLS 1.0, an obsolete protocol with known weaknesses. Switch the domain's security policy to TLS_1_2 to enforce modern encryption on incoming connections.
When you put a custom domain in front of an API Gateway endpoint, AWS terminates TLS at the edge using a security policy you choose. That policy decides which TLS versions and cipher suites clients are allowed to negotiate. If the domain is set to the older policy, it will happily accept TLS 1.0 handshakes, which means a client from 2010 can connect to your modern API over a protocol that the rest of the industry retired years ago.
The apigw_tlspolicy check looks at every custom domain name in your API Gateway configuration and reports any that permit TLS 1.0.
What this check detects
API Gateway custom domain names have a securityPolicy attribute with two possible values:
- TLS_1_0 — accepts TLS 1.0, 1.1, and 1.2 connections.
- TLS_1_2 — accepts only TLS 1.2 (and TLS 1.3 where supported).
Despite the name, TLS_1_0 is not a "TLS 1.0 only" setting. It is a floor. It allows the weakest protocol AWS still supports on that domain. This check fires when a custom domain has its security policy set to TLS_1_0, leaving the door open to legacy clients negotiating downward.
Note: This setting only applies to custom domain names. The default execute-api endpoints (like abc123.execute-api.us-east-1.amazonaws.com) are managed by AWS and already enforce TLS 1.2 as a minimum, so they are not affected by this check.
Why it matters
TLS 1.0 was published in 1999 and formally deprecated by the IETF in 2021 (RFC 8996). The PCI Security Standards Council told merchants to stop using it back in 2018. It carries real, well-documented weaknesses:
- BEAST — exploits TLS 1.0's CBC cipher implementation to decrypt portions of an encrypted session.
- POODLE-style downgrade attacks — an active attacker on the network path can force a client and server to negotiate the weakest mutually supported protocol.
- Weak cipher suites — TLS 1.0 supports ciphers and key exchanges that no longer meet modern standards.
The practical risk: if an attacker can position themselves between a client and your API (public Wi-Fi, a compromised router, a hostile ISP), the presence of TLS 1.0 gives them a downgrade target. Even if 99% of your real traffic uses TLS 1.2, leaving 1.0 enabled means a man-in-the-middle can try to coerce a session down to the weaker protocol.
There is also a compliance angle. PCI DSS, HIPAA security guidance, FedRAMP, and most internal security baselines now require TLS 1.2 or higher. An API endpoint accepting TLS 1.0 is a frequent finding in audits, and it tends to block compliance certification until fixed.
Warning: Before flipping to TLS 1.2, confirm nothing legitimate still depends on TLS 1.0 or 1.1. Old mobile SDKs, embedded IoT devices, and ancient Java 6/7 clients may not support TLS 1.2 out of the box. Check your access logs for the negotiated protocol before you make the change in production.
How to fix it
The fix is to set the custom domain's security policy to TLS_1_2. There is no separate "TLS 1.3" policy value on API Gateway. Selecting TLS_1_2 enforces 1.2 as the minimum and AWS handles newer protocols transparently.
Option 1: AWS CLI
First, check the current policy for a domain:
aws apigateway get-domain-name \
--domain-name api.example.com \
--query 'securityPolicy' \
--output text
If it returns TLS_1_0, update it with a patch operation:
aws apigateway update-domain-name \
--domain-name api.example.com \
--patch-operations op=replace,path=/securityPolicy,value=TLS_1_2
For an HTTP API or WebSocket API custom domain managed under API Gateway v2, use the v2 command instead:
aws apigatewayv2 update-domain-name \
--domain-name api.example.com \
--domain-name-configurations SecurityPolicy=TLS_1_2
Warning: Updating the security policy on a REST API custom domain triggers a CloudFront distribution update behind the scenes and can take up to 40 minutes to fully propagate. The domain stays available during this time, but plan changes outside peak hours.
Option 2: AWS Console
- Open the API Gateway console.
- Select Custom domain names from the left navigation.
- Choose the affected domain.
- Click Edit on the domain details.
- Under Minimum TLS version, select TLS 1.2.
- Save the change.
Option 3: Terraform
For a REST API domain:
resource "aws_api_gateway_domain_name" "api" {
domain_name = "api.example.com"
regional_certificate_arn = aws_acm_certificate.api.arn
security_policy = "TLS_1_2"
endpoint_configuration {
types = ["REGIONAL"]
}
}
For an HTTP or WebSocket API domain (API Gateway v2):
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"
}
}
Option 4: CloudFormation
{
"Type": "AWS::ApiGateway::DomainName",
"Properties": {
"DomainName": "api.example.com",
"RegionalCertificateArn": { "Ref": "ApiCertificate" },
"SecurityPolicy": "TLS_1_2",
"EndpointConfiguration": { "Types": ["REGIONAL"] }
}
}
Tip: If you manage dozens of domains, script the audit and remediation together. List every domain with its policy, then patch only the offenders:
for domain in $(aws apigateway get-domain-names \
--query 'items[?securityPolicy==`TLS_1_0`].domainName' \
--output text); do
echo "Updating $domain to TLS_1_2"
aws apigateway update-domain-name \
--domain-name "$domain" \
--patch-operations op=replace,path=/securityPolicy,value=TLS_1_2
done
How to verify the fix
After the change propagates, confirm the policy and test the handshake. Re-run the CLI query to check the stored value:
aws apigateway get-domain-name \
--domain-name api.example.com \
--query 'securityPolicy' \
--output text
# Expect: TLS_1_2
Then prove that a TLS 1.0 handshake is actually rejected at the wire:
# This should now FAIL with a handshake error
openssl s_client -connect api.example.com:443 -tls1
# This should succeed
openssl s_client -connect api.example.com:443 -tls1_2
If the -tls1 attempt still completes, the change has not finished propagating yet. Wait and retry.
How to prevent it from happening again
Fixing one domain is easy. Keeping every domain compliant as your account grows is the real work. Push enforcement left so the weak policy never lands in production.
Block it in IaC review with policy-as-code
If you use Terraform, a Checkov or OPA/Conftest rule can fail the pipeline when someone sets TLS_1_0 or omits the policy. A simple Conftest policy against a Terraform plan:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_api_gateway_domain_name"
resource.change.after.security_policy != "TLS_1_2"
msg := sprintf("API Gateway domain '%s' must use TLS_1_2", [resource.address])
}
Wire that into your CI pipeline so the merge is blocked before any apply runs:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json
Set a sane default in your modules
If you wrap API Gateway domains in a shared Terraform module, hardcode security_policy = "TLS_1_2" as the default and do not expose it as an overridable variable. The weak option simply stops being reachable.
Catch drift at runtime
IaC gates do not help for resources created by hand or by an older pipeline. Continuous monitoring with Lensix runs the apigw_tlspolicy check across your accounts and surfaces any domain that drifts back to TLS 1.0, including ones provisioned outside your Terraform state.
Tip: AWS Config ships a managed rule, api-gw-ssl-enabled, plus you can author a custom rule keyed on securityPolicy. Pair detective controls in Config with the preventive IaC gate above so nothing slips through either layer.
Best practices
- Default to TLS 1.2 everywhere. Not just API Gateway. Apply the same minimum to CloudFront distributions, Application Load Balancers, and ElastiCache in-transit settings.
- Audit access logs before tightening. Enable access logging and inspect the negotiated TLS version field so you know exactly which clients would break before you cut off TLS 1.0.
- Give legacy clients a deadline, not an exception. If a partner genuinely needs TLS 1.0, treat it as a tracked, time-boxed risk with an owner and an end date, not a permanent carve-out.
- Standardize through modules. Centralize domain creation so the security policy is set once and inherited everywhere, rather than copy-pasted per service.
- Re-check after every migration. Account migrations, region expansions, and disaster-recovery rebuilds are common moments where old defaults sneak back in. Make the TLS policy check part of your go-live checklist.
TLS 1.0 on an API endpoint is a small misconfiguration with an outsized footprint. It rarely breaks anything visibly, so it lingers, which is exactly why it shows up in audits and breach post-mortems. Flip the policy to TLS 1.2, gate it in your pipeline, and monitor for drift, and this one stays fixed.

