Uploading to S3 Through CloudFront Pre-Signed URLs
CloudFront signed URLs let you upload to S3 through a custom domain—useful when direct S3 pre-signed URLs are not an option.
CloudFront supports the feature of generating signed URLs. While S3 also offers similar functionality, CloudFront provides the added benefit of enabling uploads through your custom domain, making it especially useful for domain-restricted environments.
Serve private content with signed URLs and signed cookies

Specifying Trusted Signers
To begin, you must create a trusted key group for use as a trusted signer.
Specify signers that can create signed URLs and signed cookies
While you can use your AWS account as a trusted signer, AWS recommends using a key group. Refer to Choose between trusted key groups (recommended) and AWS accounts for details.
Key pairs must adhere to the following requirements:
- Type: SSH-2 RSA key pair
- Format: Base64-encoded PEM
- Key Size: 2048-bit
Use the following commands to create a key pair:
openssl genrsa -out private_key.pem 2048openssl rsa -pubout -in private_key.pem -out public_key.pemBuilding
- Pass the public key to the
PublicKeyparameter (line 5) and use it (line 38). - Ensure the S3 bucket policy allows the
s3:PutObjectaction (line 27). - Use the AllViewerExceptHostHeader origin request policy (line 85).
AWSTemplateFormatVersion: 2010-09-09Description: Example of CloudFront pre-signed URLs to upload files to S3 Bucket
Parameters: PublicKey: Type: String
Resources: S3Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub uploaded-files-${AWS::AccountId}-${AWS::Region}
S3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref S3Bucket PolicyDocument: Version: 2008-10-17 Id: PolicyForCloudFrontPrivateContent Statement: - Sid: AllowCloudFrontServicePrincipal Effect: Allow Principal: Service: cloudfront.amazonaws.com Action: - s3:PutObject Resource: !Sub ${S3Bucket.Arn}/* Condition: StringEquals: "AWS:SourceArn": !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}
CloudFrontPublicKey: Type: AWS::CloudFront::PublicKey Properties: PublicKeyConfig: Name: signer1 EncodedKey: !Ref PublicKey CallerReference: cloudfront-caller-reference-example
CloudFrontKeyGroup: Type: AWS::CloudFront::KeyGroup Properties: KeyGroupConfig: Name: cloudfront-key-group-1 Items: - !Ref CloudFrontPublicKey
CloudFrontOriginAccessControl: Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Name: !Ref S3Bucket OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4
CloudFrontDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Enabled: true HttpVersion: http2and3 Origins: - Id: !GetAtt S3Bucket.DomainName DomainName: !GetAtt S3Bucket.DomainName OriginAccessControlId: !Ref CloudFrontOriginAccessControl S3OriginConfig: OriginAccessIdentity: '' DefaultCacheBehavior: AllowedMethods: - HEAD - DELETE - POST - GET - OPTIONS - PUT - PATCH Compress: true # CachingDisabled # See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-policy-caching-disabled CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # AllViewerExceptHostHeader # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policy-all-viewer-except-host-header OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac TargetOriginId: !GetAtt S3Bucket.DomainName TrustedKeyGroups: - !Ref CloudFrontKeyGroup ViewerProtocolPolicy: https-only
Outputs: CloudFrontDistributionDomainName: Value: !GetAtt CloudFrontDistribution.DomainName CloudFrontPublicKeyId: Value: !Ref CloudFrontPublicKey S3BucketName: Value: !Ref S3BucketDeploy the CloudFormation stack with the following command:
PUBLIC_KEY=$(cat public_key.pem)aws cloudformation deploy \ --template-file template.yaml \ --stack-name cloudfront-presigned-urls-example \ --parameter-overrides PublicKey=$PUBLIC_KEYCheck the deployed resources:
aws cloudformation describe-stacks \ --stack-name cloudfront-presigned-urls-example \| jq ".Stacks[0].Outputs"Testing
Set the following variables:
CLOUDFRONT_DOMAIN=<CloudFront domain>KEYPAIR_ID=<Key pair ID>UTC_OFFSET=+9Generate a URL:
PRESIGNED_URL=$(aws cloudfront sign \ --url https://$CLOUDFRONT_DOMAIN/upload-test.txt \ --key-pair-id $KEYPAIR_ID \ --private-key file://private_key.pem \ --date-less-than $(date -v +5M "+%Y-%m-%dT%H:%M:%S$UTC_OFFSET"))
echo $PRESIGNED_URL# https://<distribution-id>.cloudfront.net/upload-test.txt?Expires=...&Signature=...Key-Pair-Id=...Upload a file:
echo 'Hello World' > example.txtcurl -X PUT -d "$(cat example.txt)" $PRESIGNED_URLConfirm the file is uploaded:
aws s3 cp s3://uploaded-files-<AWS::AccountId>-<AWS::Region>/upload-test.txt ./cat ./upload-test.txtCleaning Up
Clean up all the AWS resources provisioned during this example with the following command:
Disabling the CloudFront distribution may take several minutes.
aws s3 rm s3://uploaded-files-<AWS::AccountId>-<AWS::Region>/upload-test.txtaws cloudformation delete-stack --stack-name cloudfront-presigned-urls-exampleRelated posts
Avoiding Common Pitfalls with s3:TestEvent in AWS S3 Notifications
When configuring event notifications for S3 buckets, s3:TestEvent messages are automatically sent by S3. If this test message is not handled properly, it may cause unexpected issues.
Sign in with Slack Using Cognito User Pools and OIDC
This note describes how to federate Cognito user pools with Slack using OIDC to implement "Sign in with Slack".
Deploying FastAPI on AWS Lambda with Lambda Web Adapter
This example guides you through the process of developing API backends with FastAPI using Lambda Web Adapter.
API Gateway WebSocket: Implementing a Mock Integration
This note describes how to build an API Gateway WebSocket endpoint using mock integration.
AWS EventBridge Scheduler: Automate EC2 Scheduling with Ease
This note describes how to automate starting and stopping of EC2 instances using EventBridge Scheduler.
