AWS: host a static website on S3 + CloudFront
Hosting static websites on AWS S3, fronted by Amazon CloudFront, is a highly scalable, cost-effective, and performant solution. This architecture…
Hosting static websites on AWS S3, fronted by Amazon CloudFront, is a highly scalable, cost-effective, and performant solution. This architecture leverages S3 for durable object storage and CloudFront for global content delivery, caching, and SSL termination. This guide details the steps for setting up a static site, including custom domains, SSL, and single-page application (SPA) routing considerations.
1. S3 Bucket Setup for Static Website Hosting
The foundation of this setup is an S3 bucket configured to serve web content. While S3 has a direct "static website hosting" feature, for production deployments fronted by CloudFront, we configure it as a private origin. This ensures all traffic flows through CloudFront, allowing for SSL, caching, and other CDN benefits.
1.1. Create the S3 Bucket
Create an S3 bucket with a globally unique name. It's common practice to name the bucket after your domain (e.g., www.example.com or example.com), though this is not strictly required when using CloudFront with an OAC.
aws s3api create-bucket \
--bucket www.stackvora.tech \
--region us-east-1 \
--create-bucket-configuration LocationConstraint=us-east-1
Note: While the bucket can be in any region, choosing a region close to your primary user base or CloudFront edge locations can slightly reduce latency for origin fetches. For CloudFront origins, us-east-1 is a common choice.
1.2. Upload Website Content
Deploy your static website files (HTML, CSS, JavaScript, images) to the S3 bucket. A typical deployment involves syncing your local dist/ folder.
aws s3 sync dist/ s3://www.stackvora.tech/ \
--delete \
--acl public-read \
--exclude "node_modules/*"
--delete: Removes files from S3 that are not present in the localdist/directory.--acl public-read: While this ACL is applied, it will be overridden by the S3 bucket policy allowing access from the CloudFront OAC. This step is largely for historical compatibility and might be removed in future best practices.
1.3. Block Public Access & Configure Bucket Policy
To ensure content is only served via CloudFront, explicitly block public access to the S3 bucket and then configure a bucket policy that grants read access exclusively to your CloudFront Origin Access Control (OAC).
aws s3api put-public-access-block \
--bucket www.stackvora.tech \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
The bucket policy will be automatically generated and applied when you create the CloudFront distribution with an OAC in a later step. This policy will typically look like this, replacing <OAC_ID> and <ACCOUNT_ID>:
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::www.stackvora.tech/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<ACCOUNT_ID>:distribution/<OAC_ID>"
}
}
}
]
}
2. SSL Certificate with AWS Certificate Manager (ACM)
CloudFront requires an SSL/TLS certificate for HTTPS connections on custom domains. ACM provides free, managed certificates.
Important: Certificates used with CloudFront must be provisioned in the us-east-1 (N. Virginia) region, regardless of your S3 bucket's region.
2.1. Request a Certificate
Request a public certificate for your domain(s) (e.g., example.com and www.example.com). DNS validation is the recommended method.
aws acm request-certificate \
--domain-name www.stackvora.tech \
--validation-method DNS \
--subject-alternative-names stackvora.tech \
--region us-east-1
2.2. Validate the Certificate
ACM will provide CNAME records. Add these records to your domain's DNS configuration. Once DNS propagates, ACM will automatically validate and issue the certificate.
You can check the status:
aws acm list-certificates --region us-east-1 \
--query "CertificateSummaryList[?DomainName=='www.stackvora.tech'].Status"
The status should eventually change to ISSUED.
3. CloudFront Distribution Configuration
CloudFront acts as the content delivery network, caching your S3 content at edge locations worldwide and serving it over HTTPS.
3.1. Create an Origin Access Control (OAC)
OAC is the recommended method (introduced late 2022) to restrict S3 bucket access to CloudFront. It improves security over the older Origin Access Identity (OAI) by supporting more S3 features and different authentication types.
aws cloudfront create-origin-access-control \
--origin-access-control-config "Name=StackVoraOAC,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3"
Note the Id from the output, you will need it for the distribution creation. This step will also prompt you to update the S3 bucket policy with the necessary permissions, as shown in section 1.3.
3.2. Create the CloudFront Distribution
This is the core of the CloudFront setup. Key parameters include the S3 origin, custom domain names (CNAMEs), and the SSL certificate.
Using the AWS console is often simpler for distribution creation, but here's a conceptual CLI approach for the main settings:
aws cloudfront create-distribution \
--distribution-config '{
"CallerReference": "stackvora-website-20231120",
"Comment": "Static website for stackvora.tech",
"Aliases": {
"Quantity": 2,
"Items": ["www.stackvora.tech", "stackvora.tech"]
},
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3-www.stackvora.tech",
"DomainName": "www.stackvora.tech.s3.us-east-1.amazonaws.com",
"OriginPath": "",
"S3OriginConfig": {
"OriginAccessIdentity": ""
},
"OriginAccessControlId": "<OAC_ID_FROM_STEP_3.1>"
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-www.stackvora.tech",
"ViewerProtocolPolicy": "redirect-to-https",
"AllowedMethods": {
"Quantity": 2,
"Items": ["GET", "HEAD"],
"CachedMethods": {
"Quantity": 2,
"Items": ["GET", "HEAD"]
}
},
"Compress": true,
"ForwardedValues": {
"QueryString": false,
"Cookies": {"Forward": "none"},
"Headers": {"Quantity": 0}
},
"MinTTL": 0,
"DefaultTTL": 86400,
"MaxTTL": 31536000
},
"CacheBehaviors": { "Quantity": 0 },
"PriceClass": "PriceClass_AllLocations",
"Enabled": true,
"ViewerCertificate": {
"ACMCertificateArn": "<ACM_CERTIFICATE_ARN_FROM_STEP_2.2>",
"SSLSupportMethod": "sni-only",
"MinimumProtocolVersion": "TLSv1.2_2021"
},
"DefaultRootObject": "index.html",
"CustomErrorResponses": {
"Quantity": 1,
"Items": [
{
"ErrorCode": 403,
"ResponsePagePath": "/index.html",
"ResponseCode": 200,
"ErrorCachingMinTTL": 10
},
{
"ErrorCode": 404,
"ResponsePagePath": "/index.html",
"ResponseCode": 200,
"ErrorCachingMinTTL": 10
}
]
}
}'
Key Configuration Points:
- Origins: Point to your S3 bucket's regional endpoint (e.g.,
yourbucket.s3.us-east-1.amazonaws.com), NOT the S3 static website endpoint. Specify theOriginAccessControlId. - Viewer Protocol Policy:
redirect-to-httpsis crucial for security. - Allowed HTTP Methods: For static sites,
GETandHEADare sufficient. - Compress Objects Automatically: Set to
trueto enable Gzip/Brotli compression at the edge. - Custom SSL Certificate: Use the ACM ARN from step 2.2.
- Default Root Object: Set to
index.html. When a user requests the root (e.g.,https://www.example.com/), CloudFront will serveindex.html. - Custom Error Responses: This is vital for Single Page Applications (SPAs).
- For
403 Forbiddenand404 Not Founderrors, redirect them internally to/index.htmlwith an HTTP200 OKresponse code. This allows your SPA's client-side router to handle paths like/users/profileeven if they don't map to a physical file on S3. ErrorCachingMinTTL: Set this to a low value (e.g., 10 seconds) during development to see changes faster, increase it (e.g., 300 seconds) in production to leverage caching.
- For
4. DNS Configuration
Once your CloudFront distribution is deployed (which can take 5-15 minutes), update your domain's DNS records to point to the CloudFront distribution's domain name.
If you're using Route 53, create an A record or AAAA record with an Alias to the CloudFront distribution. For other DNS providers, create a CNAME record pointing your domain (e.g., www.stackvora.tech) to the CloudFront distribution's domain name (e.g., d12345abcdef.cloudfront.net).
4.1. Example Route 53 A Record (Alias)
Record name: www.stackvora.tech
Record type: A - Routes traffic to an IPv4 address and some AWS resources
Alias: Yes
Route traffic to: Alias to CloudFront distribution -> <Your CloudFront Distribution Domain Name>
You may also want to set up a redirect from the naked domain (stackvora.tech) to www.stackvora.tech. This can be done by creating another S3 bucket specifically for redirection, configuring it for static website hosting with redirection rules, and then pointing a CloudFront distribution (or Route 53 alias) at that bucket.
5. Common Pitfalls and Troubleshooting
- "Access Denied" or 403 Errors:
- Ensure the S3 bucket policy correctly grants
s3:GetObjectto the CloudFront OAC. - Verify the OAC ID in the CloudFront origin configuration matches the one in your S3 bucket policy.
- Check if CloudFront is trying to access the bucket using an older OAI instead of OAC.
- Confirm S3 Block Public Access settings are configured as described (blocking public access, then explicitly allowing OAC via policy).
- Check individual object ACLs; sometimes a misconfigured
aws s3 synccan set restrictive ACLs.
- Ensure the S3 bucket policy correctly grants
- SPA Routing Issues (e.g.,
/aboutgives 404):- Double-check the CloudFront Custom Error Responses for 403 and 404 errors, ensuring they redirect to
/index.htmlwith a200response code. - Clear CloudFront cache after making changes to error responses or
index.html.
- Double-check the CloudFront Custom Error Responses for 403 and 404 errors, ensuring they redirect to
- Certificate Not Applied / HTTP Only:
- Ensure your ACM certificate is in the
us-east-1region. - Verify the certificate ARN is correctly specified in the CloudFront distribution's Viewer Certificate settings.
- Check your DNS records; if they are not correctly pointing to CloudFront, your custom domain won't use the certificate.
- Ensure your ACM certificate is in the
- Old Content Being Served (Stale Cache):
- CloudFront caches content based on TTL settings. If you deploy new content and it's not showing, create an invalidation for
/*on your CloudFront distribution to force a refresh. - Consider implementing versioned filenames (e.g.,
app.1a2b3c.js) to leverage long cache TTLs for assets while ensuring new deployments are immediately visible.
- CloudFront caches content based on TTL settings. If you deploy new content and it's not showing, create an invalidation for
- Domain Name Issues:
- Ensure your custom domain is listed in the CloudFront distribution's "Alternate Domain Names (CNAMEs)".
- Verify your DNS provider has the correct CNAME or ALIAS record pointing to your CloudFront distribution domain.