Cloud Engineering ~20 min read

Hosting a static website on AWS S3 + CloudFront

A complete walkthrough of the exact AWS setup powering this portfolio — from S3 bucket to HTTPS domain with automated CI/CD via GitHub Actions.

Overview Prerequisites Architecture Tutorial Key learnings Resources

Overview

AWS S3 is more than object storage — it doubles as a highly available, serverless static website host. Pair it with CloudFront (AWS's global CDN), ACM for free SSL certificates, and Route 53 for DNS, and you get a production-grade website with sub-second global load times at a few cents per month.

This guide covers the full stack used to host this portfolio: S3 for file storage, CloudFront for caching and HTTPS termination, ACM for certificate management, Route 53 for domain routing, and GitHub Actions for zero-touch deployments on every git push.

Prerequisites

AWS account (free tier works) Registered domain name Basic HTML / CSS / JS AWS CLI installed & configured Git + GitHub account

Architecture

Visitors
Browser
Route 53 DNS
CloudFront CDN
S3 Bucket (origin)
HTTPS
ACM Certificate
attached to
CloudFront
CI/CD
GitHub repo
→ push →
GitHub Actions
→ aws s3 sync →
S3 Bucket
+
CF Invalidation

ACM certificates for CloudFront must be provisioned in us-east-1 regardless of where your S3 bucket is.

Tutorial steps

1

Create and configure your S3 bucket

Create an S3 bucket with the same name as your domain (e.g. yourdomain.com). Disable "Block all public access" and enable static website hosting under the bucket's Properties tab. Set the index document to index.html.

aws s3 mb s3://yourdomain.com --region eu-west-1 aws s3 website s3://yourdomain.com \ --index-document index.html \ --error-document index.html
2

Set the bucket policy for public read

Create a bucket policy that allows CloudFront (via OAC) to read objects. Avoid making the bucket fully public — instead use an Origin Access Control attached to your CloudFront distribution.

{ "Version": "2012-10-17", "Statement": [{ "Sid": "AllowCloudFrontOAC", "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::yourdomain.com/*" }] }
3

Request an SSL certificate via ACM

Open ACM in the us-east-1 (N. Virginia) region — this is a hard requirement for CloudFront. Request a public certificate for your domain and wildcard (*.yourdomain.com). Validate via DNS by adding the CNAME records ACM provides to Route 53.

4

Create a CloudFront distribution

In CloudFront, create a new distribution. Set the origin domain to your S3 bucket (use the S3 REST endpoint, not the website endpoint). Attach an Origin Access Control. Under Viewer Protocol Policy, choose "Redirect HTTP to HTTPS". Attach your ACM certificate and set your domain as an Alternate Domain Name (CNAME).

5

Configure Route 53

Create a hosted zone for your domain in Route 53. Add an A record (Alias) pointing to your CloudFront distribution. If using a subdomain (e.g. www), add a CNAME record pointing to the CloudFront domain name.

Type: A — Alias Alias target: dXXXXXXXXXXX.cloudfront.net
6

Set up GitHub Actions for CI/CD

Add a workflow file to your repo. On every push to main, it syncs your files to S3 and invalidates the CloudFront cache so visitors always get the latest version. Store your AWS credentials as GitHub repository secrets.

name: Deploy to AWS on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Sync to S3 run: aws s3 sync . s3://yourdomain.com --delete env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: eu-west-1 - name: Invalidate CloudFront cache run: aws cloudfront create-invalidation \ --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \ --paths "/*"
7

Upload your site and verify

Push your code to GitHub. The Actions workflow will sync to S3 automatically. Wait a few minutes for DNS propagation, then visit your domain over HTTPS to verify everything is working.

Key learnings

ACM certificates must be provisioned in us-east-1 for CloudFront — even if your bucket is in another region.
Always invalidate the CloudFront cache after deploying. Without this, visitors may see stale cached files for up to 24 hours.
Use Origin Access Control (OAC) instead of making the S3 bucket public. This keeps your files private and only accessible through CloudFront.
Your S3 bucket name must exactly match your domain name when using Route 53 alias records.
Store AWS credentials as GitHub Secrets — never commit them to source control.

Resources