The CloudFront minimum TLS version is set by each distribution’s security policy, stored in ViewerCertificate.MinimumProtocolVersion. TLSv1.2_2018 and newer allow only TLS 1.2 and 1.3; TLSv1, TLSv1_2016 and TLSv1.1_2016 still accept older versions. Distributions on the default *.cloudfront.net certificate always use TLSv1.
This example is for engineers who need to prove that no CloudFront distribution in an account still accepts TLS 1.0 or 1.1, usually for an audit, a customer security questionnaire or a PCI-style checklist. The script uses AWS SDK for JavaScript v3 to read every distribution’s CloudFront minimum TLS version, certificate type, viewer protocol policy and the TLS settings CloudFront uses towards custom origins. It’s report-only: it makes no changes.
It complements the scripts that set up and debug the same distributions: point www to CloudFront with a Route 53 alias record and troubleshoot a Route 53 domain not serving CloudFront.
Which CloudFront security policies allow old TLS versions?
A security policy sets two things for viewer connections: the minimum SSL/TLS protocol and the ciphers CloudFront may use. From the CloudFront developer guide’s protocol table:
| Security policy | Protocols accepted | Verdict |
|---|---|---|
SSLv3 |
SSLv3, TLS 1.0, 1.1, 1.2, 1.3 | Flag |
TLSv1 |
TLS 1.0, 1.1, 1.2, 1.3 | Flag |
TLSv1_2016 |
TLS 1.0, 1.1, 1.2, 1.3 | Flag |
TLSv1.1_2016 |
TLS 1.1, 1.2, 1.3 | Flag |
TLSv1.2_2018, TLSv1.2_2019, TLSv1.2_2021 |
TLS 1.2, 1.3 | OK; newer dates drop older ciphers |
TLSv1.2_2025 |
TLS 1.2, 1.3 | OK; also drops the CHACHA20 ciphers |
TLSv1.3_2025 |
TLS 1.3 only | OK; strictest |
Which policies you can choose depends on the certificate settings:
- Default certificate (
CloudFrontDefaultCertificate: true): CloudFront sets the policy toTLSv1automatically. You can’t raise it without a custom domain and certificate. - Custom certificate with SNI (
SSLSupportMethod: "sni-only"): every policy fromTLSv1toTLSv1.3_2025is available. - Custom certificate with dedicated IPs (
"vip", the Legacy Clients Support option): onlyTLSv1andSSLv3are selectable. Newer policies require an AWS Support case, or switching to SNI, which also lowers the bill.
Why flag TLS 1.0 and 1.1 at all? The IETF formally deprecated both in RFC 8996, Deprecating TLS 1.0 and TLS 1.1 (March 2021), and NIST SP 800-52 Rev. 2 requires government TLS servers to support TLS 1.2 and to support TLS 1.3 by 1 January 2024.
What else does the script check?
- Viewer protocol policy per cache behavior.
allow-allmeans plain HTTP is served, which makes the TLS minimum moot for that path.redirect-to-httpsorhttps-onlyis what you want. - Custom origin TLS. For custom origins,
OriginProtocolPolicy(http-only,match-viewer,https-only) andOriginSslProtocols(SSLv3,TLSv1,TLSv1.1,TLSv1.2) control the connection from CloudFront to your server. A strict viewer policy doesn’t help if the hop to an EC2 or load balancer origin runs over HTTP. - Certificate type. ACM, IAM or the default certificate, with the SSL support method.
S3 and VPC origins are listed by type only; the script has no TLS settings to read for them. For an S3 origin, transport security is set in the bucket policy instead, which the script to find S3 buckets that don’t require HTTPS checks. TLS protects requests in transit but doesn’t filter them; the script to find CloudFront distributions without AWS WAF checks which distributions have a web ACL.
Prerequisites and IAM permissions
- Node.js 20 or later, npm and
tsx, plus the@aws-sdk/client-cloudfrontpackage. - A profile allowed to call
cloudfront:ListDistributions. That’s the only action the script uses. CloudFront is a global service, so there’s no Region loop.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListDistributions",
"Effect": "Allow",
"Action": "cloudfront:ListDistributions",
"Resource": "*"
}
]
}
If you extend the script, the IAM policy generator for TypeScript code drafts the new policy, and the guide to review a generated IAM policy for least privilege checks it.
The script to check the CloudFront minimum TLS version
// check-cloudfront-tls.ts
// Report only: lists every CloudFront distribution with its viewer security policy
// (minimum TLS version), certificate type, viewer protocol policy and the TLS settings
// of custom origins, and flags anything that still allows TLS 1.0 or 1.1. Changes nothing.
// Usage: npx tsx check-cloudfront-tls.ts [--only-findings]
import { CloudFrontClient, paginateListDistributions, type DistributionSummary } from "@aws-sdk/client-cloudfront";
const onlyFindings = process.argv.includes("--only-findings");
// Security policies whose minimum protocol is below TLS 1.2 (viewer side).
const OLD_VIEWER_POLICIES = new Set(["SSLv3", "TLSv1", "TLSv1_2016", "TLSv1.1_2016"]);
// Origin protocols below TLS 1.2 (custom origins only).
const OLD_ORIGIN_PROTOCOLS = new Set(["SSLv3", "TLSv1", "TLSv1.1"]);
type Row = {
id: string;
domain: string;
cert: string;
securityPolicy: string;
viewerHttp: string;
origins: string;
findings: string;
};
function check(d: DistributionSummary): Row {
const vc = d.ViewerCertificate;
const findings: string[] = [];
// The default *.cloudfront.net certificate always gets the TLSv1 policy.
const defaultCert = vc?.CloudFrontDefaultCertificate === true;
const policy = defaultCert ? "TLSv1 (default cert)" : (vc?.MinimumProtocolVersion ?? "?");
if (defaultCert) findings.push("default cert: TLS 1.0 allowed");
else if (vc?.MinimumProtocolVersion && OLD_VIEWER_POLICIES.has(vc.MinimumProtocolVersion)) {
findings.push(`viewer policy ${vc.MinimumProtocolVersion} allows < TLS 1.2`);
}
if (!defaultCert && vc?.SSLSupportMethod === "vip") findings.push("dedicated IP (vip): only TLSv1/SSLv3 selectable");
// Every cache behavior has its own viewer protocol policy.
const behaviors = [d.DefaultCacheBehavior, ...(d.CacheBehaviors?.Items ?? [])];
const viewerPolicies = [...new Set(behaviors.map((b) => b?.ViewerProtocolPolicy ?? "?"))];
if (viewerPolicies.includes("allow-all")) findings.push("a behavior allows plain HTTP");
const origins = (d.Origins?.Items ?? []).map((o) => {
const c = o.CustomOriginConfig;
if (!c) return `${o.Id}: ${o.S3OriginConfig ? "S3" : o.VpcOriginConfig ? "VPC" : "other"}`;
const protocols = c.OriginSslProtocols?.Items ?? [];
const old = protocols.filter((p) => OLD_ORIGIN_PROTOCOLS.has(p));
if (c.OriginProtocolPolicy === "http-only") findings.push(`origin ${o.Id} is http-only`);
else if (old.length > 0) findings.push(`origin ${o.Id} allows ${old.join("/")}`);
return `${o.Id}: ${c.OriginProtocolPolicy} [${protocols.join(",")}]`;
});
return {
id: d.Id ?? "?",
domain: d.Aliases?.Items?.[0] ?? d.DomainName ?? "?",
cert: defaultCert ? "cloudfront.net" : `${vc?.CertificateSource ?? "?"}/${vc?.SSLSupportMethod ?? "?"}`,
securityPolicy: policy,
viewerHttp: viewerPolicies.join(","),
origins: origins.join("; "),
findings: findings.join("; ") || "ok",
};
}
async function main(): Promise<void> {
// CloudFront is a global service; the SDK signs its API requests for us-east-1.
const cf = new CloudFrontClient({ region: "us-east-1", maxAttempts: 5 });
const rows: Row[] = [];
for await (const page of paginateListDistributions({ client: cf }, {})) {
for (const d of page.DistributionList?.Items ?? []) rows.push(check(d));
}
const flagged = rows.filter((r) => r.findings !== "ok");
console.table(onlyFindings ? flagged : rows);
console.log(`${rows.length} distributions, ${flagged.length} with findings.`);
if (flagged.length > 0) process.exitCode = 2; // non-zero so a CI job can fail on findings
}
main().catch((err: unknown) => {
console.error(err);
process.exit(1);
});
ListDistributions returns summaries that already include the viewer certificate, cache behaviors and origins, so the report needs one paginated call instead of a GetDistributionConfig per distribution. The exit code is 2 when anything is flagged, so you can run the script on a schedule in CI and fail the job on a regression.
How do you run it?
npm install @aws-sdk/client-cloudfront
npm install --save-dev tsx typescript
# Every distribution
AWS_PROFILE=security-audit npx tsx check-cloudfront-tls.ts
# Only the ones with findings
AWS_PROFILE=security-audit npx tsx check-cloudfront-tls.ts --only-findings
Sample output
┌─────────┬──────────────────┬─────────────────────────────────┬────────────────────────┬───────────────────────────────────────────────────────────────┐
│ (index) │ id │ domain │ securityPolicy │ findings │
├─────────┼──────────────────┼─────────────────────────────────┼────────────────────────┼───────────────────────────────────────────────────────────────┤
│ 0 │ 'E1A2B3C4D5E6F7' │ 'www.example.com' │ 'TLSv1.2_2021' │ 'ok' │
│ 1 │ 'E2B3C4D5E6F7A8' │ 'api.example.com' │ 'TLSv1_2016' │ 'viewer policy TLSv1_2016 allows < TLS 1.2' │
│ 2 │ 'E3C4D5E6F7A8B9' │ 'd111111abcdef8.cloudfront.net' │ 'TLSv1 (default cert)' │ 'default cert: TLS 1.0 allowed; a behavior allows plain HTTP' │
│ 3 │ 'E4D5E6F7A8B9C0' │ 'shop.example.com' │ 'TLSv1.2_2021' │ 'origin legacy-alb allows TLSv1/TLSv1.1' │
└─────────┴──────────────────┴─────────────────────────────────┴────────────────────────┴───────────────────────────────────────────────────────────────┘
4 distributions, 3 with findings.
IDs and domains are placeholders, and the table is trimmed for width. Row 3 is the one people miss: the viewer side is strict, but CloudFront may still talk to the origin load balancer over TLS 1.0. The script to find load balancers without HTTPS redirects or with old TLS policies checks that side’s listeners.
How do you fix a flagged distribution?
- Check who still uses old TLSCloudFront standard logs record the protocol and cipher of each viewer request. Look at a week of traffic before you cut anyone off.
- Move off the default certificateIf the distribution serves
*.cloudfront.net, add an alternate domain name and an ACM certificate (issued in us-east-1 for CloudFront), then choose a policy. Update DNS with the Route 53 alias steps linked above. ACM renews that certificate only while its validation CNAME stays in DNS, which the script to find expiring ACM certificates checks. - Raise the security policy
TLSv1.2_2021is a common target;TLSv1.3_2025if every client supports TLS 1.3. In the API that’sGetDistributionConfig, editMinimumProtocolVersion, thenUpdateDistributionwith the returnedETagasIfMatch. - Fix origins and behaviorsSet
OriginSslProtocolstoTLSv1.2only, and changeallow-allbehaviors toredirect-to-https. - Rerun the reportChanges take time to deploy; rerun once the distribution status is back to
Deployed.
A side benefit: HTTP/2 needs viewers that support TLS 1.2 and SNI, and HTTP/3 needs TLS 1.3, so modern policies don’t cost you protocol features.
Troubleshooting
AccessDeniedonListDistributions. The role lackscloudfront:ListDistributions, or an SCP blocks CloudFront. The steps to troubleshoot an AWS IAM access denied error find the layer.- A distribution you changed still shows the old policy. Configuration changes take time to propagate; wait for
Deployedand rerun. - You can’t pick
TLSv1.2_2021in the console. The distribution uses dedicated IP (vip) support. Switch to SNI, or open a Support case.
Ask ChatWithCloud instead
With a read-only profile, ask ChatWithCloud “Which CloudFront distributions allow TLS versions below 1.2?”. It writes AWS SDK for JavaScript v2 code, runs it on your machine and explains the result; how ChatWithCloud turns questions into AWS SDK calls covers the loop. It can misread a policy name, so check anything it flags against the table above. For a wider review, the guide to analyze your AWS security posture with an AI CLI lists related questions, and the scripts to find public and private S3 buckets and find security groups open to the internet cover the next two items on most checklists.
Frequently asked questions
How do I set the minimum TLS version in CloudFront?
Use a custom certificate with SNI, then choose a security policy such as TLSv1.2_2021 in the distribution settings, or set ViewerCertificate.MinimumProtocolVersion through UpdateDistribution.
Why is my CloudFront distribution stuck on TLSv1?
It uses the default *.cloudfront.net certificate, which always gets TLSv1, or it uses dedicated IP support, which only offers TLSv1 and SSLv3.
Does TLSv1.2_2021 support TLS 1.3?
Yes. Every TLS 1.2 policy also accepts TLS 1.3. Only TLSv1.3_2025 rejects TLS 1.2.
How do I check CloudFront TLS settings with the AWS CLI?
Run aws cloudfront list-distributions --query "DistributionList.Items[].[Id,ViewerCertificate.MinimumProtocolVersion]".
Related guides
Ask your AWS account in plain English
Your first 15 runs are free, with no OpenAI key needed.
npx chatwithcloud