Who this is for
This guide is for developers deploying compiled HTML, CSS, and JavaScript who need a private S3 origin, global delivery through CloudFront, HTTPS, and a domain managed by an external DNS provider such as Namecheap.
What you will build
The browser will connect to CloudFront, not directly to S3. CloudFront terminates TLS, caches content, and reads from the private bucket through Origin Access Control (OAC). This avoids making the bucket a public website endpoint.
Architecture and trust boundaries
| Service | Role | Security boundary |
|---|---|---|
| S3 | Stores compiled files | Private; readable only by the distribution |
| CloudFront | CDN and public entry point | Enforces HTTPS and controls origin access |
| ACM | Issues the TLS certificate | Domain ownership is proven through DNS |
| DNS provider | Maps hostnames to CloudFront | Controls domain resolution |
Build the static output
Upload the contents of the generated output directory, not the directory itself. Confirm that index.html is at the bucket root and that asset paths work without a local development server.
pnpm install --frozen-lockfile
pnpm build
# Upload the contents of dist/ or out/, depending on the frameworkThis architecture supports static output. A Next.js application that depends on server rendering, route handlers, image optimization at runtime, or server actions needs a compatible compute runtime rather than S3 alone.
Create a private S3 bucket
- Create the bucket in the region that fits your operational requirements.
- Keep Block Public Access enabled.
- Leave static website hosting disabled when using the standard S3 origin with OAC.
- Upload the compiled files with correct content types.
The bucket name does not need to match the domain when CloudFront is the public endpoint.
Request the ACM certificate in us-east-1
CloudFront viewer certificates from ACM must be requested in the US East (N. Virginia) Region, us-east-1. Request names for the exact hosts you will serve, such as the apex and www variant.
Choose DNS validation and create the CNAME records shown by ACM at your DNS provider. Preserve those records after issuance so ACM can renew the certificate automatically.
Create the CloudFront distribution
- Select the regular S3 bucket endpoint as the origin, not the website endpoint.
- Create an Origin Access Control and sign origin requests.
- Redirect HTTP requests to HTTPS.
- Set
index.htmlas the default root object. - Add the custom hostnames as alternate domain names.
- Select the validated ACM certificate.
Apply the least-privilege bucket policy
Allow the CloudFront service principal to call s3:GetObject only when the request comes from your distribution ARN. Use the policy generated by the CloudFront console or adapt the documented OAC policy carefully.
Do not replace the private-origin model with a broad public-read policy merely to resolve a 403 response. A 403 is a signal to verify the origin, OAC association, object key, and bucket policy.
Connect the domain
Copy the distribution hostname. Configure the apex with the ALIAS/ANAME mechanism supported by your DNS provider and configure www as a CNAME when appropriate. Both names must already be listed on the distribution and covered by the certificate.
Deploy updates and invalidate intentionally
Use fingerprinted filenames for long-lived assets. Give HTML shorter cache policies because it points to the current asset versions. After an urgent update, create a targeted invalidation; avoid invalidating the entire distribution as a routine deployment strategy.
aws s3 sync dist/ s3://YOUR_BUCKET --delete
aws cloudfront create-invalidation --distribution-id YOUR_ID --paths "/index.html"Handle client-side routes only when needed
For a single-page application, direct requests to client routes may return 403 or 404 because S3 has no matching object. A custom error response can return /index.html with status 200, but only use this fallback for applications whose client router owns those paths. It can otherwise create soft 404s.
Common mistakes
- Using the S3 website endpoint with OAC: OAC is designed for the regular S3 origin.
- Requesting the certificate in another region: CloudFront requires the ACM certificate in
us-east-1. - Making the bucket public: fix the distribution-to-origin authorization instead.
- Uploading the output folder as a nested directory:
index.htmlmust be at the expected root. - Using a global SPA fallback for a normal static site: this can turn genuine missing pages into soft 404s.
- Changing email DNS records: web hosting does not require deleting MX, SPF, DKIM, or DMARC records.
Production checklist
- The bucket blocks public access.
- The distribution uses OAC and the standard S3 origin.
- The bucket policy is restricted to the distribution ARN.
- The certificate is issued in
us-east-1and covers every hostname. - HTTP redirects to HTTPS.
- The root document and asset content types are correct.
- Deep links return the intended status and content.
- The cache policy matches how each file changes.