Create AWS CloudFront Distributions

The aws:cloudfront/distribution:Distribution resource, part of the Pulumi AWS provider, defines a CloudFront distribution that serves content from origins through AWS edge locations. This guide focuses on four capabilities: S3 origin configuration with SSL, origin failover for availability, managed caching policies, and standard logging V2 to S3 and Firehose.

Distributions reference S3 buckets, ACM certificates, origin access controls, and optionally Route53 zones or Kinesis Firehose streams. The examples are intentionally small. Combine them with your own origin infrastructure, SSL certificates, and logging destinations.

Serve S3 content with custom domains and SSL

Most deployments distribute static content from S3 buckets using custom domains and SSL certificates for branded HTTPS URLs.

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

const b = new aws.s3.Bucket("b", {
    bucket: "mybucket",
    tags: {
        Name: "My bucket",
    },
});
const s3OriginId = "myS3Origin";
const myDomain = "mydomain.com";
const myDomainGetCertificate = aws.acm.getCertificate({
    region: "us-east-1",
    domain: `*.${myDomain}`,
    statuses: ["ISSUED"],
});
const _default = new aws.cloudfront.OriginAccessControl("default", {
    name: "default-oac",
    originAccessControlOriginType: "s3",
    signingBehavior: "always",
    signingProtocol: "sigv4",
});
const s3Distribution = new aws.cloudfront.Distribution("s3_distribution", {
    origins: [{
        domainName: b.bucketRegionalDomainName,
        originAccessControlId: _default.id,
        originId: s3OriginId,
    }],
    enabled: true,
    isIpv6Enabled: true,
    comment: "Some comment",
    defaultRootObject: "index.html",
    aliases: [
        `mysite.${myDomain}`,
        `yoursite.${myDomain}`,
    ],
    defaultCacheBehavior: {
        allowedMethods: [
            "DELETE",
            "GET",
            "HEAD",
            "OPTIONS",
            "PATCH",
            "POST",
            "PUT",
        ],
        cachedMethods: [
            "GET",
            "HEAD",
        ],
        targetOriginId: s3OriginId,
        forwardedValues: {
            queryString: false,
            cookies: {
                forward: "none",
            },
        },
        viewerProtocolPolicy: "allow-all",
        minTtl: 0,
        defaultTtl: 3600,
        maxTtl: 86400,
    },
    orderedCacheBehaviors: [
        {
            pathPattern: "/content/immutable/*",
            allowedMethods: [
                "GET",
                "HEAD",
                "OPTIONS",
            ],
            cachedMethods: [
                "GET",
                "HEAD",
                "OPTIONS",
            ],
            targetOriginId: s3OriginId,
            forwardedValues: {
                queryString: false,
                headers: ["Origin"],
                cookies: {
                    forward: "none",
                },
            },
            minTtl: 0,
            defaultTtl: 86400,
            maxTtl: 31536000,
            compress: true,
            viewerProtocolPolicy: "redirect-to-https",
        },
        {
            pathPattern: "/content/*",
            allowedMethods: [
                "GET",
                "HEAD",
                "OPTIONS",
            ],
            cachedMethods: [
                "GET",
                "HEAD",
            ],
            targetOriginId: s3OriginId,
            forwardedValues: {
                queryString: false,
                cookies: {
                    forward: "none",
                },
            },
            minTtl: 0,
            defaultTtl: 3600,
            maxTtl: 86400,
            compress: true,
            viewerProtocolPolicy: "redirect-to-https",
        },
    ],
    priceClass: "PriceClass_200",
    restrictions: {
        geoRestriction: {
            restrictionType: "whitelist",
            locations: [
                "US",
                "CA",
                "GB",
                "DE",
            ],
        },
    },
    tags: {
        Environment: "production",
    },
    viewerCertificate: {
        acmCertificateArn: myDomainGetCertificate.then(myDomainGetCertificate => myDomainGetCertificate.arn),
        sslSupportMethod: "sni-only",
    },
});
// See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
const originBucketPolicy = aws.iam.getPolicyDocumentOutput({
    statements: [{
        sid: "AllowCloudFrontServicePrincipalReadWrite",
        effect: "Allow",
        principals: [{
            type: "Service",
            identifiers: ["cloudfront.amazonaws.com"],
        }],
        actions: [
            "s3:GetObject",
            "s3:PutObject",
        ],
        resources: [pulumi.interpolate`${b.arn}/*`],
        conditions: [{
            test: "StringEquals",
            variable: "AWS:SourceArn",
            values: [s3Distribution.arn],
        }],
    }],
});
const bBucketPolicy = new aws.s3.BucketPolicy("b", {
    bucket: b.bucket,
    policy: originBucketPolicy.apply(originBucketPolicy => originBucketPolicy.json),
});
// Create Route53 records for the CloudFront distribution aliases
const myDomainGetZone = aws.route53.getZone({
    name: myDomain,
});
const cloudfront: aws.route53.Record[] = [];
s3Distribution.aliases.apply(rangeBody => {
    for (const range of rangeBody.map((v, k) => ({key: k, value: v}))) {
        cloudfront.push(new aws.route53.Record(`cloudfront-${range.key}`, {
            zoneId: myDomainGetZone.then(myDomainGetZone => myDomainGetZone.zoneId),
            name: range.value,
            type: aws.route53.RecordType.A,
            aliases: [{
                name: s3Distribution.domainName,
                zoneId: s3Distribution.hostedZoneId,
                evaluateTargetHealth: false,
            }],
        }));
    }
});
import pulumi
import pulumi_aws as aws

b = aws.s3.Bucket("b",
    bucket="mybucket",
    tags={
        "Name": "My bucket",
    })
s3_origin_id = "myS3Origin"
my_domain = "mydomain.com"
my_domain_get_certificate = aws.acm.get_certificate(region="us-east-1",
    domain=f"*.{my_domain}",
    statuses=["ISSUED"])
default = aws.cloudfront.OriginAccessControl("default",
    name="default-oac",
    origin_access_control_origin_type="s3",
    signing_behavior="always",
    signing_protocol="sigv4")
s3_distribution = aws.cloudfront.Distribution("s3_distribution",
    origins=[{
        "domain_name": b.bucket_regional_domain_name,
        "origin_access_control_id": default.id,
        "origin_id": s3_origin_id,
    }],
    enabled=True,
    is_ipv6_enabled=True,
    comment="Some comment",
    default_root_object="index.html",
    aliases=[
        f"mysite.{my_domain}",
        f"yoursite.{my_domain}",
    ],
    default_cache_behavior={
        "allowed_methods": [
            "DELETE",
            "GET",
            "HEAD",
            "OPTIONS",
            "PATCH",
            "POST",
            "PUT",
        ],
        "cached_methods": [
            "GET",
            "HEAD",
        ],
        "target_origin_id": s3_origin_id,
        "forwarded_values": {
            "query_string": False,
            "cookies": {
                "forward": "none",
            },
        },
        "viewer_protocol_policy": "allow-all",
        "min_ttl": 0,
        "default_ttl": 3600,
        "max_ttl": 86400,
    },
    ordered_cache_behaviors=[
        {
            "path_pattern": "/content/immutable/*",
            "allowed_methods": [
                "GET",
                "HEAD",
                "OPTIONS",
            ],
            "cached_methods": [
                "GET",
                "HEAD",
                "OPTIONS",
            ],
            "target_origin_id": s3_origin_id,
            "forwarded_values": {
                "query_string": False,
                "headers": ["Origin"],
                "cookies": {
                    "forward": "none",
                },
            },
            "min_ttl": 0,
            "default_ttl": 86400,
            "max_ttl": 31536000,
            "compress": True,
            "viewer_protocol_policy": "redirect-to-https",
        },
        {
            "path_pattern": "/content/*",
            "allowed_methods": [
                "GET",
                "HEAD",
                "OPTIONS",
            ],
            "cached_methods": [
                "GET",
                "HEAD",
            ],
            "target_origin_id": s3_origin_id,
            "forwarded_values": {
                "query_string": False,
                "cookies": {
                    "forward": "none",
                },
            },
            "min_ttl": 0,
            "default_ttl": 3600,
            "max_ttl": 86400,
            "compress": True,
            "viewer_protocol_policy": "redirect-to-https",
        },
    ],
    price_class="PriceClass_200",
    restrictions={
        "geo_restriction": {
            "restriction_type": "whitelist",
            "locations": [
                "US",
                "CA",
                "GB",
                "DE",
            ],
        },
    },
    tags={
        "Environment": "production",
    },
    viewer_certificate={
        "acm_certificate_arn": my_domain_get_certificate.arn,
        "ssl_support_method": "sni-only",
    })
# See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
origin_bucket_policy = aws.iam.get_policy_document_output(statements=[{
    "sid": "AllowCloudFrontServicePrincipalReadWrite",
    "effect": "Allow",
    "principals": [{
        "type": "Service",
        "identifiers": ["cloudfront.amazonaws.com"],
    }],
    "actions": [
        "s3:GetObject",
        "s3:PutObject",
    ],
    "resources": [b.arn.apply(lambda arn: f"{arn}/*")],
    "conditions": [{
        "test": "StringEquals",
        "variable": "AWS:SourceArn",
        "values": [s3_distribution.arn],
    }],
}])
b_bucket_policy = aws.s3.BucketPolicy("b",
    bucket=b.bucket,
    policy=origin_bucket_policy.json)
# Create Route53 records for the CloudFront distribution aliases
my_domain_get_zone = aws.route53.get_zone(name=my_domain)
cloudfront = []
def create_cloudfront(range_body):
    for range in [{"key": k, "value": v} for [k, v] in enumerate(range_body)]:
        cloudfront.append(aws.route53.Record(f"cloudfront-{range['key']}",
            zone_id=my_domain_get_zone.zone_id,
            name=range["value"],
            type=aws.route53.RecordType.A,
            aliases=[{
                "name": s3_distribution.domain_name.apply(lambda domain_name: domain_name),
                "zone_id": s3_distribution.hosted_zone_id.apply(lambda hosted_zone_id: hosted_zone_id),
                "evaluate_target_health": False,
            }]))

s3_distribution.aliases.apply(create_cloudfront)
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/acm"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		b, err := s3.NewBucket(ctx, "b", &s3.BucketArgs{
			Bucket: pulumi.String("mybucket"),
			Tags: pulumi.StringMap{
				"Name": pulumi.String("My bucket"),
			},
		})
		if err != nil {
			return err
		}
		s3OriginId := "myS3Origin"
		myDomain := "mydomain.com"
		myDomainGetCertificate, err := acm.LookupCertificate(ctx, &acm.LookupCertificateArgs{
			Region: pulumi.StringRef("us-east-1"),
			Domain: pulumi.StringRef(fmt.Sprintf("*.%v", myDomain)),
			Statuses: []string{
				"ISSUED",
			},
		}, nil)
		if err != nil {
			return err
		}
		_default, err := cloudfront.NewOriginAccessControl(ctx, "default", &cloudfront.OriginAccessControlArgs{
			Name:                          pulumi.String("default-oac"),
			OriginAccessControlOriginType: pulumi.String("s3"),
			SigningBehavior:               pulumi.String("always"),
			SigningProtocol:               pulumi.String("sigv4"),
		})
		if err != nil {
			return err
		}
		s3Distribution, err := cloudfront.NewDistribution(ctx, "s3_distribution", &cloudfront.DistributionArgs{
			Origins: cloudfront.DistributionOriginArray{
				&cloudfront.DistributionOriginArgs{
					DomainName:            b.BucketRegionalDomainName,
					OriginAccessControlId: _default.ID(),
					OriginId:              pulumi.String(s3OriginId),
				},
			},
			Enabled:           pulumi.Bool(true),
			IsIpv6Enabled:     pulumi.Bool(true),
			Comment:           pulumi.String("Some comment"),
			DefaultRootObject: pulumi.String("index.html"),
			Aliases: pulumi.StringArray{
				pulumi.Sprintf("mysite.%v", myDomain),
				pulumi.Sprintf("yoursite.%v", myDomain),
			},
			DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
				AllowedMethods: pulumi.StringArray{
					pulumi.String("DELETE"),
					pulumi.String("GET"),
					pulumi.String("HEAD"),
					pulumi.String("OPTIONS"),
					pulumi.String("PATCH"),
					pulumi.String("POST"),
					pulumi.String("PUT"),
				},
				CachedMethods: pulumi.StringArray{
					pulumi.String("GET"),
					pulumi.String("HEAD"),
				},
				TargetOriginId: pulumi.String(s3OriginId),
				ForwardedValues: &cloudfront.DistributionDefaultCacheBehaviorForwardedValuesArgs{
					QueryString: pulumi.Bool(false),
					Cookies: &cloudfront.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs{
						Forward: pulumi.String("none"),
					},
				},
				ViewerProtocolPolicy: pulumi.String("allow-all"),
				MinTtl:               pulumi.Int(0),
				DefaultTtl:           pulumi.Int(3600),
				MaxTtl:               pulumi.Int(86400),
			},
			OrderedCacheBehaviors: cloudfront.DistributionOrderedCacheBehaviorArray{
				&cloudfront.DistributionOrderedCacheBehaviorArgs{
					PathPattern: pulumi.String("/content/immutable/*"),
					AllowedMethods: pulumi.StringArray{
						pulumi.String("GET"),
						pulumi.String("HEAD"),
						pulumi.String("OPTIONS"),
					},
					CachedMethods: pulumi.StringArray{
						pulumi.String("GET"),
						pulumi.String("HEAD"),
						pulumi.String("OPTIONS"),
					},
					TargetOriginId: pulumi.String(s3OriginId),
					ForwardedValues: &cloudfront.DistributionOrderedCacheBehaviorForwardedValuesArgs{
						QueryString: pulumi.Bool(false),
						Headers: pulumi.StringArray{
							pulumi.String("Origin"),
						},
						Cookies: &cloudfront.DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs{
							Forward: pulumi.String("none"),
						},
					},
					MinTtl:               pulumi.Int(0),
					DefaultTtl:           pulumi.Int(86400),
					MaxTtl:               pulumi.Int(31536000),
					Compress:             pulumi.Bool(true),
					ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
				},
				&cloudfront.DistributionOrderedCacheBehaviorArgs{
					PathPattern: pulumi.String("/content/*"),
					AllowedMethods: pulumi.StringArray{
						pulumi.String("GET"),
						pulumi.String("HEAD"),
						pulumi.String("OPTIONS"),
					},
					CachedMethods: pulumi.StringArray{
						pulumi.String("GET"),
						pulumi.String("HEAD"),
					},
					TargetOriginId: pulumi.String(s3OriginId),
					ForwardedValues: &cloudfront.DistributionOrderedCacheBehaviorForwardedValuesArgs{
						QueryString: pulumi.Bool(false),
						Cookies: &cloudfront.DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs{
							Forward: pulumi.String("none"),
						},
					},
					MinTtl:               pulumi.Int(0),
					DefaultTtl:           pulumi.Int(3600),
					MaxTtl:               pulumi.Int(86400),
					Compress:             pulumi.Bool(true),
					ViewerProtocolPolicy: pulumi.String("redirect-to-https"),
				},
			},
			PriceClass: pulumi.String("PriceClass_200"),
			Restrictions: &cloudfront.DistributionRestrictionsArgs{
				GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
					RestrictionType: pulumi.String("whitelist"),
					Locations: pulumi.StringArray{
						pulumi.String("US"),
						pulumi.String("CA"),
						pulumi.String("GB"),
						pulumi.String("DE"),
					},
				},
			},
			Tags: pulumi.StringMap{
				"Environment": pulumi.String("production"),
			},
			ViewerCertificate: &cloudfront.DistributionViewerCertificateArgs{
				AcmCertificateArn: pulumi.String(myDomainGetCertificate.Arn),
				SslSupportMethod:  pulumi.String("sni-only"),
			},
		})
		if err != nil {
			return err
		}
		// See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
		originBucketPolicy := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{
			Statements: iam.GetPolicyDocumentStatementArray{
				&iam.GetPolicyDocumentStatementArgs{
					Sid:    pulumi.String("AllowCloudFrontServicePrincipalReadWrite"),
					Effect: pulumi.String("Allow"),
					Principals: iam.GetPolicyDocumentStatementPrincipalArray{
						&iam.GetPolicyDocumentStatementPrincipalArgs{
							Type: pulumi.String("Service"),
							Identifiers: pulumi.StringArray{
								pulumi.String("cloudfront.amazonaws.com"),
							},
						},
					},
					Actions: pulumi.StringArray{
						pulumi.String("s3:GetObject"),
						pulumi.String("s3:PutObject"),
					},
					Resources: pulumi.StringArray{
						b.Arn.ApplyT(func(arn string) (string, error) {
							return fmt.Sprintf("%v/*", arn), nil
						}).(pulumi.StringOutput),
					},
					Conditions: iam.GetPolicyDocumentStatementConditionArray{
						&iam.GetPolicyDocumentStatementConditionArgs{
							Test:     pulumi.String("StringEquals"),
							Variable: pulumi.String("AWS:SourceArn"),
							Values: pulumi.StringArray{
								s3Distribution.Arn,
							},
						},
					},
				},
			},
		}, nil)
		_, err = s3.NewBucketPolicy(ctx, "b", &s3.BucketPolicyArgs{
			Bucket: b.Bucket,
			Policy: pulumi.String(originBucketPolicy.ApplyT(func(originBucketPolicy iam.GetPolicyDocumentResult) (*string, error) {
				return &originBucketPolicy.Json, nil
			}).(pulumi.StringPtrOutput)),
		})
		if err != nil {
			return err
		}
		// Create Route53 records for the CloudFront distribution aliases
		myDomainGetZone, err := route53.LookupZone(ctx, &route53.LookupZoneArgs{
			Name: pulumi.StringRef(myDomain),
		}, nil)
		if err != nil {
			return err
		}
		var cloudfront []*route53.Record
		for key0, val0 := range s3Distribution.Aliases {
			__res, err := route53.NewRecord(ctx, fmt.Sprintf("cloudfront-%v", key0), &route53.RecordArgs{
				ZoneId: pulumi.String(myDomainGetZone.ZoneId),
				Name:   pulumi.String(val0),
				Type:   pulumi.String(route53.RecordTypeA),
				Aliases: route53.RecordAliasArray{
					&route53.RecordAliasArgs{
						Name:                 s3Distribution.DomainName,
						ZoneId:               s3Distribution.HostedZoneId,
						EvaluateTargetHealth: pulumi.Bool(false),
					},
				},
			})
			if err != nil {
				return err
			}
			cloudfront = append(cloudfront, __res)
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var b = new Aws.S3.Bucket("b", new()
    {
        BucketName = "mybucket",
        Tags = 
        {
            { "Name", "My bucket" },
        },
    });

    var s3OriginId = "myS3Origin";

    var myDomain = "mydomain.com";

    var myDomainGetCertificate = Aws.Acm.GetCertificate.Invoke(new()
    {
        Region = "us-east-1",
        Domain = $"*.{myDomain}",
        Statuses = new[]
        {
            "ISSUED",
        },
    });

    var @default = new Aws.CloudFront.OriginAccessControl("default", new()
    {
        Name = "default-oac",
        OriginAccessControlOriginType = "s3",
        SigningBehavior = "always",
        SigningProtocol = "sigv4",
    });

    var s3Distribution = new Aws.CloudFront.Distribution("s3_distribution", new()
    {
        Origins = new[]
        {
            new Aws.CloudFront.Inputs.DistributionOriginArgs
            {
                DomainName = b.BucketRegionalDomainName,
                OriginAccessControlId = @default.Id,
                OriginId = s3OriginId,
            },
        },
        Enabled = true,
        IsIpv6Enabled = true,
        Comment = "Some comment",
        DefaultRootObject = "index.html",
        Aliases = new[]
        {
            $"mysite.{myDomain}",
            $"yoursite.{myDomain}",
        },
        DefaultCacheBehavior = new Aws.CloudFront.Inputs.DistributionDefaultCacheBehaviorArgs
        {
            AllowedMethods = new[]
            {
                "DELETE",
                "GET",
                "HEAD",
                "OPTIONS",
                "PATCH",
                "POST",
                "PUT",
            },
            CachedMethods = new[]
            {
                "GET",
                "HEAD",
            },
            TargetOriginId = s3OriginId,
            ForwardedValues = new Aws.CloudFront.Inputs.DistributionDefaultCacheBehaviorForwardedValuesArgs
            {
                QueryString = false,
                Cookies = new Aws.CloudFront.Inputs.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs
                {
                    Forward = "none",
                },
            },
            ViewerProtocolPolicy = "allow-all",
            MinTtl = 0,
            DefaultTtl = 3600,
            MaxTtl = 86400,
        },
        OrderedCacheBehaviors = new[]
        {
            new Aws.CloudFront.Inputs.DistributionOrderedCacheBehaviorArgs
            {
                PathPattern = "/content/immutable/*",
                AllowedMethods = new[]
                {
                    "GET",
                    "HEAD",
                    "OPTIONS",
                },
                CachedMethods = new[]
                {
                    "GET",
                    "HEAD",
                    "OPTIONS",
                },
                TargetOriginId = s3OriginId,
                ForwardedValues = new Aws.CloudFront.Inputs.DistributionOrderedCacheBehaviorForwardedValuesArgs
                {
                    QueryString = false,
                    Headers = new[]
                    {
                        "Origin",
                    },
                    Cookies = new Aws.CloudFront.Inputs.DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs
                    {
                        Forward = "none",
                    },
                },
                MinTtl = 0,
                DefaultTtl = 86400,
                MaxTtl = 31536000,
                Compress = true,
                ViewerProtocolPolicy = "redirect-to-https",
            },
            new Aws.CloudFront.Inputs.DistributionOrderedCacheBehaviorArgs
            {
                PathPattern = "/content/*",
                AllowedMethods = new[]
                {
                    "GET",
                    "HEAD",
                    "OPTIONS",
                },
                CachedMethods = new[]
                {
                    "GET",
                    "HEAD",
                },
                TargetOriginId = s3OriginId,
                ForwardedValues = new Aws.CloudFront.Inputs.DistributionOrderedCacheBehaviorForwardedValuesArgs
                {
                    QueryString = false,
                    Cookies = new Aws.CloudFront.Inputs.DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs
                    {
                        Forward = "none",
                    },
                },
                MinTtl = 0,
                DefaultTtl = 3600,
                MaxTtl = 86400,
                Compress = true,
                ViewerProtocolPolicy = "redirect-to-https",
            },
        },
        PriceClass = "PriceClass_200",
        Restrictions = new Aws.CloudFront.Inputs.DistributionRestrictionsArgs
        {
            GeoRestriction = new Aws.CloudFront.Inputs.DistributionRestrictionsGeoRestrictionArgs
            {
                RestrictionType = "whitelist",
                Locations = new[]
                {
                    "US",
                    "CA",
                    "GB",
                    "DE",
                },
            },
        },
        Tags = 
        {
            { "Environment", "production" },
        },
        ViewerCertificate = new Aws.CloudFront.Inputs.DistributionViewerCertificateArgs
        {
            AcmCertificateArn = myDomainGetCertificate.Apply(getCertificateResult => getCertificateResult.Arn),
            SslSupportMethod = "sni-only",
        },
    });

    // See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
    var originBucketPolicy = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
            {
                Sid = "AllowCloudFrontServicePrincipalReadWrite",
                Effect = "Allow",
                Principals = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
                    {
                        Type = "Service",
                        Identifiers = new[]
                        {
                            "cloudfront.amazonaws.com",
                        },
                    },
                },
                Actions = new[]
                {
                    "s3:GetObject",
                    "s3:PutObject",
                },
                Resources = new[]
                {
                    $"{b.Arn}/*",
                },
                Conditions = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementConditionInputArgs
                    {
                        Test = "StringEquals",
                        Variable = "AWS:SourceArn",
                        Values = new[]
                        {
                            s3Distribution.Arn,
                        },
                    },
                },
            },
        },
    });

    var bBucketPolicy = new Aws.S3.BucketPolicy("b", new()
    {
        Bucket = b.BucketName,
        Policy = originBucketPolicy.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
    });

    // Create Route53 records for the CloudFront distribution aliases
    var myDomainGetZone = Aws.Route53.GetZone.Invoke(new()
    {
        Name = myDomain,
    });

    var cloudfront = new List<Aws.Route53.Record>();
    foreach (var range in )
    {
        cloudfront.Add(new Aws.Route53.Record($"cloudfront-{range.Key}", new()
        {
            ZoneId = myDomainGetZone.Apply(getZoneResult => getZoneResult.ZoneId),
            Name = range.Value,
            Type = Aws.Route53.RecordType.A,
            Aliases = new[]
            {
                new Aws.Route53.Inputs.RecordAliasArgs
                {
                    Name = s3Distribution.DomainName,
                    ZoneId = s3Distribution.HostedZoneId,
                    EvaluateTargetHealth = false,
                },
            },
        }));
    }
});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.acm.AcmFunctions;
import com.pulumi.aws.acm.inputs.GetCertificateArgs;
import com.pulumi.aws.cloudfront.OriginAccessControl;
import com.pulumi.aws.cloudfront.OriginAccessControlArgs;
import com.pulumi.aws.cloudfront.Distribution;
import com.pulumi.aws.cloudfront.DistributionArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionDefaultCacheBehaviorArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionDefaultCacheBehaviorForwardedValuesArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOrderedCacheBehaviorArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOrderedCacheBehaviorForwardedValuesArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionRestrictionsArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionRestrictionsGeoRestrictionArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionViewerCertificateArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.s3.BucketPolicy;
import com.pulumi.aws.s3.BucketPolicyArgs;
import com.pulumi.aws.route53.Route53Functions;
import com.pulumi.aws.route53.inputs.GetZoneArgs;
import com.pulumi.aws.route53.Record;
import com.pulumi.aws.route53.RecordArgs;
import com.pulumi.aws.route53.inputs.RecordAliasArgs;
import com.pulumi.codegen.internal.KeyedValue;
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 b = new Bucket("b", BucketArgs.builder()
            .bucket("mybucket")
            .tags(Map.of("Name", "My bucket"))
            .build());

        final var s3OriginId = "myS3Origin";

        final var myDomain = "mydomain.com";

        final var myDomainGetCertificate = AcmFunctions.getCertificate(GetCertificateArgs.builder()
            .region("us-east-1")
            .domain(String.format("*.%s", myDomain))
            .statuses("ISSUED")
            .build());

        var default_ = new OriginAccessControl("default", OriginAccessControlArgs.builder()
            .name("default-oac")
            .originAccessControlOriginType("s3")
            .signingBehavior("always")
            .signingProtocol("sigv4")
            .build());

        var s3Distribution = new Distribution("s3Distribution", DistributionArgs.builder()
            .origins(DistributionOriginArgs.builder()
                .domainName(b.bucketRegionalDomainName())
                .originAccessControlId(default_.id())
                .originId(s3OriginId)
                .build())
            .enabled(true)
            .isIpv6Enabled(true)
            .comment("Some comment")
            .defaultRootObject("index.html")
            .aliases(            
                String.format("mysite.%s", myDomain),
                String.format("yoursite.%s", myDomain))
            .defaultCacheBehavior(DistributionDefaultCacheBehaviorArgs.builder()
                .allowedMethods(                
                    "DELETE",
                    "GET",
                    "HEAD",
                    "OPTIONS",
                    "PATCH",
                    "POST",
                    "PUT")
                .cachedMethods(                
                    "GET",
                    "HEAD")
                .targetOriginId(s3OriginId)
                .forwardedValues(DistributionDefaultCacheBehaviorForwardedValuesArgs.builder()
                    .queryString(false)
                    .cookies(DistributionDefaultCacheBehaviorForwardedValuesCookiesArgs.builder()
                        .forward("none")
                        .build())
                    .build())
                .viewerProtocolPolicy("allow-all")
                .minTtl(0)
                .defaultTtl(3600)
                .maxTtl(86400)
                .build())
            .orderedCacheBehaviors(            
                DistributionOrderedCacheBehaviorArgs.builder()
                    .pathPattern("/content/immutable/*")
                    .allowedMethods(                    
                        "GET",
                        "HEAD",
                        "OPTIONS")
                    .cachedMethods(                    
                        "GET",
                        "HEAD",
                        "OPTIONS")
                    .targetOriginId(s3OriginId)
                    .forwardedValues(DistributionOrderedCacheBehaviorForwardedValuesArgs.builder()
                        .queryString(false)
                        .headers("Origin")
                        .cookies(DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs.builder()
                            .forward("none")
                            .build())
                        .build())
                    .minTtl(0)
                    .defaultTtl(86400)
                    .maxTtl(31536000)
                    .compress(true)
                    .viewerProtocolPolicy("redirect-to-https")
                    .build(),
                DistributionOrderedCacheBehaviorArgs.builder()
                    .pathPattern("/content/*")
                    .allowedMethods(                    
                        "GET",
                        "HEAD",
                        "OPTIONS")
                    .cachedMethods(                    
                        "GET",
                        "HEAD")
                    .targetOriginId(s3OriginId)
                    .forwardedValues(DistributionOrderedCacheBehaviorForwardedValuesArgs.builder()
                        .queryString(false)
                        .cookies(DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs.builder()
                            .forward("none")
                            .build())
                        .build())
                    .minTtl(0)
                    .defaultTtl(3600)
                    .maxTtl(86400)
                    .compress(true)
                    .viewerProtocolPolicy("redirect-to-https")
                    .build())
            .priceClass("PriceClass_200")
            .restrictions(DistributionRestrictionsArgs.builder()
                .geoRestriction(DistributionRestrictionsGeoRestrictionArgs.builder()
                    .restrictionType("whitelist")
                    .locations(                    
                        "US",
                        "CA",
                        "GB",
                        "DE")
                    .build())
                .build())
            .tags(Map.of("Environment", "production"))
            .viewerCertificate(DistributionViewerCertificateArgs.builder()
                .acmCertificateArn(myDomainGetCertificate.arn())
                .sslSupportMethod("sni-only")
                .build())
            .build());

        // See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
        final var originBucketPolicy = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .sid("AllowCloudFrontServicePrincipalReadWrite")
                .effect("Allow")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .type("Service")
                    .identifiers("cloudfront.amazonaws.com")
                    .build())
                .actions(                
                    "s3:GetObject",
                    "s3:PutObject")
                .resources(b.arn().applyValue(_arn -> String.format("%s/*", _arn)))
                .conditions(GetPolicyDocumentStatementConditionArgs.builder()
                    .test("StringEquals")
                    .variable("AWS:SourceArn")
                    .values(s3Distribution.arn())
                    .build())
                .build())
            .build());

        var bBucketPolicy = new BucketPolicy("bBucketPolicy", BucketPolicyArgs.builder()
            .bucket(b.bucket())
            .policy(originBucketPolicy.applyValue(_originBucketPolicy -> _originBucketPolicy.json()))
            .build());

        // Create Route53 records for the CloudFront distribution aliases
        final var myDomainGetZone = Route53Functions.getZone(GetZoneArgs.builder()
            .name(myDomain)
            .build());

        for (var range : KeyedValue.of(s3Distribution.aliases())) {
            new Record("cloudfront-" + range.key(), RecordArgs.builder()
                .zoneId(myDomainGetZone.zoneId())
                .name(range.value())
                .type("A")
                .aliases(RecordAliasArgs.builder()
                    .name(s3Distribution.domainName())
                    .zoneId(s3Distribution.hostedZoneId())
                    .evaluateTargetHealth(false)
                    .build())
                .build());
        }

    }
}
resources:
  b:
    type: aws:s3:Bucket
    properties:
      bucket: mybucket
      tags:
        Name: My bucket
  bBucketPolicy:
    type: aws:s3:BucketPolicy
    name: b
    properties:
      bucket: ${b.bucket}
      policy: ${originBucketPolicy.json}
  default:
    type: aws:cloudfront:OriginAccessControl
    properties:
      name: default-oac
      originAccessControlOriginType: s3
      signingBehavior: always
      signingProtocol: sigv4
  s3Distribution:
    type: aws:cloudfront:Distribution
    name: s3_distribution
    properties:
      origins:
        - domainName: ${b.bucketRegionalDomainName}
          originAccessControlId: ${default.id}
          originId: ${s3OriginId}
      enabled: true
      isIpv6Enabled: true
      comment: Some comment
      defaultRootObject: index.html
      aliases:
        - mysite.${myDomain}
        - yoursite.${myDomain}
      defaultCacheBehavior:
        allowedMethods:
          - DELETE
          - GET
          - HEAD
          - OPTIONS
          - PATCH
          - POST
          - PUT
        cachedMethods:
          - GET
          - HEAD
        targetOriginId: ${s3OriginId}
        forwardedValues:
          queryString: false
          cookies:
            forward: none
        viewerProtocolPolicy: allow-all
        minTtl: 0
        defaultTtl: 3600
        maxTtl: 86400
      orderedCacheBehaviors:
        - pathPattern: /content/immutable/*
          allowedMethods:
            - GET
            - HEAD
            - OPTIONS
          cachedMethods:
            - GET
            - HEAD
            - OPTIONS
          targetOriginId: ${s3OriginId}
          forwardedValues:
            queryString: false
            headers:
              - Origin
            cookies:
              forward: none
          minTtl: 0
          defaultTtl: 86400
          maxTtl: 3.1536e+07
          compress: true
          viewerProtocolPolicy: redirect-to-https
        - pathPattern: /content/*
          allowedMethods:
            - GET
            - HEAD
            - OPTIONS
          cachedMethods:
            - GET
            - HEAD
          targetOriginId: ${s3OriginId}
          forwardedValues:
            queryString: false
            cookies:
              forward: none
          minTtl: 0
          defaultTtl: 3600
          maxTtl: 86400
          compress: true
          viewerProtocolPolicy: redirect-to-https
      priceClass: PriceClass_200
      restrictions:
        geoRestriction:
          restrictionType: whitelist
          locations:
            - US
            - CA
            - GB
            - DE
      tags:
        Environment: production
      viewerCertificate:
        acmCertificateArn: ${myDomainGetCertificate.arn}
        sslSupportMethod: sni-only
  cloudfront:
    type: aws:route53:Record
    properties:
      zoneId: ${myDomainGetZone.zoneId}
      name: ${range.value}
      type: A
      aliases:
        - name: ${s3Distribution.domainName}
          zoneId: ${s3Distribution.hostedZoneId}
          evaluateTargetHealth: false
    options: {}
variables:
  # See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
  originBucketPolicy:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - sid: AllowCloudFrontServicePrincipalReadWrite
            effect: Allow
            principals:
              - type: Service
                identifiers:
                  - cloudfront.amazonaws.com
            actions:
              - s3:GetObject
              - s3:PutObject
            resources:
              - ${b.arn}/*
            conditions:
              - test: StringEquals
                variable: AWS:SourceArn
                values:
                  - ${s3Distribution.arn}
  s3OriginId: myS3Origin
  myDomain: mydomain.com
  myDomainGetCertificate:
    fn::invoke:
      function: aws:acm:getCertificate
      arguments:
        region: us-east-1
        domain: '*.${myDomain}'
        statuses:
          - ISSUED
  # Create Route53 records for the CloudFront distribution aliases
  myDomainGetZone:
    fn::invoke:
      function: aws:route53:getZone
      arguments:
        name: ${myDomain}

The origins array defines where CloudFront fetches content. Each origin needs a domainName (the S3 bucket’s regional endpoint) and an originId that cache behaviors reference via targetOriginId. The originAccessControlId grants CloudFront permission to read from the bucket. The aliases property lists custom domains, which require a matching ACM certificate in viewerCertificate. The example includes orderedCacheBehaviors for path-specific caching rules and a bucket policy that allows CloudFront access.

Configure origin failover for high availability

When primary origins fail, CloudFront can automatically route requests to backup origins based on error codes.

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

const s3Distribution = new aws.cloudfront.Distribution("s3_distribution", {
    originGroups: [{
        originId: "groupS3",
        failoverCriteria: {
            statusCodes: [
                403,
                404,
                500,
                502,
            ],
        },
        members: [
            {
                originId: "primaryS3",
            },
            {
                originId: "failoverS3",
            },
        ],
    }],
    origins: [
        {
            domainName: primary.bucketRegionalDomainName,
            originId: "primaryS3",
            s3OriginConfig: {
                originAccessIdentity: _default.cloudfrontAccessIdentityPath,
            },
        },
        {
            domainName: failover.bucketRegionalDomainName,
            originId: "failoverS3",
            s3OriginConfig: {
                originAccessIdentity: _default.cloudfrontAccessIdentityPath,
            },
        },
    ],
    defaultCacheBehavior: {
        targetOriginId: "groupS3",
    },
});
import pulumi
import pulumi_aws as aws

s3_distribution = aws.cloudfront.Distribution("s3_distribution",
    origin_groups=[{
        "origin_id": "groupS3",
        "failover_criteria": {
            "status_codes": [
                403,
                404,
                500,
                502,
            ],
        },
        "members": [
            {
                "origin_id": "primaryS3",
            },
            {
                "origin_id": "failoverS3",
            },
        ],
    }],
    origins=[
        {
            "domain_name": primary["bucketRegionalDomainName"],
            "origin_id": "primaryS3",
            "s3_origin_config": {
                "origin_access_identity": default["cloudfrontAccessIdentityPath"],
            },
        },
        {
            "domain_name": failover["bucketRegionalDomainName"],
            "origin_id": "failoverS3",
            "s3_origin_config": {
                "origin_access_identity": default["cloudfrontAccessIdentityPath"],
            },
        },
    ],
    default_cache_behavior={
        "target_origin_id": "groupS3",
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := cloudfront.NewDistribution(ctx, "s3_distribution", &cloudfront.DistributionArgs{
			OriginGroups: cloudfront.DistributionOriginGroupArray{
				&cloudfront.DistributionOriginGroupArgs{
					OriginId: pulumi.String("groupS3"),
					FailoverCriteria: &cloudfront.DistributionOriginGroupFailoverCriteriaArgs{
						StatusCodes: pulumi.IntArray{
							pulumi.Int(403),
							pulumi.Int(404),
							pulumi.Int(500),
							pulumi.Int(502),
						},
					},
					Members: cloudfront.DistributionOriginGroupMemberArray{
						&cloudfront.DistributionOriginGroupMemberArgs{
							OriginId: pulumi.String("primaryS3"),
						},
						&cloudfront.DistributionOriginGroupMemberArgs{
							OriginId: pulumi.String("failoverS3"),
						},
					},
				},
			},
			Origins: cloudfront.DistributionOriginArray{
				&cloudfront.DistributionOriginArgs{
					DomainName: pulumi.Any(primary.BucketRegionalDomainName),
					OriginId:   pulumi.String("primaryS3"),
					S3OriginConfig: &cloudfront.DistributionOriginS3OriginConfigArgs{
						OriginAccessIdentity: pulumi.Any(_default.CloudfrontAccessIdentityPath),
					},
				},
				&cloudfront.DistributionOriginArgs{
					DomainName: pulumi.Any(failover.BucketRegionalDomainName),
					OriginId:   pulumi.String("failoverS3"),
					S3OriginConfig: &cloudfront.DistributionOriginS3OriginConfigArgs{
						OriginAccessIdentity: pulumi.Any(_default.CloudfrontAccessIdentityPath),
					},
				},
			},
			DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
				TargetOriginId: pulumi.String("groupS3"),
			},
		})
		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 s3Distribution = new Aws.CloudFront.Distribution("s3_distribution", new()
    {
        OriginGroups = new[]
        {
            new Aws.CloudFront.Inputs.DistributionOriginGroupArgs
            {
                OriginId = "groupS3",
                FailoverCriteria = new Aws.CloudFront.Inputs.DistributionOriginGroupFailoverCriteriaArgs
                {
                    StatusCodes = new[]
                    {
                        403,
                        404,
                        500,
                        502,
                    },
                },
                Members = new[]
                {
                    new Aws.CloudFront.Inputs.DistributionOriginGroupMemberArgs
                    {
                        OriginId = "primaryS3",
                    },
                    new Aws.CloudFront.Inputs.DistributionOriginGroupMemberArgs
                    {
                        OriginId = "failoverS3",
                    },
                },
            },
        },
        Origins = new[]
        {
            new Aws.CloudFront.Inputs.DistributionOriginArgs
            {
                DomainName = primary.BucketRegionalDomainName,
                OriginId = "primaryS3",
                S3OriginConfig = new Aws.CloudFront.Inputs.DistributionOriginS3OriginConfigArgs
                {
                    OriginAccessIdentity = @default.CloudfrontAccessIdentityPath,
                },
            },
            new Aws.CloudFront.Inputs.DistributionOriginArgs
            {
                DomainName = failover.BucketRegionalDomainName,
                OriginId = "failoverS3",
                S3OriginConfig = new Aws.CloudFront.Inputs.DistributionOriginS3OriginConfigArgs
                {
                    OriginAccessIdentity = @default.CloudfrontAccessIdentityPath,
                },
            },
        },
        DefaultCacheBehavior = new Aws.CloudFront.Inputs.DistributionDefaultCacheBehaviorArgs
        {
            TargetOriginId = "groupS3",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.Distribution;
import com.pulumi.aws.cloudfront.DistributionArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginGroupArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginGroupFailoverCriteriaArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginS3OriginConfigArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionDefaultCacheBehaviorArgs;
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 s3Distribution = new Distribution("s3Distribution", DistributionArgs.builder()
            .originGroups(DistributionOriginGroupArgs.builder()
                .originId("groupS3")
                .failoverCriteria(DistributionOriginGroupFailoverCriteriaArgs.builder()
                    .statusCodes(                    
                        403,
                        404,
                        500,
                        502)
                    .build())
                .members(                
                    DistributionOriginGroupMemberArgs.builder()
                        .originId("primaryS3")
                        .build(),
                    DistributionOriginGroupMemberArgs.builder()
                        .originId("failoverS3")
                        .build())
                .build())
            .origins(            
                DistributionOriginArgs.builder()
                    .domainName(primary.bucketRegionalDomainName())
                    .originId("primaryS3")
                    .s3OriginConfig(DistributionOriginS3OriginConfigArgs.builder()
                        .originAccessIdentity(default_.cloudfrontAccessIdentityPath())
                        .build())
                    .build(),
                DistributionOriginArgs.builder()
                    .domainName(failover.bucketRegionalDomainName())
                    .originId("failoverS3")
                    .s3OriginConfig(DistributionOriginS3OriginConfigArgs.builder()
                        .originAccessIdentity(default_.cloudfrontAccessIdentityPath())
                        .build())
                    .build())
            .defaultCacheBehavior(DistributionDefaultCacheBehaviorArgs.builder()
                .targetOriginId("groupS3")
                .build())
            .build());

    }
}
resources:
  s3Distribution:
    type: aws:cloudfront:Distribution
    name: s3_distribution
    properties:
      originGroups:
        - originId: groupS3
          failoverCriteria:
            statusCodes:
              - 403
              - 404
              - 500
              - 502
          members:
            - originId: primaryS3
            - originId: failoverS3
      origins:
        - domainName: ${primary.bucketRegionalDomainName}
          originId: primaryS3
          s3OriginConfig:
            originAccessIdentity: ${default.cloudfrontAccessIdentityPath}
        - domainName: ${failover.bucketRegionalDomainName}
          originId: failoverS3
          s3OriginConfig:
            originAccessIdentity: ${default.cloudfrontAccessIdentityPath}
      defaultCacheBehavior:
        targetOriginId: groupS3

The originGroups array defines failover logic. The failoverCriteria specifies which HTTP status codes trigger failover (403, 404, 500, 502). The members array lists origins in priority order: CloudFront tries primaryS3 first, then failoverS3. The defaultCacheBehavior references the origin group by its originId rather than a single origin.

Use AWS managed caching policies

AWS provides pre-configured caching policies that implement common patterns without manual cache configuration.

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

const s3OriginId = "myS3Origin";
const s3Distribution = new aws.cloudfront.Distribution("s3_distribution", {
    origins: [{
        domainName: primary.bucketRegionalDomainName,
        originId: "myS3Origin",
        s3OriginConfig: {
            originAccessIdentity: _default.cloudfrontAccessIdentityPath,
        },
    }],
    enabled: true,
    isIpv6Enabled: true,
    comment: "Some comment",
    defaultRootObject: "index.html",
    defaultCacheBehavior: {
        cachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
        allowedMethods: [
            "GET",
            "HEAD",
            "OPTIONS",
        ],
        cachedMethods: [
            "GET",
            "HEAD",
        ],
        targetOriginId: s3OriginId,
        viewerProtocolPolicy: "allow-all",
    },
    restrictions: {
        geoRestriction: {
            restrictionType: "whitelist",
            locations: [
                "US",
                "CA",
                "GB",
                "DE",
            ],
        },
    },
    viewerCertificate: {
        cloudfrontDefaultCertificate: true,
    },
});
import pulumi
import pulumi_aws as aws

s3_origin_id = "myS3Origin"
s3_distribution = aws.cloudfront.Distribution("s3_distribution",
    origins=[{
        "domain_name": primary["bucketRegionalDomainName"],
        "origin_id": "myS3Origin",
        "s3_origin_config": {
            "origin_access_identity": default["cloudfrontAccessIdentityPath"],
        },
    }],
    enabled=True,
    is_ipv6_enabled=True,
    comment="Some comment",
    default_root_object="index.html",
    default_cache_behavior={
        "cache_policy_id": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
        "allowed_methods": [
            "GET",
            "HEAD",
            "OPTIONS",
        ],
        "cached_methods": [
            "GET",
            "HEAD",
        ],
        "target_origin_id": s3_origin_id,
        "viewer_protocol_policy": "allow-all",
    },
    restrictions={
        "geo_restriction": {
            "restriction_type": "whitelist",
            "locations": [
                "US",
                "CA",
                "GB",
                "DE",
            ],
        },
    },
    viewer_certificate={
        "cloudfront_default_certificate": True,
    })
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		s3OriginId := "myS3Origin"
		_, err := cloudfront.NewDistribution(ctx, "s3_distribution", &cloudfront.DistributionArgs{
			Origins: cloudfront.DistributionOriginArray{
				&cloudfront.DistributionOriginArgs{
					DomainName: pulumi.Any(primary.BucketRegionalDomainName),
					OriginId:   pulumi.String("myS3Origin"),
					S3OriginConfig: &cloudfront.DistributionOriginS3OriginConfigArgs{
						OriginAccessIdentity: pulumi.Any(_default.CloudfrontAccessIdentityPath),
					},
				},
			},
			Enabled:           pulumi.Bool(true),
			IsIpv6Enabled:     pulumi.Bool(true),
			Comment:           pulumi.String("Some comment"),
			DefaultRootObject: pulumi.String("index.html"),
			DefaultCacheBehavior: &cloudfront.DistributionDefaultCacheBehaviorArgs{
				CachePolicyId: pulumi.String("4135ea2d-6df8-44a3-9df3-4b5a84be39ad"),
				AllowedMethods: pulumi.StringArray{
					pulumi.String("GET"),
					pulumi.String("HEAD"),
					pulumi.String("OPTIONS"),
				},
				CachedMethods: pulumi.StringArray{
					pulumi.String("GET"),
					pulumi.String("HEAD"),
				},
				TargetOriginId:       pulumi.String(s3OriginId),
				ViewerProtocolPolicy: pulumi.String("allow-all"),
			},
			Restrictions: &cloudfront.DistributionRestrictionsArgs{
				GeoRestriction: &cloudfront.DistributionRestrictionsGeoRestrictionArgs{
					RestrictionType: pulumi.String("whitelist"),
					Locations: pulumi.StringArray{
						pulumi.String("US"),
						pulumi.String("CA"),
						pulumi.String("GB"),
						pulumi.String("DE"),
					},
				},
			},
			ViewerCertificate: &cloudfront.DistributionViewerCertificateArgs{
				CloudfrontDefaultCertificate: pulumi.Bool(true),
			},
		})
		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 s3OriginId = "myS3Origin";

    var s3Distribution = new Aws.CloudFront.Distribution("s3_distribution", new()
    {
        Origins = new[]
        {
            new Aws.CloudFront.Inputs.DistributionOriginArgs
            {
                DomainName = primary.BucketRegionalDomainName,
                OriginId = "myS3Origin",
                S3OriginConfig = new Aws.CloudFront.Inputs.DistributionOriginS3OriginConfigArgs
                {
                    OriginAccessIdentity = @default.CloudfrontAccessIdentityPath,
                },
            },
        },
        Enabled = true,
        IsIpv6Enabled = true,
        Comment = "Some comment",
        DefaultRootObject = "index.html",
        DefaultCacheBehavior = new Aws.CloudFront.Inputs.DistributionDefaultCacheBehaviorArgs
        {
            CachePolicyId = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
            AllowedMethods = new[]
            {
                "GET",
                "HEAD",
                "OPTIONS",
            },
            CachedMethods = new[]
            {
                "GET",
                "HEAD",
            },
            TargetOriginId = s3OriginId,
            ViewerProtocolPolicy = "allow-all",
        },
        Restrictions = new Aws.CloudFront.Inputs.DistributionRestrictionsArgs
        {
            GeoRestriction = new Aws.CloudFront.Inputs.DistributionRestrictionsGeoRestrictionArgs
            {
                RestrictionType = "whitelist",
                Locations = new[]
                {
                    "US",
                    "CA",
                    "GB",
                    "DE",
                },
            },
        },
        ViewerCertificate = new Aws.CloudFront.Inputs.DistributionViewerCertificateArgs
        {
            CloudfrontDefaultCertificate = true,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.Distribution;
import com.pulumi.aws.cloudfront.DistributionArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionOriginS3OriginConfigArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionDefaultCacheBehaviorArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionRestrictionsArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionRestrictionsGeoRestrictionArgs;
import com.pulumi.aws.cloudfront.inputs.DistributionViewerCertificateArgs;
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) {
        final var s3OriginId = "myS3Origin";

        var s3Distribution = new Distribution("s3Distribution", DistributionArgs.builder()
            .origins(DistributionOriginArgs.builder()
                .domainName(primary.bucketRegionalDomainName())
                .originId("myS3Origin")
                .s3OriginConfig(DistributionOriginS3OriginConfigArgs.builder()
                    .originAccessIdentity(default_.cloudfrontAccessIdentityPath())
                    .build())
                .build())
            .enabled(true)
            .isIpv6Enabled(true)
            .comment("Some comment")
            .defaultRootObject("index.html")
            .defaultCacheBehavior(DistributionDefaultCacheBehaviorArgs.builder()
                .cachePolicyId("4135ea2d-6df8-44a3-9df3-4b5a84be39ad")
                .allowedMethods(                
                    "GET",
                    "HEAD",
                    "OPTIONS")
                .cachedMethods(                
                    "GET",
                    "HEAD")
                .targetOriginId(s3OriginId)
                .viewerProtocolPolicy("allow-all")
                .build())
            .restrictions(DistributionRestrictionsArgs.builder()
                .geoRestriction(DistributionRestrictionsGeoRestrictionArgs.builder()
                    .restrictionType("whitelist")
                    .locations(                    
                        "US",
                        "CA",
                        "GB",
                        "DE")
                    .build())
                .build())
            .viewerCertificate(DistributionViewerCertificateArgs.builder()
                .cloudfrontDefaultCertificate(true)
                .build())
            .build());

    }
}
resources:
  s3Distribution:
    type: aws:cloudfront:Distribution
    name: s3_distribution
    properties:
      origins:
        - domainName: ${primary.bucketRegionalDomainName}
          originId: myS3Origin
          s3OriginConfig:
            originAccessIdentity: ${default.cloudfrontAccessIdentityPath}
      enabled: true
      isIpv6Enabled: true
      comment: Some comment
      defaultRootObject: index.html
      defaultCacheBehavior:
        cachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
        allowedMethods:
          - GET
          - HEAD
          - OPTIONS
        cachedMethods:
          - GET
          - HEAD
        targetOriginId: ${s3OriginId}
        viewerProtocolPolicy: allow-all
      restrictions:
        geoRestriction:
          restrictionType: whitelist
          locations:
            - US
            - CA
            - GB
            - DE
      viewerCertificate:
        cloudfrontDefaultCertificate: true
variables:
  s3OriginId: myS3Origin

The cachePolicyId property references an AWS-managed policy by ID. This replaces the forwardedValues configuration from earlier examples. Managed policies handle query strings, headers, and cookies according to AWS best practices. The example uses policy ID “4135ea2d-6df8-44a3-9df3-4b5a84be39ad” (CachingOptimized).

Send access logs to S3 with standard logging V2

CloudFront’s standard logging V2 delivers access logs to S3 in Parquet format for structured analytics.

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

const example = new aws.cloudfront.Distribution("example", {});
const exampleLogDeliverySource = new aws.cloudwatch.LogDeliverySource("example", {
    region: "us-east-1",
    name: "example",
    logType: "ACCESS_LOGS",
    resourceArn: example.arn,
});
const exampleBucket = new aws.s3.Bucket("example", {
    bucket: "testbucket",
    forceDestroy: true,
});
const exampleLogDeliveryDestination = new aws.cloudwatch.LogDeliveryDestination("example", {
    region: "us-east-1",
    name: "s3-destination",
    outputFormat: "parquet",
    deliveryDestinationConfiguration: {
        destinationResourceArn: pulumi.interpolate`${exampleBucket.arn}/prefix`,
    },
});
const exampleLogDelivery = new aws.cloudwatch.LogDelivery("example", {
    region: "us-east-1",
    deliverySourceName: exampleLogDeliverySource.name,
    deliveryDestinationArn: exampleLogDeliveryDestination.arn,
    s3DeliveryConfigurations: [{
        suffixPath: "/123456678910/{DistributionId}/{yyyy}/{MM}/{dd}/{HH}",
    }],
});
import pulumi
import pulumi_aws as aws

example = aws.cloudfront.Distribution("example")
example_log_delivery_source = aws.cloudwatch.LogDeliverySource("example",
    region="us-east-1",
    name="example",
    log_type="ACCESS_LOGS",
    resource_arn=example.arn)
example_bucket = aws.s3.Bucket("example",
    bucket="testbucket",
    force_destroy=True)
example_log_delivery_destination = aws.cloudwatch.LogDeliveryDestination("example",
    region="us-east-1",
    name="s3-destination",
    output_format="parquet",
    delivery_destination_configuration={
        "destination_resource_arn": example_bucket.arn.apply(lambda arn: f"{arn}/prefix"),
    })
example_log_delivery = aws.cloudwatch.LogDelivery("example",
    region="us-east-1",
    delivery_source_name=example_log_delivery_source.name,
    delivery_destination_arn=example_log_delivery_destination.arn,
    s3_delivery_configurations=[{
        "suffix_path": "/123456678910/{DistributionId}/{yyyy}/{MM}/{dd}/{HH}",
    }])
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudwatch"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/s3"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := cloudfront.NewDistribution(ctx, "example", nil)
		if err != nil {
			return err
		}
		exampleLogDeliverySource, err := cloudwatch.NewLogDeliverySource(ctx, "example", &cloudwatch.LogDeliverySourceArgs{
			Region:      pulumi.String("us-east-1"),
			Name:        pulumi.String("example"),
			LogType:     pulumi.String("ACCESS_LOGS"),
			ResourceArn: example.Arn,
		})
		if err != nil {
			return err
		}
		exampleBucket, err := s3.NewBucket(ctx, "example", &s3.BucketArgs{
			Bucket:       pulumi.String("testbucket"),
			ForceDestroy: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		exampleLogDeliveryDestination, err := cloudwatch.NewLogDeliveryDestination(ctx, "example", &cloudwatch.LogDeliveryDestinationArgs{
			Region:       pulumi.String("us-east-1"),
			Name:         pulumi.String("s3-destination"),
			OutputFormat: pulumi.String("parquet"),
			DeliveryDestinationConfiguration: &cloudwatch.LogDeliveryDestinationDeliveryDestinationConfigurationArgs{
				DestinationResourceArn: exampleBucket.Arn.ApplyT(func(arn string) (string, error) {
					return fmt.Sprintf("%v/prefix", arn), nil
				}).(pulumi.StringOutput),
			},
		})
		if err != nil {
			return err
		}
		_, err = cloudwatch.NewLogDelivery(ctx, "example", &cloudwatch.LogDeliveryArgs{
			Region:                 pulumi.String("us-east-1"),
			DeliverySourceName:     exampleLogDeliverySource.Name,
			DeliveryDestinationArn: exampleLogDeliveryDestination.Arn,
			S3DeliveryConfigurations: cloudwatch.LogDeliveryS3DeliveryConfigurationArray{
				&cloudwatch.LogDeliveryS3DeliveryConfigurationArgs{
					SuffixPath: pulumi.String("/123456678910/{DistributionId}/{yyyy}/{MM}/{dd}/{HH}"),
				},
			},
		})
		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 example = new Aws.CloudFront.Distribution("example");

    var exampleLogDeliverySource = new Aws.CloudWatch.LogDeliverySource("example", new()
    {
        Region = "us-east-1",
        Name = "example",
        LogType = "ACCESS_LOGS",
        ResourceArn = example.Arn,
    });

    var exampleBucket = new Aws.S3.Bucket("example", new()
    {
        BucketName = "testbucket",
        ForceDestroy = true,
    });

    var exampleLogDeliveryDestination = new Aws.CloudWatch.LogDeliveryDestination("example", new()
    {
        Region = "us-east-1",
        Name = "s3-destination",
        OutputFormat = "parquet",
        DeliveryDestinationConfiguration = new Aws.CloudWatch.Inputs.LogDeliveryDestinationDeliveryDestinationConfigurationArgs
        {
            DestinationResourceArn = exampleBucket.Arn.Apply(arn => $"{arn}/prefix"),
        },
    });

    var exampleLogDelivery = new Aws.CloudWatch.LogDelivery("example", new()
    {
        Region = "us-east-1",
        DeliverySourceName = exampleLogDeliverySource.Name,
        DeliveryDestinationArn = exampleLogDeliveryDestination.Arn,
        S3DeliveryConfigurations = new[]
        {
            new Aws.CloudWatch.Inputs.LogDeliveryS3DeliveryConfigurationArgs
            {
                SuffixPath = "/123456678910/{DistributionId}/{yyyy}/{MM}/{dd}/{HH}",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.Distribution;
import com.pulumi.aws.cloudwatch.LogDeliverySource;
import com.pulumi.aws.cloudwatch.LogDeliverySourceArgs;
import com.pulumi.aws.s3.Bucket;
import com.pulumi.aws.s3.BucketArgs;
import com.pulumi.aws.cloudwatch.LogDeliveryDestination;
import com.pulumi.aws.cloudwatch.LogDeliveryDestinationArgs;
import com.pulumi.aws.cloudwatch.inputs.LogDeliveryDestinationDeliveryDestinationConfigurationArgs;
import com.pulumi.aws.cloudwatch.LogDelivery;
import com.pulumi.aws.cloudwatch.LogDeliveryArgs;
import com.pulumi.aws.cloudwatch.inputs.LogDeliveryS3DeliveryConfigurationArgs;
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 Distribution("example");

        var exampleLogDeliverySource = new LogDeliverySource("exampleLogDeliverySource", LogDeliverySourceArgs.builder()
            .region("us-east-1")
            .name("example")
            .logType("ACCESS_LOGS")
            .resourceArn(example.arn())
            .build());

        var exampleBucket = new Bucket("exampleBucket", BucketArgs.builder()
            .bucket("testbucket")
            .forceDestroy(true)
            .build());

        var exampleLogDeliveryDestination = new LogDeliveryDestination("exampleLogDeliveryDestination", LogDeliveryDestinationArgs.builder()
            .region("us-east-1")
            .name("s3-destination")
            .outputFormat("parquet")
            .deliveryDestinationConfiguration(LogDeliveryDestinationDeliveryDestinationConfigurationArgs.builder()
                .destinationResourceArn(exampleBucket.arn().applyValue(_arn -> String.format("%s/prefix", _arn)))
                .build())
            .build());

        var exampleLogDelivery = new LogDelivery("exampleLogDelivery", LogDeliveryArgs.builder()
            .region("us-east-1")
            .deliverySourceName(exampleLogDeliverySource.name())
            .deliveryDestinationArn(exampleLogDeliveryDestination.arn())
            .s3DeliveryConfigurations(LogDeliveryS3DeliveryConfigurationArgs.builder()
                .suffixPath("/123456678910/{DistributionId}/{yyyy}/{MM}/{dd}/{HH}")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:cloudfront:Distribution
  exampleLogDeliverySource:
    type: aws:cloudwatch:LogDeliverySource
    name: example
    properties:
      region: us-east-1
      name: example
      logType: ACCESS_LOGS
      resourceArn: ${example.arn}
  exampleBucket:
    type: aws:s3:Bucket
    name: example
    properties:
      bucket: testbucket
      forceDestroy: true
  exampleLogDeliveryDestination:
    type: aws:cloudwatch:LogDeliveryDestination
    name: example
    properties:
      region: us-east-1
      name: s3-destination
      outputFormat: parquet
      deliveryDestinationConfiguration:
        destinationResourceArn: ${exampleBucket.arn}/prefix
  exampleLogDelivery:
    type: aws:cloudwatch:LogDelivery
    name: example
    properties:
      region: us-east-1
      deliverySourceName: ${exampleLogDeliverySource.name}
      deliveryDestinationArn: ${exampleLogDeliveryDestination.arn}
      s3DeliveryConfigurations:
        - suffixPath: /123456678910/{DistributionId}/{yyyy}/{MM}/{dd}/{HH}

The LogDeliverySource identifies the distribution as a log source. The LogDeliveryDestination configures S3 as the target, with outputFormat set to “parquet” for efficient querying. The LogDelivery resource connects source to destination, with s3DeliveryConfigurations defining the path structure for log files. This is CloudFront’s newer logging approach; the loggingConfig property configures legacy logging.

Stream access logs to Kinesis Data Firehose

For real-time processing, CloudFront can stream access logs to Kinesis Data Firehose.

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

const example = new aws.cloudfront.Distribution("example", {});
const cloudfrontLogs = new aws.kinesis.FirehoseDeliveryStream("cloudfront_logs", {
    region: "us-east-1",
    tags: {
        LogDeliveryEnabled: "true",
    },
});
const exampleLogDeliverySource = new aws.cloudwatch.LogDeliverySource("example", {
    region: "us-east-1",
    name: "cloudfront-logs-source",
    logType: "ACCESS_LOGS",
    resourceArn: example.arn,
});
const exampleLogDeliveryDestination = new aws.cloudwatch.LogDeliveryDestination("example", {
    region: "us-east-1",
    name: "firehose-destination",
    outputFormat: "json",
    deliveryDestinationConfiguration: {
        destinationResourceArn: cloudfrontLogs.arn,
    },
});
const exampleLogDelivery = new aws.cloudwatch.LogDelivery("example", {
    region: "us-east-1",
    deliverySourceName: exampleLogDeliverySource.name,
    deliveryDestinationArn: exampleLogDeliveryDestination.arn,
});
import pulumi
import pulumi_aws as aws

example = aws.cloudfront.Distribution("example")
cloudfront_logs = aws.kinesis.FirehoseDeliveryStream("cloudfront_logs",
    region="us-east-1",
    tags={
        "LogDeliveryEnabled": "true",
    })
example_log_delivery_source = aws.cloudwatch.LogDeliverySource("example",
    region="us-east-1",
    name="cloudfront-logs-source",
    log_type="ACCESS_LOGS",
    resource_arn=example.arn)
example_log_delivery_destination = aws.cloudwatch.LogDeliveryDestination("example",
    region="us-east-1",
    name="firehose-destination",
    output_format="json",
    delivery_destination_configuration={
        "destination_resource_arn": cloudfront_logs.arn,
    })
example_log_delivery = aws.cloudwatch.LogDelivery("example",
    region="us-east-1",
    delivery_source_name=example_log_delivery_source.name,
    delivery_destination_arn=example_log_delivery_destination.arn)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudfront"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudwatch"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/kinesis"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		example, err := cloudfront.NewDistribution(ctx, "example", nil)
		if err != nil {
			return err
		}
		cloudfrontLogs, err := kinesis.NewFirehoseDeliveryStream(ctx, "cloudfront_logs", &kinesis.FirehoseDeliveryStreamArgs{
			Region: pulumi.String("us-east-1"),
			Tags: pulumi.StringMap{
				"LogDeliveryEnabled": pulumi.String("true"),
			},
		})
		if err != nil {
			return err
		}
		exampleLogDeliverySource, err := cloudwatch.NewLogDeliverySource(ctx, "example", &cloudwatch.LogDeliverySourceArgs{
			Region:      pulumi.String("us-east-1"),
			Name:        pulumi.String("cloudfront-logs-source"),
			LogType:     pulumi.String("ACCESS_LOGS"),
			ResourceArn: example.Arn,
		})
		if err != nil {
			return err
		}
		exampleLogDeliveryDestination, err := cloudwatch.NewLogDeliveryDestination(ctx, "example", &cloudwatch.LogDeliveryDestinationArgs{
			Region:       pulumi.String("us-east-1"),
			Name:         pulumi.String("firehose-destination"),
			OutputFormat: pulumi.String("json"),
			DeliveryDestinationConfiguration: &cloudwatch.LogDeliveryDestinationDeliveryDestinationConfigurationArgs{
				DestinationResourceArn: cloudfrontLogs.Arn,
			},
		})
		if err != nil {
			return err
		}
		_, err = cloudwatch.NewLogDelivery(ctx, "example", &cloudwatch.LogDeliveryArgs{
			Region:                 pulumi.String("us-east-1"),
			DeliverySourceName:     exampleLogDeliverySource.Name,
			DeliveryDestinationArn: exampleLogDeliveryDestination.Arn,
		})
		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 example = new Aws.CloudFront.Distribution("example");

    var cloudfrontLogs = new Aws.Kinesis.FirehoseDeliveryStream("cloudfront_logs", new()
    {
        Region = "us-east-1",
        Tags = 
        {
            { "LogDeliveryEnabled", "true" },
        },
    });

    var exampleLogDeliverySource = new Aws.CloudWatch.LogDeliverySource("example", new()
    {
        Region = "us-east-1",
        Name = "cloudfront-logs-source",
        LogType = "ACCESS_LOGS",
        ResourceArn = example.Arn,
    });

    var exampleLogDeliveryDestination = new Aws.CloudWatch.LogDeliveryDestination("example", new()
    {
        Region = "us-east-1",
        Name = "firehose-destination",
        OutputFormat = "json",
        DeliveryDestinationConfiguration = new Aws.CloudWatch.Inputs.LogDeliveryDestinationDeliveryDestinationConfigurationArgs
        {
            DestinationResourceArn = cloudfrontLogs.Arn,
        },
    });

    var exampleLogDelivery = new Aws.CloudWatch.LogDelivery("example", new()
    {
        Region = "us-east-1",
        DeliverySourceName = exampleLogDeliverySource.Name,
        DeliveryDestinationArn = exampleLogDeliveryDestination.Arn,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudfront.Distribution;
import com.pulumi.aws.kinesis.FirehoseDeliveryStream;
import com.pulumi.aws.kinesis.FirehoseDeliveryStreamArgs;
import com.pulumi.aws.cloudwatch.LogDeliverySource;
import com.pulumi.aws.cloudwatch.LogDeliverySourceArgs;
import com.pulumi.aws.cloudwatch.LogDeliveryDestination;
import com.pulumi.aws.cloudwatch.LogDeliveryDestinationArgs;
import com.pulumi.aws.cloudwatch.inputs.LogDeliveryDestinationDeliveryDestinationConfigurationArgs;
import com.pulumi.aws.cloudwatch.LogDelivery;
import com.pulumi.aws.cloudwatch.LogDeliveryArgs;
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 Distribution("example");

        var cloudfrontLogs = new FirehoseDeliveryStream("cloudfrontLogs", FirehoseDeliveryStreamArgs.builder()
            .region("us-east-1")
            .tags(Map.of("LogDeliveryEnabled", "true"))
            .build());

        var exampleLogDeliverySource = new LogDeliverySource("exampleLogDeliverySource", LogDeliverySourceArgs.builder()
            .region("us-east-1")
            .name("cloudfront-logs-source")
            .logType("ACCESS_LOGS")
            .resourceArn(example.arn())
            .build());

        var exampleLogDeliveryDestination = new LogDeliveryDestination("exampleLogDeliveryDestination", LogDeliveryDestinationArgs.builder()
            .region("us-east-1")
            .name("firehose-destination")
            .outputFormat("json")
            .deliveryDestinationConfiguration(LogDeliveryDestinationDeliveryDestinationConfigurationArgs.builder()
                .destinationResourceArn(cloudfrontLogs.arn())
                .build())
            .build());

        var exampleLogDelivery = new LogDelivery("exampleLogDelivery", LogDeliveryArgs.builder()
            .region("us-east-1")
            .deliverySourceName(exampleLogDeliverySource.name())
            .deliveryDestinationArn(exampleLogDeliveryDestination.arn())
            .build());

    }
}
resources:
  example:
    type: aws:cloudfront:Distribution
  cloudfrontLogs:
    type: aws:kinesis:FirehoseDeliveryStream
    name: cloudfront_logs
    properties:
      region: us-east-1
      tags:
        LogDeliveryEnabled: 'true'
  exampleLogDeliverySource:
    type: aws:cloudwatch:LogDeliverySource
    name: example
    properties:
      region: us-east-1
      name: cloudfront-logs-source
      logType: ACCESS_LOGS
      resourceArn: ${example.arn}
  exampleLogDeliveryDestination:
    type: aws:cloudwatch:LogDeliveryDestination
    name: example
    properties:
      region: us-east-1
      name: firehose-destination
      outputFormat: json
      deliveryDestinationConfiguration:
        destinationResourceArn: ${cloudfrontLogs.arn}
  exampleLogDelivery:
    type: aws:cloudwatch:LogDelivery
    name: example
    properties:
      region: us-east-1
      deliverySourceName: ${exampleLogDeliverySource.name}
      deliveryDestinationArn: ${exampleLogDeliveryDestination.arn}

The LogDeliveryDestination points to a Firehose stream instead of S3, with outputFormat set to “json” for streaming. The Firehose stream must have the tag LogDeliveryEnabled set to “true”. From Firehose, logs can flow to S3, Redshift, Elasticsearch, or custom processors.

Beyond these examples

These snippets focus on specific distribution-level features: S3 origin configuration with custom domains, origin failover and managed caching policies, and standard logging V2 to S3 and Firehose. They’re intentionally minimal rather than full CDN deployments.

The examples may reference pre-existing infrastructure such as S3 buckets, ACM certificates, Route53 hosted zones, origin access identities or origin access controls, and Kinesis Firehose streams for logging. They focus on configuring the distribution rather than provisioning everything around it.

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

  • Path-based cache behaviors (orderedCacheBehaviors)
  • Custom error responses and WAF integration
  • Geographic restrictions and price class selection
  • Lambda@Edge function associations

These omissions are intentional: the goal is to illustrate how each distribution feature is wired, not provide drop-in CDN modules. See the CloudFront Distribution resource reference for all available configuration options.

Let's create AWS CloudFront Distributions

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Deployment & Lifecycle
Why does creating or updating my CloudFront distribution take so long?
CloudFront distributions take about 15 minutes to reach a deployed state after creation or modification. During this time, delete operations are blocked.
How can I delete an enabled distribution without waiting 15 minutes?
Set retainOnDelete to true to disable the distribution instead of deleting it. Alternatively, set waitForDeployment to false to skip waiting for deployment completion (default is true).
Can I change the staging property after creating my distribution?
No, the staging property is immutable and cannot be changed after distribution creation.
Caching & Behaviors
Should I use cachePolicyId or forwardedValues for cache behavior?
Use cachePolicyId (preferred). The forwardedValues option is deprecated. The defaultCacheBehavior requires either cachePolicyId or forwardedValues to be set.
What HTTP versions does CloudFront support?
CloudFront supports http1.1, http2, http2and3, and http3. The default is http2.
Origins & Access Control
How do I set up origin access control for S3?
Create an aws.cloudfront.OriginAccessControl resource, reference it in originAccessControlId, and add an S3 bucket policy allowing the CloudFront service principal with a condition on AWS:SourceArn matching your distribution’s ARN.
How do I configure failover routing between origins?
Use originGroups with failoverCriteria specifying status codes (such as 403, 404, 500, 502) and members pointing to your primary and failover origins.
Security & Certificates
What region must my ACM certificate be in for CloudFront?
ACM certificates for CloudFront must be in the us-east-1 region.
Why am I getting errors when associating a WAF web ACL?
The WAF Web ACL must exist in the WAF Global (CloudFront) region, and your credentials must have waf:GetWebACL permissions. For WAFv2, use the ACL ARN; for WAF Classic, use the ACL ID.
Logging
What's the difference between legacy logging and v2 logging?
AWS provides two versions of CloudFront access logs. The loggingConfig property configures legacy version standard logs, while v2 logging uses separate aws.cloudwatch.LogDeliverySource, LogDeliveryDestination, and LogDelivery resources.
How do I enable v2 logging to S3?
Create aws.cloudwatch.LogDeliverySource, LogDeliveryDestination (with outputFormat set to parquet), and LogDelivery resources. Use s3DeliveryConfigurations to customize the delivery path.
How do I enable v2 logging to Data Firehose?
Create a Firehose delivery stream with the tag LogDeliveryEnabled: "true", then create LogDeliverySource, LogDeliveryDestination (with outputFormat set to json), and LogDelivery resources.
DNS & Routing
What's the CloudFront hosted zone ID for Route53 alias records?
The hostedZoneId for all CloudFront distributions is always Z2FDTNDATAQYW2. Use this value when creating Route53 alias records pointing to your distribution’s domainName.

Using a different cloud?

Explore networking guides for other cloud providers: