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 FREEFrequently Asked Questions
Certificate Types & Creation
certificateAuthorityArn to the ARN of your ACM Private Certificate Authority when creating the certificate.certificateBody, privateKey, and optionally certificateChain when creating the resource. You can update these values in place to renew imported certificates.Validation & Issuance
aws.acm.CertificateValidation to wait for the certificate to become ready.domainValidationOptions output to create Route53 records, then use aws.acm.CertificateValidation to wait for validation to complete.domainValidationOptions output property contains the validation records, but it’s only populated when DNS validation is used.Renewal & Lifecycle
earlyRenewalDuration to start the renewal process earlier than the default 60-day window.Configuration & Immutability
domainName, keyAlgorithm, subjectAlternativeNames, validationMethod, certificateAuthorityArn, and validationOptions.certificateBody, privateKey, and certificateChain values to update an imported certificate in place.