Terraform module for hosting a static website on AWS. Designed around the Astro framework but works with any static site generator.
Browser → CloudFront (CDN + Lambda@Edge) → S3 (private bucket)
↑
ACM Certificate (us-east-1)
- Traffic is served exclusively through CloudFront — the S3 bucket is private and accessed via Origin Access Control (OAC).
- Lambda@Edge functions handle viewer filtering, prerender proxying, and response processing at the edge.
- HTTPS is enforced via an ACM certificate with a minimum TLS version of 1.2.
| Component | Description |
|---|---|
| S3 Bucket | Private bucket storing static assets; accessible only via CloudFront OAC |
| CloudFront Distribution | CDN with IPv6, forced HTTPS, 1-year cache TTL, and Lambda@Edge associations |
| ACM Certificate | SSL/TLS certificate provisioned in us-east-1 (required by CloudFront) |
| Route 53 Records | Optional DNS validation records for the ACM certificate (see SSL Certificate Validation) |
| Lambda@Edge Module | Local Terraform module (modules/lambda-at-edge/) deploying edge functions for viewer filtering, prerender proxy, geo-redirect, and response handling |
| IAM User & Group | Deployer user (<domain>_deployer) with scoped S3 PUT and CloudFront invalidation permissions for CI/CD |
- Terraform
~> 1.15 - AWS credentials configured with sufficient permissions
- A registered domain name
Reference this module from your root Terraform configuration:
module "static-hosting" {
source = "github.com/krishanthisera/aws-static-hosting?ref=master"
domain_name = "example.com"
bucket_name = "example.com"
aws_region = "ap-southeast-2"
common_tags = { Project = "my-site" }
}Then initialise and apply from your root configuration:
terraform init
terraform applyBy default, three Lambda@Edge functions are associated with the CloudFront distribution:
| Event Type | Function | Purpose |
|---|---|---|
viewer-request |
filter-function |
Filters incoming viewer requests and determines if they should be prerendered |
origin-request |
prerender-proxy |
Proxies bot/crawler traffic to a prerender service for server-side rendering |
origin-response |
response-handler |
Applies cache-control headers to origin responses |
These functions are managed by a local Terraform module located in modules/lambda-at-edge/. The TypeScript source code for each function is in modules/lambda-at-edge/edge-functions/packages/.
The module also includes a geo-redirect function (not enabled by default) that redirects users based on their geographic location.
To disable all Lambda@Edge associations, set lambda_associations to an empty list:
module "static-hosting" {
source = "github.com/krishanthisera/aws-static-hosting?ref=master"
# ...
lambda_associations = []
}To use a custom set of functions, provide your own list:
lambda_associations = [
{
event_type = "viewer-request"
lambda_name = "my-custom-function"
},
{
event_type = "origin-request"
lambda_name = "geo-redirect" # Enable geo-redirect
}
]The ACM certificate is provisioned by Terraform. DNS validation behaviour is controlled by create_validation_records:
create_validation_records |
Behaviour |
|---|---|
false (default) |
Certificate is created but Route 53 validation records are not managed. Add the CNAME records manually in your DNS provider. |
true |
Route 53 validation records are created automatically. The hosted zone is looked up by domain_name. Terraform waits for the certificate to reach ISSUED before proceeding. |
Enable automatic validation:
create_validation_records = trueTerraform creates an IAM user named <domain_name>_deployer with two scoped policies:
- S3 PUT — upload build artifacts to the assets bucket
- CloudFront invalidation — purge the CDN cache after deployment
After terraform apply, generate an access key pair for this user and add the credentials to your CI/CD pipeline secrets.
| Output | Description |
|---|---|
assets_bucket_domain_name |
Regional domain name of the S3 assets bucket |
cloudfront_arn |
ARN of the CloudFront distribution |
cloudfront_domain_name |
CloudFront domain name (e.g. d1234.cloudfront.net) |
| Name | Version |
|---|---|
| terraform | ~> 1.15 |
| aws | ~> 6.55 |
| Name | Version |
|---|---|
| aws | 6.55.0 |
| aws.us_east_1 | 6.55.0 |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| additional_domain_aliases | Additional domain aliases for the website. | list(string) |
[] |
no |
| aws_region | AWS region to deploy to. This where the S3 bucket will be created. | string |
"ap-southeast-2" |
no |
| bucket_name | The name of the bucket without the www. prefix. Normally domain_name. | string |
n/a | yes |
| common_tags | Common tags you want applied to all components. | any |
n/a | yes |
| create_validation_records | Whether to create Route 53 validation records for the SSL certificate. | bool |
false |
no |
| domain_name | The domain name for the website. | string |
n/a | yes |
| lambda_associations | Lambda function associations | list(object({ |
[ |
no |
| ssl_certificate_arn | SSL certificate ARN for the CloudFront distribution. | string |
"" |
no |
| Name | Description |
|---|---|
| assets_bucket_domain_name | n/a |
| cloudfront_arn | n/a |
| cloudfront_distribution_id | n/a |
| cloudfront_domain_name | n/a |
| iam_user_pipeline_deployment_user_arn | n/a |
| iam_user_pipeline_deployment_user_name | n/a |