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 FREEFrequently Asked Questions
Certificate Types & Creation
certificateAuthorityArn property to reference an aws.acmpca.CertificateAuthority resource.certificateBody, privateKey, and optionally certificateChain properties when creating the Certificate resource.Validation & Issuance
aws.acm.CertificateValidation to wait for validation to complete.aws.acm.CertificateValidation along with aws.route53.Record to create DNS validation records and wait for validation to complete.domainValidationOptions output property, which contains the validation records. This property is only set when DNS validation is used.Renewal & Lifecycle
earlyRenewalDuration to start renewal earlier.pendingRenewal output property, which is true if the certificate is within the earlyRenewalDuration period.Configuration & Limitations
domainName, keyAlgorithm, subjectAlternativeNames, validationMethod, certificateAuthorityArn, and validationOptions.certificateBody, privateKey, certificateChain) to update an existing imported certificate in place.