Develop

AWS S3 + CloudFront 배포 완벽 가이드 — 정적 파일을 CDN으로 서빙하는 방법

Next.js 정적 빌드 결과물을 AWS S3에 올리고 CloudFront CDN을 엮어 전 세계 사용자에게 0.1초 만에 배포하는 무중단 정적 호스팅 파이프라인의 실무 구축 노하우를 정리했습니다.

AWSS3CloudFrontCDNinfra
AWS 콘솔에서 S3 버킷과 CloudFront 배포가 연결된 구성 화면 — 정적 파일 CDN 배포 예시
  • ·S3 버킷은 기본적으로 퍼블릭 접근이 차단되어 있으며 CloudFront OAC를 통해서만 접근하도록 구성하는 것이 보안상 안전하다
  • ·CloudFront는 전 세계 400개 이상의 엣지 로케이션에서 캐시된 파일을 서빙해 원본 서버 부하를 줄인다
  • ·S3 버킷 정책과 CloudFront 배포의 캐시 정책은 별도로 관리되며 캐시 무효화(Invalidation)로 즉시 갱신할 수 있다
  • ·AWS CLI의 s3 sync 명령으로 로컬 빌드 결과물을 S3에 자동 업로드할 수 있다
Next.js 정적 빌드 결과물을 처음에는 가볍게 EC2 인스턴스에 올린 뒤 Nginx로 서빙했었습니다. 그런데 동접자가 조금만 몰려도 CPU가 비명을 지르고 서버가 버벅거리더군요. 이를 해결하고자 S3 + CloudFront CDN 조합으로 아키텍처를 개편했습니다. 엣지 로케이션 캐싱 덕분에 EC2 서버 부하는 0에 수렴하게 되었고 로딩 속도도 눈에 띄게 빨라졌습니다. 다만 처음에 S3 버킷을 퍼블릭으로 완전히 열어두었다가 비용 폭탄을 맞을 뻔한 아찔한 경험을 한 뒤, 지금은 무조건 OAC(Origin Access Control)를 적용해 외부 직접 접근을 엄격히 차단하고 있습니다.

1. S3 버킷 생성과 업로드 최적화

AWS S3 버킷을 생성하고 AWS CLI로 빌드 파일을 업로드하는 최적의 방법

정적 사이트 호스팅을 위해 AWS S3 버킷을 처음 만들 때, 보안을 위해 퍼블릭 접근 차단 설정을 절대로 풀지 않는 것이 좋습니다. 대신 CloudFront만 접근할 수 있게 닫아두는 게 안전하죠. 로컬에서 매번 빌드 후 콘솔에 들어가 수동으로 마우스 드래그 업로드를 하는 것은 번거롭습니다. 이때는 AWS CLI의 s3 sync 명령어를 사용하면 빌드 결과물 폴더 전체를 파일 변경 사항만 식별하여 동기화할 수 있습니다. 특히 --delete 옵션을 사용해 로컬 빌드 결과물에서 사라진 레거시 파일을 S3에서도 깔끔하게 자동 청소해주어야 쓸데없는 스토리지 공간 낭비를 막을 수 있습니다.

# Next.js 정적 빌드 후 S3 업로드
npx next build

# 빌드 결과물 업로드 (out 또는 .next/static)
aws s3 sync ./out s3://my-bucket-name \
  --delete \
  --cache-control "public,max-age=31536000,immutable" \
  --exclude "*.html"

# HTML 파일은 캐시 없이
aws s3 sync ./out s3://my-bucket-name \
  --exclude "*" --include "*.html" \
  --cache-control "no-cache"

# CloudFront 캐시 무효화
aws cloudfront create-invalidation \
  --distribution-id DISTRIBUTION_ID \
  --paths "/*"

2. CloudFront OAC 설정과 캐싱 정책

CloudFront OAC와 S3 연동으로 보안을 강화하고 정적 파일을 CDN 서빙하는 방법

S3 버킷을 완전히 꽁꽁 닫아둔 채로 배포를 하려면 CloudFront의 OAC(Origin Access Control) 보안 링킹 설정이 필수적입니다. OAC를 활성화하면 CloudFront가 S3 원본 버킷에 서명된 요청을 던지며, AWS가 자동으로 생성해 주는 S3 버킷 정책(Bucket Policy)을 S3 콘솔에 붙여넣기만 하면 연동이 끝납니다. 이때 캐시 정책은 기본 제공되는 CachingOptimized 설정을 쓰면 되며, 사용자가 커스텀 도메인(예: blog.jalleria.com)을 입히고 싶다면 AWS ACM(Certificate Manager)에서 무료 SSL 인증서를 발급받아 CNAME 배포 도메인에 안전하게 물리시면 됩니다.

3. GitHub Actions CI/CD 배포 자동화

GitHub Actions로 AWS S3 업로드와 CloudFront 캐시 무효화 자동화하기

로컬 터미널에서 수동 배포를 돌리는 것은 언젠가 실수를 유발합니다. GitHub Actions 워크플로에 배포 자동화 파이프라인을 셋업하는 것이 훨씬 직관적이고 편안하죠. AWS credentials 액션을 통해 임시 권한을 획득한 후 S3 동기화 명령을 수행합니다. 여기서 가장 중요한 디테일은 바로 CloudFront 캐시 무효화(Invalidation) 단계입니다. 새 빌드 파일을 아무리 S3에 열심히 올려도 CDN 엣지 노드가 옛날 파일을 움켜쥐고 있으면 사용자는 새로고침을 해도 옛날 페이지만 보게 됩니다. 배포 스크립트 마지막 단계에 aws cloudfront create-invalidation을 덧붙여 캐시를 강제 휘발시켜 주는 코드를 꼭 챙겨 넣어야 합니다.

자주 묻는 질문

S3 버킷 정책을 바꿨는데도 CloudFront에서 여전히 Access Denied 403 에러가 발생해요.+

S3 버킷의 객체 소유권(Object Ownership) 설정을 확인해 보세요. 버킷 소유자가 강제되는 규칙으로 되어 있지 않거나, CloudFront OAC의 권한 허용 구문이 S3 정책 JSON에 완벽하게 복사되었는지 꼼꼼히 대조해야 합니다. 드물게 CloudFront 배포가 전 세계 엣지에 완전히 전파(Deploying)되는 데 최대 5분 정도 소요될 수 있으니 조금 기다려 보시는 것도 좋습니다.

Next.js App Router 프로젝트도 S3 + CloudFront 정적 배포가 전적으로 가능한가요?+

next.config.js 파일에 output: 'export' 설정을 지정하면 빌드 시 순수 HTML/CSS/JS 파일들로 내보내기가 실행되어 완벽하게 배포할 수 있습니다. 다만 서버에서 동적으로 쿼리를 태우는 Server Component나 Middleware, API Route 등 NodeJS 서버 런타임이 무조건 필요한 기능은 동작하지 않으니, 순수 정적 웹사이트 빌드가 가능한 도메인에만 이 아키텍처를 도입하는 것이 바람직합니다.

관련 글