Shipping production-grade TLS with AWS Certificate Manager
AWS Certificate Manager (ACM) issues and renews SSL/TLS certificates for free across AWS services. This notebook distils the guidance shared by AWS Networking Hero Colm MacCárthaigh, Ben Kehoe, and the ACM product team, so you can provision certificates programmatically, validate ownership securely, and deliver trusted HTTPS with FsCDK.
Quick start patterns
Each example references best practices from the AWS Networking Blog and re:Invent NET sessions—adapt them to match your compliance and automation requirements.
#r "../src/bin/Release/net8.0/publish/Amazon.JSII.Runtime.dll"
#r "../src/bin/Release/net8.0/publish/Constructs.dll"
#r "../src/bin/Release/net8.0/publish/Amazon.CDK.Lib.dll"
#r "../src/bin/Release/net8.0/publish/FsCDK.dll"
open FsCDK
open Amazon.CDK
open Amazon.CDK.AWS.CertificateManager
open Amazon.CDK.AWS.Route53
open Amazon.CDK.AWS.CloudFront
Basic Certificate with DNS Validation
Create a certificate for a domain with automatic DNS validation.
stack "BasicCertificate" {
certificate "MyCert" {
domainName "example.com"
subjectAlternativeName "www.example.com"
}
}
Wildcard Certificate
Secure all subdomains with a wildcard certificate.
stack "WildcardCert" {
certificate "WildcardCert" {
domainName "example.com"
subjectAlternativeName "*.example.com"
certificateName "example-wildcard"
}
}
Certificate with Route53 DNS Validation
Use a Route53 hosted zone for automated DNS validation.
stack "Route53Cert" {
let myHostedZone = hostedZone "example.com" { comment "Production domain" }
certificate "Route53Cert" {
domainName "example.com"
subjectAlternativeName "*.example.com"
dnsValidation myHostedZone
}
}
CloudFront Certificate (us-east-1)
CloudFront requires certificates in us-east-1. Use DnsValidatedCertificate for cross-region deployment.
stack "CloudFrontCert" {
let! myHostedZone = hostedZone "example.com" { comment "Production domain" }
dnsValidatedCertificate "CFCert" {
domainName "cdn.example.com"
hostedZone myHostedZone
region "us-east-1" // Required for CloudFront
}
}
Multi-Domain Certificate
Include multiple domains in a single certificate.
stack "MultiDomainCert" {
certificate "MultiDomainCert" {
domainName "example.com"
subjectAlternativeName "www.example.com"
subjectAlternativeName "api.example.com"
subjectAlternativeName "admin.example.com"
}
}
Email validation fallback
Only use email validation when DNS automation is unavailable. Follow the contingency plan outlined in the AWS Certificate Manager documentation—track approval emails, secure shared inboxes, and transition to DNS validation as soon as possible.
stack "EmailValidatedCert" {
certificate "EmailCert" {
domainName "example.com"
emailValidation
}
}
Complete HTTPS setup with CloudFront
Combine S3 static hosting, ACM-issued certificates, and CloudFront to deliver globally cached HTTPS content. This mirrors the reference architecture from the AWS Modern Applications Blog article “Deploying secure static websites with CloudFront and ACM.”
stack "HTTPSWebsite" {
// S3 bucket for website
let websiteBucket =
bucket "WebsiteBucket" {
versioned false
websiteIndexDocument "index.html"
websiteErrorDocument "error.html"
blockPublicAccess Amazon.CDK.AWS.S3.BlockPublicAccess.BLOCK_ALL
}
// Certificate for custom domain
let cert =
certificate "SiteCert" {
domainName "www.example.com"
subjectAlternativeName "example.com"
}
// CloudFront distribution
cloudFrontDistribution "CDN" {
s3DefaultBehavior (S3OriginType.StaticWebsiteOrigin websiteBucket.Bucket.Value)
domainName "www.example.com"
domainName "example.com"
certificate cert.Certificate.Value
defaultRootObject "index.html"
}
}
Implementation checklist & recommended resources
Security
- Prefer DNS validation for automation and least privilege, as emphasised in re:Invent NET409 “Advanced certificate management.”
- Issue certificates in the region required by the consuming service (us-east-1 for CloudFront, region-specific for regional endpoints).
- Monitor ACM expiry metrics with CloudWatch and configure AWS Config rules to detect certificates nearing renewal.
Operations & cost
- Tag certificates with owner, environment, and renewal contact details.
- Consolidate SANs and leverage wildcard certificates where appropriate, but avoid overloading certificates with unrelated domains.
- Delete unused certificates to reduce operational noise.
Further learning
- *AWS Networking Blog* – “Simplify HTTPS with ACM and Route 53.”
- *re:Invent NET409* – Advanced certificate management (4.8★ session rating).
- *AWS Security Blog* – “Enforce TLS everywhere with ACM and CloudFront.”
- *Ben Kehoe* – “Infrastructure as Policy: automating certificate issuance.”
Adopt these practices so every certificate request, validation, and renewal remains automated, auditable, and aligned with AWS Hero-recommended guard rails.
<summary>Creates an AWS CDK Stack construct.</summary>
<param name="name">The name of the stack.</param>
<code lang="fsharp"> stack "MyStack" { lambda myFunction bucket myBucket } </code>
<summary>Creates an ACM certificate with AWS best practices.</summary>
<param name="name">The certificate name.</param>
<code lang="fsharp"> certificate "MySiteCert" { domainName "example.com" subjectAlternativeName "*.example.com" subjectAlternativeName "www.example.com" dnsValidation myHostedZone } </code>
<summary>Sets the primary domain name for the certificate.</summary>
<param name="domain">The domain name (e.g., "example.com").</param>
<summary>Adds a subject alternative name (SAN).</summary>
<param name="san">Additional domain name (e.g., "*.example.com", "www.example.com").</param>
<summary>Sets the certificate name in ACM.</summary>
<summary> Creates a new Route 53 hosted zone builder. Example: hostedZone "example.com" { comment "Production domain" } </summary>
<summary>Uses DNS validation with a specific hosted zone.</summary>
<summary>Creates a DNS-validated certificate for cross-region use (e.g., CloudFront).</summary>
<param name="name">The certificate name.</param>
<code lang="fsharp"> dnsValidatedCertificate "CloudFrontCert" { domainName "example.com" hostedZone myHostedZone region "us-east-1" } </code>
<summary>Sets the domain name.</summary>
<summary>Sets the Route53 hosted zone for DNS validation.</summary>
<summary>Sets the region for the certificate (useful for CloudFront which requires us-east-1).</summary>
<summary>Uses email validation.</summary>
<summary> Enables or disables versioning for the S3 bucket. **Security Best Practice:** Enable versioning for: - Critical data that requires audit trails - Data subject to compliance requirements (HIPAA, SOC2, etc.) - Production buckets storing business data **Cost Consideration:** Versioning stores all versions of objects, increasing storage costs. Only disable for: - Temporary/cache buckets - Build artifacts with short lifecycle - Development/testing buckets **Default:** false (opt-in for cost optimization) </summary>
<param name="value">True to enable versioning, false to disable.</param>
<param name="config">The current bucket configuration.</param>
<code lang="fsharp"> bucket "production-data" { versioned true // Enable for production } bucket "cache-bucket" { versioned false // Disable for temp data } </code>
type BlockPublicAccess = inherit DeputyBase new: options: IBlockPublicAccessOptions -> unit member BlockPublicAcls: Nullable<bool> member BlockPublicPolicy: Nullable<bool> member IgnorePublicAcls: Nullable<bool> member RestrictPublicBuckets: Nullable<bool> static member BLOCK_ACLS: BlockPublicAccess static member BLOCK_ACLS_ONLY: BlockPublicAccess static member BLOCK_ALL: BlockPublicAccess
--------------------
AWS.S3.BlockPublicAccess(options: AWS.S3.IBlockPublicAccessOptions) : AWS.S3.BlockPublicAccess
<summary>Creates a CloudFront distribution with AWS best practices.</summary>
<param name="name">The distribution name.</param>
<remarks> Example: cloudFrontDistribution "MyCDN" { s3DefaultBehavior myBucket defaultRootObject "index.html" domainName "static.example.com" priceClass PriceClass.PRICE_CLASS_100 } </remarks>
<summary> Convenience: default S3 origin behavior with common best-practice defaults. Defaults: - ViewerProtocolPolicy = REDIRECT_TO_HTTPS - CachePolicy = CachePolicy.CACHING_OPTIMIZED - OriginRequestPolicy = OriginRequestPolicy.CORS_S3_ORIGIN - Compress = true You can override any default via optional parameters. </summary>
<summary> The underlying CDK Bucket construct - use for advanced scenarios </summary>
<summary>Adds a domain name (call multiple times to add several).</summary>
<summary>Sets an ACM certificate for the distribution.</summary>
<summary>Sets the default root object (e.g., "index.html").</summary>
FsCDK