My static website on AWS S3 and CloudFront
How I created a static website on AWS S3 and CloudFront with nice redirects and security headers.
Requirements
- HTTPS
- Redirect from http to https
- Support https on all websites
- Use www URLs
- Redirect from jarett-lee.com to www.jarett-lee.com
- /path/ is the canonical URL
- Redirect from /path to /path/
- Redirect from /path/index.html to /path/
- Serve files like normal, such as /style.css
- Redirect common misspellings
- Redirect from jarrett-lee.com to www.jarett-lee.com
- Redirect from jarettlee.com to www.jarett-lee.com
Why are these my requirements?
Important requirements
- HTTPS - https is standard and secure
- Redirect common misspellings - I’ve seen jarett misspelled as jarrett too many times
- Canonical URL - I don’t want to include a rel=“canonical” link in every file, so I need redirects to signal which URL is the canonical URL
Preferences
- Use www URLs - I like how www.jarett-lee.com looks and www is slightly more flexible
- /path/ is the canonical URL - I like how /path/ looks better than /path/index.html and /path
Primary resources
These resources fulfill all my requirements except the additional common misspelling domains.
- Domain - Route 53 > Domains > Registered domains
- $13.00 a year per .com domain
- > Register Domain
- Domain name: jarett-lee.com
- Hosted zone - Route 53 > Hosted zones
- $0.50 a month per hosted zone
- > Create hosted zone
- The NS record and SOA record should be created automatically
- SSL/TLS certificate - AWS Certificate Manager > Certificates
- > Request
- Fully qualified domain name:
- jarett-lee.com
- *.jarett-lee.com
- SSL/TLS CNAME record - AWS Certificate Manager > Certificates > [certificate id for jarett-lee.com]
- > Create records in Route 53
- Bucket - Amazon S3 > Buckets
- This is where your website will live
- The folder structure in the bucket should have a /index.html file for the home page and /path/index.html
- > Create bucket
- Bucket name: personal-website
- Response headers policy - CloudFront > Policies > Response headers
- This policy is probably more strict than necessary, but it’s perfect for my static website
- > Create response headers policy
- Response headers policy name: StaticWebsiteHeadersPolicy
- Strict-Transport-Security: enable
- max-age seconds: 63072000
- preload: check
- includeSubDomains: check
- X-Content-Type-Options: enable
- X-Frame-Options: enable
- Origin: DENY
- X-XSS-Protection: enable
- X-XSS-Protection: Enabled
- block: check
- Referrer-Policy: enable
- Referrer-Policy: strict-origin-when-cross-origin
- Content-Security-Policy: enable
- Content-Security-Policy: default-src ‘none’; img-src ‘self’; script-src ‘self’ ‘unsafe-eval’; style-src ‘self’; object-src ‘none’
- CloudFront function - CloudFront > Functions
- > Create function
- CloudFront function name: RedirectFunction
- See code below
- CloudFront distribution - CloudFront > Distributions
- The resources don’t have to be created in this order, but going in this order means you don’t have to jump between create pages and refresh resource lists
- I use the cheapest edge locations because my website doesn’t get much traffic
- > Create distribution
- Origin domain: personal-website
- Orign access: Origin access control settings (recommended)
- Viewer protocol policy: Redirect HTTP to HTTPS
- Response headers policy: StaticWebsiteHeadersPolicy
- Viewer request:
- Function type: CloudFront Functions
- Function ARN / Name: RedirectFunction
- Price class: Use only North America and Europe
- Alternate domain name (CNAME):
- jarett-lee.com
- www.jarett-lee.com
- Custom SSL certificate: jarett-lee.com ([certificate id])
- Supported HTTP versions:
- HTTPS/2: check
- HTTPS/3: check
- Bucket policy
- CloudFront > Distributions > [CloudFront distribution for jarett-lee.com] > Origins > Edit
- > Copy policy
- > Go to S3 bucket permissions
- Amazon S3 > Buckets > personal-website > Permissions > Bucket policy
- > Edit
- CloudFront > Distributions > [CloudFront distribution for jarett-lee.com] > Origins > Edit
- Bare A record - Route 53 > Hosted zones > jarett-lee.com
- > Create record
- Record type: A
- Alias: enable
- Route traffic to: Alias to CloudFront distribution
- Choose distribution: jarett-lee.com ([CloudFront domain name])
- www CNAME record - Route 53 > Hosted zones > jarett-lee.com
- Copy the domain name from the previous record, but remove the extra period at the end of the domain name
- > Create record
- Record name:
- subdomain: www
- Record type: CNAME
- Value: [CloudFront domain name]
CloudFront function name: RedirectFunction
function handler(event) {
var request = event.request
var headers = request.headers
var host = request.headers.host && request.headers.host.value
var uri = request.uri
var redirect = false
var errorResponse = {
statusCode: 500,
statusDescription: 'Internal Server Error',
}
var targetHost = 'www.jarett-lee.com'
if (!host) {
return errorResponse
} else if (host !== targetHost) {
redirect = true
}
var newUri = uri
var prefixPath = uri.match('(.*/)index.html')
if (uri.length === 0 || uri === '/') {
request.uri = '/index.html'
} else if (uri.length === 1) {
return errorResponse
} else if (uri.match('.*/$')) {
request.uri += 'index.html'
} else if (prefixPath) {
newUri = prefixPath[1]
redirect = true
} else if (request.uri.match('/[^/.]+$')) {
newUri = request.uri + '/'
redirect = true
}
if (redirect) {
var newUrl = `https://${targetHost}${newUri}`
var response = {
statusCode: 301,
statusDescription: 'Found',
headers: {
"location": { "value": newUrl },
},
}
return response
}
return request
}
Redirect resources
These resources my common misspelling domain requirements.
- Repeat steps 1-4 with the new domain
- Domain - Route 53 > Domains > Registered domains > Register Domain
- Hosted zone - Route 53 > Hosted zones > Create hosted zone
- SSL/TLS certificate - AWS Certificate Manager > Certificates > Request
- SSL/TLS CNAME records - AWS Certificate Manager > Certificates > [certificate id] > Create records in Route 53
- Skip to step 8 and create a CloudFront distribution
- The other resources have already been created
- CloudFront distribution - CloudFront > Distributions > Create distribution
- Skip step 9, updating the bucket policy
- The redirect distribution should never make a request to the bucket due to the redirect function, so the origin doesn’t matter
- Repeat steps 10 and 11 with the new domain
- Bare A record - Route 53 > Hosted zones > [domain] > Create record
- www CNAME record - Route 53 > Hosted zones > [domain] > Create record
Design decisions
What I used to do in the past
I used to use a Lambda@Edge origin request function to handle redirects and another Lambda@Edge origin response function to add response headers because CloudFront functions and response headers policies didn’t exist in 2012 and I didn’t want to run a Lambda@Edge function on every request.
Now, the AWS documentation recommends using CloudFront functions for redirects and response headers.
301 vs 302 redirects
I use a 301 permanent redirect instead of a 302 temporary redirect because I don’t plan to change my redirecting scheme.
Additional things I did
Turn on standard logging
- Bucket - Amazon S3 > Buckets
- > Create bucket
- Bucket name: personal-website-logs
- CloudFront Distribution - CloudFront > Distributions > [distribution id] > General > Settings
- > Edit
- Standard logging: On
- S3 bucket: personal-website-logs
- > Enable ACLs
Use a different response headers policy for certain paths
The majority of my website is pure html, so I have a very strict response headers policy, but I wanted to host some coding projects on my website as well, so I wanted a different response headers policy for the /random/* path.
- Response headers policy - CloudFront > Policies > Response headers
- > Create response headers policy
- CloudFront Distribution - CloudFront > Distributions > [distribution id] > Behaviors
- Copy the settings from the existing distribution except for the settings listed here
- > Create behavior
- Path pattern: /random/*
- Response headers policy: [response headers policy]
Things I didn’t want, but I could’ve done
- Redirect from /path/file.html to /path/file/
- If you want this, edit the RedirectFunction
Things I wanted, but I couldn’t figure out
I wanted
- To block direct access to the CloudFront domain name
- Less steps to create a redirect; creating a CloudFront distribution feels like overkill