Create AWS ACM Certificates

The aws:acm/certificate:Certificate resource, part of the Pulumi AWS provider, requests and manages ACM certificates in three forms: Amazon-issued certificates with DNS or email validation, imported certificates from external certificate authorities, and private certificates from ACM Private CA. This guide focuses on three capabilities: email validation configuration, importing externally-issued certificates, and extracting DNS validation records.

Amazon-issued certificates require validation through DNS records or email confirmation. Imported certificates need certificate materials from an external CA. The resource requests certificates but doesn’t wait for validation to complete; use aws.acm.CertificateValidation for that. The examples are intentionally small. Combine them with Route 53 zones, validation resources, and your own certificate infrastructure.

Request a certificate with email validation

Email validation sends confirmation messages to domain owners when DNS record creation isn’t feasible or when organizational policy requires manual approval.

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

The validationMethod property set to “EMAIL” triggers email-based validation. The validationOptions block lets you redirect validation emails to a parent domain (example.com) rather than the certificate domain (testing.example.com). AWS sends validation emails to standard addresses at the validation domain; someone must click the approval link to complete validation.

Import an externally-issued certificate

Organizations with existing certificate infrastructure can import certificates issued by external CAs into ACM for use with AWS services like CloudFront or Application Load Balancers.

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 private key materials. This example generates a self-signed certificate for demonstration, but in practice you’d import certificates from your organization’s CA. Imported certificates aren’t eligible for AWS managed renewal; you must update them manually before expiration.

Create DNS validation records programmatically

DNS validation requires creating CNAME records that prove domain ownership. The domainValidationOptions output provides the exact record details needed.

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: {}

After requesting a certificate with DNS validation, the domainValidationOptions output contains resourceRecordName, resourceRecordValue, and resourceRecordType for each domain. This example creates Route 53 records from those outputs. For complete validation, combine this with aws.acm.CertificateValidation to wait until AWS confirms the records.

Beyond these examples

These snippets focus on specific certificate-level features: certificate request and validation methods, external certificate import, and DNS validation record creation. They’re intentionally minimal rather than full TLS deployment modules.

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

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

  • CertificateValidation resource for waiting on validation completion
  • Subject Alternative Names (SANs) for multi-domain certificates
  • Private certificates using ACM Private CA (certificateAuthorityArn)
  • Certificate 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 I can create?
You can create Amazon-issued certificates (AWS provides the CA and manages renewal), imported certificates (from an external CA), and private certificates (issued by an ACM Private Certificate Authority).
How do I create a private certificate?
Set the certificateAuthorityArn property to reference an aws.acmpca.CertificateAuthority resource.
How do I import an existing certificate into ACM?
Provide the certificateBody, privateKey, and optionally certificateChain properties when creating the Certificate resource.
Validation & Issuance
Does the Certificate resource wait for my certificate to be issued?
No, the resource requests the certificate but doesn’t wait for issuance or validation. Use aws.acm.CertificateValidation to wait for validation to complete.
How do I validate my ACM certificate?
Use aws.acm.CertificateValidation along with aws.route53.Record to create DNS validation records and wait for validation to complete.
What's the difference between DNS and EMAIL validation?
DNS validation can be fully automated using Route53 records, while EMAIL validation requires manual steps outside of Pulumi and should be avoided.
How do I get the DNS validation records for my certificate?
Use the domainValidationOptions output property, which contains the validation records. This property is only set when DNS validation is used.
Renewal & Lifecycle
Can imported certificates automatically renew?
No, imported certificates are not eligible for automatic renewal because they’re not managed by AWS. You must manually supply new certificate materials to update them.
How does managed renewal work for private certificates?
Private certificates are eligible for managed renewal if they’ve been exported or associated with an AWS service. By default, certificates are valid for 395 days and renewal starts 60 days before expiration. Use earlyRenewalDuration to start renewal earlier.
How can I tell if my private certificate is pending renewal?
Check the pendingRenewal output property, which is true if the certificate is within the earlyRenewalDuration period.
Configuration & Limitations
What properties can't I change after creating a certificate?
The following properties are immutable: domainName, keyAlgorithm, subjectAlternativeNames, validationMethod, certificateAuthorityArn, and validationOptions.
Can I update an imported certificate with new materials?
Yes, you can supply new certificate materials (certificateBody, privateKey, certificateChain) to update an existing imported certificate in place.

Using a different cloud?

Explore security guides for other cloud providers: