AWS

[AWS] CloudFront + ACM + S3 기반 정적 웹호스팅

forward-ing 2026. 11. 7. 13:30

지난 글에서 Route 53으로 kbworld.dev 도메인을 구매했다. (지난 글)

이번에는 이 도메인으로 실제 웹사이트를 띄워보려고 한다.

 

서버(EC2) 없이 S3에 HTML 파일만 올려서 사이트를 운영하는 정적 웹 호스팅을 진행하고, .dev 도메인 특성상 필요한 HTTPS까지 적용해보자.

 

정적 웹사이트 호스팅이란?

정적 웹사이트 호스팅은 서버에서 별도 처리 없이, 미리 만들어진 정적 파일(HTML, CSS, JS, 이미지)을 그대로 전달하는 방식이다.

 

구분 정적 웹사이트 동적 웹사이트
응답 저장된 파일을 그대로 전달 요청마다 서버가 처리 후 생성
서버 필요 없음 (S3로 충분) 필요 (EC2, 컨테이너 등)
예시 포트폴리오, 문서, React/Vue 빌드 결과물 로그인, 게시판, DB 조회

 

S3는 원래 파일 저장소지만, 웹 서버처럼 파일을 응답해주는 기능이 있다.

서버를 관리할 필요가 없고 비용도 거의 들지 않아 개인 사이트 용도로 많이 쓴다.

 

S3만으로는 .dev 도메인을 쓸 수 없다

처음엔 S3 버킷에서 정적 웹사이트 호스팅 옵션을 켜고, Route 53에서 도메인을 연결하면 끝날 줄 알았다.

 

