Stackvora.tech

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 local dist/ 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 the OriginAccessControlId.
  • Viewer Protocol Policy: redirect-to-https is crucial for security.
  • Allowed HTTP Methods: For static sites, GET and HEAD are sufficient.
  • Compress Objects Automatically: Set to true to 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 serve index.html.
  • Custom Error Responses: This is vital for Single Page Applications (SPAs).
    • For 403 Forbidden and 404 Not Found errors, redirect them internally to /index.html with an HTTP 200 OK response code. This allows your SPA's client-side router to handle paths like /users/profile even 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.

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:GetObject to 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 sync can set restrictive ACLs.
  • SPA Routing Issues (e.g., /about gives 404):
    • Double-check the CloudFront Custom Error Responses for 403 and 404 errors, ensuring they redirect to /index.html with a 200 response code.
    • Clear CloudFront cache after making changes to error responses or index.html.
  • Certificate Not Applied / HTTP Only:
    • Ensure your ACM certificate is in the us-east-1 region.
    • 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.
  • 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.
  • 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.

Back to the knowledge base · Ask the AI assistant