Header menu logo FsCDK

Certificate Manager 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

Operations & cost

Further learning

Adopt these practices so every certificate request, validation, and renewal remains automated, auditable, and aligned with AWS Hero-recommended guard rails.

namespace FsCDK
namespace Amazon
namespace Amazon.CDK
namespace Amazon.CDK.AWS
namespace Amazon.CDK.AWS.CertificateManager
namespace Amazon.CDK.AWS.Route53
namespace Amazon.CDK.AWS.CloudFront
val stack: name: string -> StackBuilder
<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>
val certificate: name: string -> CertificateBuilder
<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>
custom operation: domainName (string) Calls CertificateBuilder.DomainName
<summary>Sets the primary domain name for the certificate.</summary>
<param name="domain">The domain name (e.g., "example.com").</param>
custom operation: subjectAlternativeName (string) Calls CertificateBuilder.SubjectAlternativeName
<summary>Adds a subject alternative name (SAN).</summary>
<param name="san">Additional domain name (e.g., "*.example.com", "www.example.com").</param>
custom operation: certificateName (string) Calls CertificateBuilder.CertificateName
<summary>Sets the certificate name in ACM.</summary>
val myHostedZone: Route53HostedZoneSpec
val hostedZone: zoneName: string -> Route53HostedZoneBuilder
<summary> Creates a new Route 53 hosted zone builder. Example: hostedZone "example.com" { comment "Production domain" } </summary>
custom operation: comment (string) Calls Route53HostedZoneBuilder.Comment
custom operation: dnsValidation (Route53HostedZoneSpec) Calls CertificateBuilder.DnsValidation
<summary>Uses DNS validation with a specific hosted zone.</summary>
val myHostedZone: IHostedZone
val dnsValidatedCertificate: name: string -> DnsValidatedCertificateBuilder
<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>
custom operation: domainName (string) Calls DnsValidatedCertificateBuilder.DomainName
<summary>Sets the domain name.</summary>
custom operation: hostedZone (IHostedZone) Calls DnsValidatedCertificateBuilder.HostedZone
<summary>Sets the Route53 hosted zone for DNS validation.</summary>
custom operation: region (string) Calls DnsValidatedCertificateBuilder.Region
<summary>Sets the region for the certificate (useful for CloudFront which requires us-east-1).</summary>
custom operation: emailValidation Calls CertificateBuilder.EmailValidation
<summary>Uses email validation.</summary>
val websiteBucket: BucketSpec
val bucket: name: string -> BucketBuilder
custom operation: versioned (bool) Calls BucketBuilder.Versioned
<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>
custom operation: websiteIndexDocument (string) Calls BucketBuilder.WebsiteIndexDocument
custom operation: websiteErrorDocument (string) Calls BucketBuilder.WebsiteErrorDocument
custom operation: blockPublicAccess (AWS.S3.BlockPublicAccess) Calls BucketBuilder.BlockPublicAccess
namespace Amazon.CDK.AWS.S3
Multiple items
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
property AWS.S3.BlockPublicAccess.BLOCK_ALL: AWS.S3.BlockPublicAccess with get
val cert: CertificateSpec
val cloudFrontDistribution: name: string -> DistributionBuilder
<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>
custom operation: s3DefaultBehavior (S3OriginType) (ViewerProtocolPolicy option) (ICachePolicy option) (IOriginRequestPolicy option) (ResponseHeadersPolicy option) (AllowedMethods option) (CachedMethods option) (bool option) Calls DistributionBuilder.S3DefaultBehavior
<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>
type S3OriginType = | StaticWebsiteOrigin of bucket: IBucket | GenericOrigin of origin: IOrigin member Equals: S3OriginType * IEqualityComparer -> bool
union case S3OriginType.StaticWebsiteOrigin: bucket: AWS.S3.IBucket -> S3OriginType
BucketSpec.Bucket: AWS.S3.Bucket option
<summary> The underlying CDK Bucket construct - use for advanced scenarios </summary>
property Option.Value: AWS.S3.Bucket with get
custom operation: domainName (string) Calls DistributionBuilder.DomainName
<summary>Adds a domain name (call multiple times to add several).</summary>
custom operation: certificate (ICertificate) Calls DistributionBuilder.Certificate
<summary>Sets an ACM certificate for the distribution.</summary>
CertificateSpec.Certificate: ICertificate option
property Option.Value: ICertificate with get
custom operation: defaultRootObject (string) Calls DistributionBuilder.DefaultRootObject
<summary>Sets the default root object (e.g., "index.html").</summary>

Type something to start searching.