그런데 문제가 있다.

 

  • S3 웹사이트 엔드포인트(http://버킷이름.s3-website.ap-northeast-2.amazonaws.com)는 HTTP만 지원한다.
  • 지난 글에서 정리했듯이 .dev는 HSTS preload 목록에 등록된 TLD라서 HTTPS가 아니면 접속이 안 된다.

 

즉, S3 단독으로는 https://kbworld.dev를 띄울 수 없다.

그래서 S3 앞에 CloudFront를 두고, ACM 인증서로 HTTPS를 적용해야 한다.

 

최종 구성

사용자 → Route 53 (kbworld.dev)
       → CloudFront (HTTPS, ACM 인증서)
       → S3 버킷 (비공개, OAC로만 접근 허용)

 

서비스 역할
S3 웹사이트 파일 저장
CloudFront CDN. HTTPS 처리 및 전 세계 엣지에서 캐싱
ACM SSL/TLS 인증서 무료 발급
Route 53 도메인을 CloudFront로 연결 (Alias 레코드)

 

 

이번 구성에서는 S3 버킷을 비공개로 두고, OAC를 이용해 CloudFront를 통해서만 접근하도록 한다.

 

OAC(Origin Access Control)는 CloudFront만 S3 버킷에 접근할 수 있도록 제한하는 기능이다.

예전에 쓰던 OAI(Origin Access Identity)의 후속 기능으로, 현재는 OAC 사용이 권장된다.

 

요금

항목 요금
S3 스토리지 GB당 월 약 $0.025 (서울 리전, Standard)
S3 → CloudFront 전송 무료
ACM 퍼블릭 인증서 무료
CloudFront 아래 참고
Route 53 호스팅 영역 월 $0.50 (지난 글 참고)

 

CloudFront는 2025년 말부터 요금 방식이 두 가지다.

 

  • 종량제(Pay-as-you-go): 데이터 전송량과 요청 수만큼 과금. 매월 1TB 전송, 1,000만 요청까지 무료
  • 정액제(Flat-rate plan): Free($0), Pro($15), Business($200), Premium($1,000) 중 선택. 초과 요금 없음
    • Free 플랜은 월 100만 요청, 100GB 전송까지 지원

 

※ 신규 계정의 무료 플랜(크레딧형) 상태에서는 정액제 플랜을 사용할 수 없다. 이 경우 종량제로 진행하면 된다.

 

어느 쪽이든 개인 사이트 수준의 트래픽이면 호스팅 영역 월 $0.50 + S3 저장 비용이 사실상 전부다.

 

참고 - AWS CloudFront 요금

 

1. S3 버킷 생성

AWS 콘솔 → Amazon S3 → 버킷 만들기

 

 

  • AWS 리전: 아시아 태평양(서울) ap-northeast-2
  • 버킷 유형: 범용
  • 버킷 이름: kbworld-dev-123 (전 세계에서 유일해야 함)
  • 객체 소유권: ACL 비활성화됨 (기본값)
  • 퍼블릭 액세스 차단: 모든 퍼블릭 액세스 차단 체크 유지 (기본값)

 

 

 

 

나머지는 모두 기본값으로 두고 생성을 한다.

 

정상적으로 생성되었다면 아래와 같이 버킷 - 범용 버킷에서 해당 버킷을 확인할 수 있다.

 

 

 

2. 웹사이트 파일 업로드

테스트용으로 간단한 파일 두 개를 만든다.

 

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>kbworld.dev</title>
</head>
<body>
  <h1>Hello, kbworld.dev!</h1>
  <p>S3 + CloudFront로 호스팅 중입니다.</p>
</body>
</html>

 

error.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>404 Not Found</title>
</head>
<body>
  <h1>404</h1>
  <p>페이지를 찾을 수 없습니다.</p>
</body>
</html>

 

생성한 버킷을 클릭하여 업로드 버튼을 통해  두 파일을 추가하고 업로드한다.

 

 

 

정상적으로 업로드가 완료되었으면, 아래와 같이 해당 버킷에 업로드 된 객체(파일)을 확인할 수 있다.

 

 

이후 원하는 객체 파일을 클릭하여 객체 접속 URL을 확인할 수 있는데 이 주소를 복사하고

 

 

실제로 접속해보면 AccessDenied가 뜬다. 이 현상은 버킷이 현재 비공개 상태라서 거부되는 것은 정상적인 현상이다.

3. ACM 인증서 발급

이제 HTTPS 적용을 위해 인증서를 발급받는다.

 

※ CloudFront에 연결할 인증서는 반드시 버지니아 북부(us-east-1) 리전에서 발급해야 한다.

서울 리전에서 발급하면 CloudFront 설정 화면에서 인증서가 목록에 나타나지 않는다.

 

콘솔 우측 상단 리전을 미국 동부(버지니아 북부) 로 변경한 뒤 ACM → 인증서 요청

 

 

인증서 유형은 퍼블릭 인증서로 선택 후 다음을 누른다.

 

 

퍼블릭 인증서 요청 페이지에서는

도메인 이름을 kbworld.dev, *.kbworld.dev 를 적어주면 된다.

입력 칸을 추가하려면 이 인증서에 다른 이름 추가 버튼을 누르면 칸이 추가된다.

*.kbworld.dev(와일드카드) 도메인도 추가하는 이유는 인증서 1개로 www.kbworld.dev 같은 서브도메인까지 커버할 수 있기 때문이다.

 

 

검증 방법은 DNS 검증(기본값)으로 두면 되고,

나머지 항목들도 모두 기본값으로 두고 요청 버튼을 누르면 된다.

 

4. DNS 검증

요청 후 인증서 상태는 검증 대기 중 상태로 표시된다.

DNS 검증을 하기 위해 해당 도메인 DNS에 방금 만든 인증서의 CNAME의 이름과 값을 추가해야한다.

 

AWS ACM은 Route 53에 등록한 도메인에 대해서는 간단하게 버튼 하나만 눌러 CNAME 값을 자동으로 추가해주는 기능이 있다.

인증서 상세 화면에서 Route 53에서 레코드 생성 버튼을 누른다.

 

해당 도메인들이 맞는지 확인 후 레코드 생성 버튼을 누른다.

 

 

해당 Route 53에 등록된 도메인에 대해 DNS 레코드가 정상적으로 추가되었다.

 

 

 

이제 몇 분만 기다리면 상태가 발급됨으로 바뀐다.

 

※ 도메인과 호스팅 영역이 모두 Route 53에 있으니 버튼 하나로 검증이 끝난다. 다른 업체(가비아 등)에서 도메인을 관리한다면 해당 업체 DNS 설정에 CNAME 레코드를 직접 추가해서 인증받아야 한다.

 

5. CloudFront 배포 생성

CloudFront → 배포 생성

 

 

 

현재는 연습 목적이니 플랜은 무료 플랜을 선택한다.

 

 

배포 이름을 작성하고 만들 CloudFront에 연결할 도메인을 입력한다.

도메인 자동 연결 기능은 Route 53 호스팅 영역이 있는 도메인만 지원한다. 다른 DNS 업체를 쓴다면 이 섹션은 건너뛰고, 배포 생성 후 대체 도메인과 인증서를 직접 설정하면 된다.

필자의 경우 Route 53에 도메인을 등록했기 때문에 kbworld.dev를 입력 후 도메인 확인 버튼을 눌러 확인을 하고 다음 버튼을 눌렀다.

 

 

이제 오리진을 지정해야 한다. 

 

오리진(origin) 이란?

CloudFront가 트래픽이 오면 연결할 콘텐츠 위치라고 생각하면 된다.

 

오리진 유형을 Amazon S3로 설정하고 S3 origin을 방금 만들었던 버킷으로 설정한다.

나머지는 모두 기본값으로 두고 다음 버튼을 눌러 계속 진행한다.

 

 

보안 활성화 페이지도 기본값으로 두고 다음을 누른다.

 

 

TLS 인증서는 AWS ACM을 통해 발급받았으니 해당 인증서를 확인하고 선택 후 다음 버튼을 누른다.

 

 

설정된 값들에 대해 다시 한번 확인해보고 정상이라면 배포 생성을 통해 CloudFront를 배포한다.

 

※ 기본값인 "오리진에 대한 CloudFront 액세스 권한 부여"가 켜져 있으면 OAC가 생성되고, S3 버킷 정책에 이 배포만 GetObject를 허용하는 정책이 자동으로 추가된다. 버킷을 비공개로 둬도 CloudFront가 파일을 읽어올 수 있는 이유다.

 

 

CloudFront - 배포 탭에서 해당 배포가 활성화 상태인지 확인

 

 

6. 에러 페이지 설정

없는 경로로 접속하면 S3는 404가 아니라 403 AccessDenied를 반환한다.

버킷 목록 조회(ListBucket) 권한이 없어서 "파일이 없다"는 사실조차 알려주지 않기 때문이다.

 

그래서 CloudFront에서 403 에러를 error.html로 바꿔준다.

CloudFront → 배포 → 오류 페이지 → 사용자 정의 오류 응답 생성

 

 

HTTP 오류 코드: 403

오류 응답 사용자 정의: 예

응답 페이지 경로: /error.html

HTTP 응답 코드: 404

설정 후 생성한다.

 

 

아직 도메인을 연결하지 않은 상태지만 CloudFront에 만들어진 배포 도메인으로 접속해서 테스트해볼 수 있다.

현재 오리진인 S3에는 index.html, error.html만 업로드되어있기 때문에 없는 파일명으로 도메인을 입력해서 404 응답으로 error.html이 뜨는지 확인해보자.

 

CloudFront 배포 도메인/hello.html 결과로 아래와 같이 404가 잘나온다.

 

 

 

 

 

 

7. Route 53 레코드 연결

이제 도메인을 CloudFront로 연결한다.

 

Route 53 → 호스팅 영역 → kbworld.dev → 레코드 생성

 

 

레코드 유형은 A 레코드로 두고, 별칭을 활성화한다.

트래픽 라우팅 대상은 CloudFront로 설정하고 방금 만든 CloudFront 배포 도메인과 연결한 후 레코드를 생성한다.

 

 

 

성공적으로 A 레코드가 추가되었다.

 

 

 

이제 kbworld.dev로 접속을 해보자.

 

 

 

 

 

정상적으로 잘 동작하고 HTTPS 연결도 잘 되었다.

(CloudFront는 엣지로 분산되어 있기 때문에 IP가 여러 개 조회될 수 있다)

 

[참고] 파일 수정 시 캐시 무효화

index.html을 수정해서 다시 업로드해도 사이트에 바로 반영되지 않는다.

CloudFront가 엣지에 이전 파일을 캐싱하고 있기 때문이다. (기본 TTL 24시간)

 

이럴 때는 캐시 무효화(Invalidation)를 생성하면 된다. (따로 진행은 안했다)

 

 

※ 무효화 경로는 매월 1,000개까지 무료이고, /*는 1개로 계산된다.

 

마무리

S3에 정적 파일을 올리고, ACM으로 인증서를 발급받고, CloudFront에 HTTPS를 적용하고, Route 53으로 도메인을 연결해 https://kbworld.dev를 띄웠다.

버킷은 비공개로 두고 OAC로 CloudFront만 접근하게 했기 때문에, S3에 직접 접근하는 경로는 막혀 있다.