Create AWS ACM Certificates

The aws:acm/certificate:Certificate resource, part of the Pulumi AWS provider, requests and manages SSL/TLS certificates from AWS Certificate Manager. ACM supports three certificate types: Amazon-issued certificates with automatic renewal, imported certificates from external authorities, and private certificates from ACM Private CA. This guide focuses on three capabilities: email validation, certificate import, and DNS validation record generation.

Amazon-issued certificates require validation via email or DNS records to prove domain ownership. Imported certificates require existing certificate materials from external CAs and are not eligible for automatic renewal. The examples are intentionally small. Combine them with Route 53 zones, validation resources, or your own DNS infrastructure.

Request a certificate with email validation

Teams can use email validation when DNS validation isn’t feasible, though this requires manual approval outside of Pulumi.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const cert = new aws.acm.Certificate("cert", {
    domainName: "testing.example.com",
    validationMethod: "EMAIL",
    validationOptions: [{
        domainName: "testing.example.com",
        validationDomain: "example.com",
    }],
});
import pulumi
import pulumi_aws as aws

cert = aws.acm.Certificate("cert",
    domain_name="testing.example.com",
    validation_method="EMAIL",
    validation_options=[{
        "domain_name": "testing.example.com",
        "validation_domain": "example.com",
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/acm"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := acm.NewCertificate(ctx, "cert", &acm.CertificateArgs{
			DomainName:       pulumi.String("testing.example.com"),
			ValidationMethod: pulumi.String("EMAIL"),
			ValidationOptions: acm.CertificateValidationOptionArray{
				&acm.CertificateValidationOptionArgs{
					DomainName:       pulumi.String("testing.example.com"),
					ValidationDomain: pulumi.String("example.com"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var cert = new Aws.Acm.Certificate("cert", new()
    {
        DomainName = "testing.example.com",
        ValidationMethod = "EMAIL",
        ValidationOptions = new[]
        {
            new Aws.Acm.Inputs.CertificateValidationOptionArgs
            {
                DomainName = "testing.example.com",
                ValidationDomain = "example.com",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.acm.Certificate;
import com.pulumi.aws.acm.CertificateArgs;
import com.pulumi.aws.acm.inputs.CertificateValidationOptionArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var cert = new Certificate("cert", CertificateArgs.builder()
            .domainName("testing.example.com")
            .validationMethod("EMAIL")
            .validationOptions(CertificateValidationOptionArgs.builder()
                .domainName("testing.example.com")
                .validationDomain("example.com")
                .build())
            .build());

    }
}
resources:
  cert:
    type: aws:acm:Certificate
    properties:
      domainName: testing.example.com
      validationMethod: EMAIL
      validationOptions:
        - domainName: testing.example.com
          validationDomain: example.com

When you request a certificate with email validation, AWS sends validation emails to domain contacts. The validationOptions property lets you redirect validation emails to a parent domain (example.com) even when requesting a certificate for a subdomain (testing.example.com). This is useful when you don’t have access to subdomain email addresses but control the parent domain.

Import an existing certificate from another authority

Organizations with certificates from external CAs can import them into ACM to use with CloudFront, Application Load Balancers, or other AWS services.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as tls from "@pulumi/tls";

const example = new tls.PrivateKey("example", {algorithm: "RSA"});
const exampleSelfSignedCert = new tls.SelfSignedCert("example", {
    keyAlgorithm: "RSA",
    privateKeyPem: example.privateKeyPem,
    subject: [{
        commonName: "example.com",
        organization: "ACME Examples, Inc",
    }],
    validityPeriodHours: 12,
    allowedUses: [
        "key_encipherment",
        "digital_signature",
        "server_auth",
    ],
});
const cert = new aws.acm.Certificate("cert", {
    privateKey: example.privateKeyPem,
    certificateBody: exampleSelfSignedCert.certPem,
});
import pulumi
import pulumi_aws as aws
import pulumi_tls as tls

example = tls.PrivateKey("example", algorithm="RSA")
example_self_signed_cert = tls.SelfSignedCert("example",
    key_algorithm="RSA",
    private_key_pem=example.private_key_pem,
    subject=[{
        "commonName": "example.com",
        "organization": "ACME Examples, Inc",
    }],
    validity_period_hours=12,
    allowed_uses=[
        "key_encipherment",
        "digital_signature",
        "server_auth",
    ])
cert = aws.acm.Certificate("cert",
    private_key=example.private_key_pem,
    certificate_body=example_self_signed_cert.cert_pem)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/acm"
	"github.com/pulumi/pulumi-tls/sdk/v5/go/tls"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := tls.NewPrivateKey(ctx, "example", &tls.PrivateKeyArgs{
			Algorithm: pulumi.String("RSA"),
		})
		if err != nil {
			return err
		}
		exampleSelfSignedCert, err := tls.NewSelfSignedCert(ctx, "example", &tls.SelfSignedCertArgs{
			KeyAlgorithm:  "RSA",
			PrivateKeyPem: example.PrivateKeyPem,
			Subject: tls.SelfSignedCertSubjectArgs{
				map[string]interface{}{
					"commonName":   "example.com",
					"organization": "ACME Examples, Inc",
				},
			},
			ValidityPeriodHours: pulumi.Int(12),
			AllowedUses: pulumi.StringArray{
				pulumi.String("key_encipherment"),
				pulumi.String("digital_signature"),
				pulumi.String("server_auth"),
			},
		})
		if err != nil {
			return err
		}
		_, err = acm.NewCertificate(ctx, "cert", &acm.CertificateArgs{
			PrivateKey:      example.PrivateKeyPem,
			CertificateBody: exampleSelfSignedCert.CertPem,
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
using Tls = Pulumi.Tls;

return await Deployment.RunAsync(() => 
{
    var example = new Tls.PrivateKey("example", new()
    {
        Algorithm = "RSA",
    });

    var exampleSelfSignedCert = new Tls.SelfSignedCert("example", new()
    {
        KeyAlgorithm = "RSA",
        PrivateKeyPem = example.PrivateKeyPem,
        Subject = new[]
        {
            
            {
                { "commonName", "example.com" },
                { "organization", "ACME Examples, Inc" },
            },
        },
        ValidityPeriodHours = 12,
        AllowedUses = new[]
        {
            "key_encipherment",
            "digital_signature",
            "server_auth",
        },
    });

    var cert = new Aws.Acm.Certificate("cert", new()
    {
        PrivateKey = example.PrivateKeyPem,
        CertificateBody = exampleSelfSignedCert.CertPem,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.tls.PrivateKey;
import com.pulumi.tls.PrivateKeyArgs;
import com.pulumi.tls.SelfSignedCert;
import com.pulumi.tls.SelfSignedCertArgs;
import com.pulumi.aws.acm.Certificate;
import com.pulumi.aws.acm.CertificateArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new PrivateKey("example", PrivateKeyArgs.builder()
            .algorithm("RSA")
            .build());

        var exampleSelfSignedCert = new SelfSignedCert("exampleSelfSignedCert", SelfSignedCertArgs.builder()
            .keyAlgorithm("RSA")
            .privateKeyPem(example.privateKeyPem())
            .subject(SelfSignedCertSubjectArgs.builder()
                .commonName("example.com")
                .organization("ACME Examples, Inc")
                .build())
            .validityPeriodHours(12)
            .allowedUses(            
                "key_encipherment",
                "digital_signature",
                "server_auth")
            .build());

        var cert = new Certificate("cert", CertificateArgs.builder()
            .privateKey(example.privateKeyPem())
            .certificateBody(exampleSelfSignedCert.certPem())
            .build());

    }
}
resources:
  example:
    type: tls:PrivateKey
    properties:
      algorithm: RSA
  exampleSelfSignedCert:
    type: tls:SelfSignedCert
    name: example
    properties:
      keyAlgorithm: RSA
      privateKeyPem: ${example.privateKeyPem}
      subject:
        - commonName: example.com
          organization: ACME Examples, Inc
      validityPeriodHours: 12
      allowedUses:
        - key_encipherment
        - digital_signature
        - server_auth
  cert:
    type: aws:acm:Certificate
    properties:
      privateKey: ${example.privateKeyPem}
      certificateBody: ${exampleSelfSignedCert.certPem}

The certificateBody and privateKey properties contain the certificate and its private key in PEM format. Imported certificates are not eligible for AWS automatic renewal; you must supply new certificate materials before expiration to update the certificate in place.

Create DNS validation records from certificate output

DNS validation requires creating CNAME records that prove domain ownership. The certificate outputs validation requirements that can be used to create these records programmatically.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example: aws.route53.Record[] = [];
for (const range of Object.entries(.reduce((__obj, dvo) => ({ ...__obj, [dvo.domainName]: {
    name: dvo.resourceRecordName,
    record: dvo.resourceRecordValue,
    type: dvo.resourceRecordType,
} }))).map(([k, v]) => ({key: k, value: v}))) {
    example.push(new aws.route53.Record(`example-${range.key}`, {
        allowOverwrite: true,
        name: range.value.name,
        records: [range.value.record],
        ttl: 60,
        type: aws.route53.RecordType[range.value.type],
        zoneId: exampleAwsRoute53Zone.zoneId,
    }));
}
import pulumi
import pulumi_aws as aws

example = []
for range in [{"key": k, "value": v} for [k, v] in enumerate({dvo.domain_name: {
    name: dvo.resource_record_name,
    record: dvo.resource_record_value,
    type: dvo.resource_record_type,
} for dvo in example_aws_acm_certificate.domain_validation_options})]:
    example.append(aws.route53.Record(f"example-{range['key']}",
        allow_overwrite=True,
        name=range["value"]["name"],
        records=[range["value"]["record"]],
        ttl=60,
        type=aws.route53.RecordType(range["value"]["type"]),
        zone_id=example_aws_route53_zone["zoneId"]))
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new List<Aws.Route53.Record>();
    foreach (var range in .ToDictionary(item => {
        var dvo = item.Value;
        return dvo.DomainName;
    }, item => {
        var dvo = item.Value;
        return 
        {
            { "name", dvo.ResourceRecordName },
            { "record", dvo.ResourceRecordValue },
            { "type", dvo.ResourceRecordType },
        };
    }).Select(pair => new { pair.Key, pair.Value }))
    {
        example.Add(new Aws.Route53.Record($"example-{range.Key}", new()
        {
            AllowOverwrite = true,
            Name = range.Value.Name,
            Records = new[]
            {
                range.Value.Record,
            },
            Ttl = 60,
            Type = System.Enum.Parse<Aws.Route53.RecordType>(range.Value.Type),
            ZoneId = exampleAwsRoute53Zone.ZoneId,
        }));
    }
});
resources:
  example:
    type: aws:route53:Record
    properties:
      allowOverwrite: true
      name: ${range.value.name}
      records:
        - ${range.value.record}
      ttl: 60
      type: ${range.value.type}
      zoneId: ${exampleAwsRoute53Zone.zoneId}
    options: {}

The domainValidationOptions output contains the CNAME record details AWS requires for validation. Each entry includes resourceRecordName, resourceRecordValue, and resourceRecordType. This example creates Route 53 records automatically, but you can use these values with any DNS provider. Typically, you’d also use aws.acm.CertificateValidation to wait for validation to complete before using the certificate.

Beyond these examples

These snippets focus on specific certificate-level features: email and DNS validation methods, certificate import from external authorities, and validation record generation. They’re intentionally minimal rather than full TLS deployment workflows.

The examples may reference pre-existing infrastructure such as Route 53 hosted zones for DNS validation, and email access to domain contacts for email validation. They focus on certificate configuration rather than provisioning the surrounding infrastructure.

To keep things focused, common certificate patterns are omitted, including:

  • DNS validation with CertificateValidation resource (automated waiting)
  • Private certificates from ACM Private CA (certificateAuthorityArn)
  • Subject Alternative Names for multi-domain certificates
  • Early renewal configuration (earlyRenewalDuration)

These omissions are intentional: the goal is to illustrate how each certificate feature is wired, not provide drop-in TLS modules. See the ACM Certificate resource reference for all available configuration options.

Let's create AWS ACM Certificates

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Certificate Types & Creation
What are the three types of ACM certificates?
ACM supports three certificate types: Amazon-issued (AWS provides the CA and manages renewal), imported (issued by external CA), and private (issued by ACM Private Certificate Authority).
How do I create a private certificate?
Set certificateAuthorityArn to the ARN of your ACM Private Certificate Authority when creating the certificate.
How do I import an existing certificate?
Provide certificateBody, privateKey, and optionally certificateChain when creating the resource. You can update these values in place to renew imported certificates.
Validation & Issuance
Why isn't my certificate ready to use immediately after creation?
The certificate resource only requests the certificate but doesn’t wait for validation or issuance. Use aws.acm.CertificateValidation to wait for the certificate to become ready.
Should I use DNS or EMAIL validation?
Use DNS validation, which can be fully automated with Route53 records. EMAIL validation should be avoided because it requires manual steps outside of Pulumi.
How do I complete DNS validation?
Use the domainValidationOptions output to create Route53 records, then use aws.acm.CertificateValidation to wait for validation to complete.
How do I access the DNS validation records?
The domainValidationOptions output property contains the validation records, but it’s only populated when DNS validation is used.
Renewal & Lifecycle
Which certificate types support automatic renewal?
Amazon-issued certificates renew automatically. Imported certificates don’t support automatic renewal. Private certificates renew automatically only if they’ve been exported or associated with an AWS service.
How long are private certificates valid?
Private certificates are valid for 395 days by default, with managed renewal starting 60 days before expiration.
How do I renew my private certificate earlier than 60 days before expiration?
Configure earlyRenewalDuration to start the renewal process earlier than the default 60-day window.
Configuration & Immutability
What properties can't be changed after creating a certificate?
These properties are immutable: domainName, keyAlgorithm, subjectAlternativeNames, validationMethod, certificateAuthorityArn, and validationOptions.
Can I update an imported certificate with new materials?
Yes, you can supply new certificateBody, privateKey, and certificateChain values to update an imported certificate in place.

Using a different cloud?

Explore security guides for other cloud providers: