목차
14 장

CloudFront로 정적 사이트 배포

AWS의 글로벌 CDN, CloudFront. Origin / Behavior / Cache Policy의 흐름, S3 + CloudFront 정적 호스팅 패턴, OAC로 S3를 안전하게 가리는 법, 그리고 무효화(invalidation)의 운영 흐름까지 정리합니다.

10장 S3의 정적 호스팅 방식에는 한계가 있습니다. HTTPS가 안 되고, 사용자 가까이에서 캐시도 못 하며, 커스텀 도메인과 SSL도 바로 붙지 않습니다. 이 세 가지를 한 번에 푸는 서비스가 CloudFront입니다.

CloudFront는 AWS의 **CDN (Content Delivery Network)**입니다. 전 세계 600여 곳의 Edge Location에 캐시를 두고, 사용자에게 가장 가까운 Edge가 응답합니다. 정적 사이트뿐 아니라 동적 API도 통과시킬 수 있습니다.

본 챕터에서는 CloudFront의 구조에서 시작해 S3 + CloudFront 패턴, OAC, 캐시 정책, 무효화까지 한 줄로 정리합니다. 여기서 쓰는 ACM의 리전 규칙은 13장 ALB / NLB와 ACM의 연장이고, 도메인 연결은 12장 Route 53의 Alias입니다. 2부의 EC2 / VPC / S3 / RDS / Route 53 / ALB / CloudFront라는 기본 도구상자가 본 챕터로 한곳에 모입니다.

CDN이 푸는 문제 #

S3 정적 사이트를 그대로 쓰면 다음이 좋지 않습니다.

문제CloudFront가 푸는 기능
미국 사용자가 서울 S3에서 받으면 200ms+Edge 캐시로 ms 응답
HTTPS 직접 안 됨ACM 인증서 + 자동 HTTPS
Egress 비용이 비쌈 ($0.09/GB)CloudFront → 인터넷 ($0.085/GB) + 캐시 hit는 무료
캐시 헤더 / 압축 / HTTP/2 / HTTP/3 직접자동
WAF / Shield 직접CloudFront와 통합
DDoSAWS Shield Standard 자동

S3 + CloudFront는 모든 정적 사이트의 표준 패턴입니다.

CloudFront의 구조 #

CloudFront Distribution의 구조
 사용자
  │ HTTPS request: example.com/path/to/asset.js
┌────────────────────────────────────┐
│       CloudFront Edge Location      │
│       (사용자에 가장 가까운 지점)         │
│                                      │
│    Cache 확인                         │
│      ├── HIT → 캐시에서 응답             │
│      └── MISS                        │
└────────────────────────────────────┘
                │ MISS 시
        ┌────────────────────┐
        │   Cache Behavior   │
        │  (path 매칭 / 정책)   │
        └────────┬───────────┘
            ┌────────┐
            │ Origin │  ← S3, ALB, EC2, Lambda Function URL ...
            └────────┘

핵심 구성 요소는 다음과 같습니다.

  • Distribution — 한 묶음의 설정입니다(도메인 + 인증서 + 동작).
  • Origin — 캐시 미스 시 받을 곳입니다(S3, ALB, 사용자 도메인 등).
  • Cache Behavior — path 매칭과 캐시 정책, Origin 매칭입니다.

Distribution 만들기 #

간단한 CloudFront Distribution
aws cloudfront create-distribution \
  --origin-domain-name my-bucket.s3.ap-northeast-2.amazonaws.com \
  --default-root-object index.html

설정 항목은 다음과 같습니다.

옵션의미
Origin domainS3 버킷 또는 ALB DNS 또는 직접 도메인
Default root object/ 요청 시 보낼 파일 (index.html)
Alternate domain (CNAME)example.com, www.example.com
SSL certificateACM (반드시 us-east-1)
Price class어느 대륙 Edge까지 쓸지
LoggingS3에 access log 저장
WAFWeb ACL 연결

Price Class #

Price Class의미
All전 세계 모든 Edge — 비용 가장 높음
200미국 / 유럽 / 아시아 / 호주
100미국 / 유럽 / 이스라엘 / 일부 아시아 — 가장 저렴

한국 / 일본 사용자만이면 200 이상을 권장합니다. 글로벌 서비스는 All입니다.

Origin — 어디서 받을까 #

S3 Origin #

가장 흔한 경우입니다. 정적 사이트 / 이미지 / 비디오에 씁니다.

S3 Origin
Origin: my-bucket.s3.ap-northeast-2.amazonaws.com
        (REST API endpoint, NOT the s3-website-... URL)

s3-website-* URL이 아니라 REST API URL을 사용합니다. 그래야 OAC로 안전하게 보호할 수 있습니다.

Custom Origin (ALB / 임의 HTTP 서버) #

ALB / EC2 / 외부 서버도 Origin이 될 수 있습니다. 이 패턴은 동적 API 가속 용도입니다.

ALB Origin
Origin: my-alb-1234567890.elb.ap-northeast-2.amazonaws.com
HTTPS Port: 443
SSL Protocols: TLSv1.2, TLSv1.3
Origin Path: (없음 또는 /api)

ALB → CloudFront → 사용자 패턴은 WAF + Edge SSL termination + 캐시의 역할을 합니다.

Lambda Function URL Origin #

Lambda가 직접 Origin 역할을 합니다. 서버리스 정적 + 동적 사이트에 씁니다(17장 Lambda 기초).

Origin Group — 페일오버 #

Primary Origin이 죽으면 Secondary로 넘어갑니다. 멀티 리전 DR 구성입니다.

Cache Behavior — 라우팅 + 캐시 정책 #

같은 Distribution 안에서 path 마다 다른 동작을 줄 수 있습니다.

Cache Behavior의 역할
Path Pattern   | Origin | Cache Policy | TTL
---------------+--------+--------------+--------
/api/*         | ALB    | NoCaching    | 0
/static/*      | S3     | Optimized    | 1 day
default (*)    | S3     | Optimized    | 1 hour

처리 순서는 path pattern 매칭(구체적인 게 먼저)이고, 매칭이 없으면 default로 갑니다.

Cache Policy #

캐시 동작을 정의합니다. AWS가 제공하는 기본 정책과 커스텀 정책이 있습니다.

기본 Cache Policy
CachingOptimized               ← 정적 자산 (이미지 / JS / CSS)
CachingOptimizedForUncompressedObjects  ← 압축 안 된 자산
CachingDisabled                ← API 캐시 안 함
Elemental-MediaPackage         ← 미디어 스트리밍

캐시 키(무엇이 다르면 별도 캐시 항목이 되는지)는 다음으로 정합니다.

  • Query string — 모두 / 일부 / 없음
  • Header — 어떤 헤더로 구분할지
  • Cookie — 어떤 쿠키로 구분할지
단순 정적 자산의 캐시 키
Query: 무시
Header: 무시 (또는 Accept, Accept-Encoding)
Cookie: 무시
언어 / 디바이스 분리 SSR의 캐시 키
Query: 모두
Header: Accept-Language, CloudFront-Is-Mobile-Viewer
Cookie: session-id

Origin Request Policy #

Origin으로 무엇을 보낼지 정합니다. 캐시 키와 다른 개념입니다.

Cache PolicyOrigin Request Policy
무엇을 결정캐시 키 + TTLOrigin으로 보낼 헤더 / 쿼리 / 쿠키
효과캐시 히트율동적 응답에 무엇이 영향을 주는가

Response Headers Policy #

응답 헤더를 자동으로 추가하거나 수정합니다. Strict-Transport-Security, X-Content-Type-Options 같은 보안 헤더에 씁니다.

추천 보안 헤더
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; ...

TTL — 얼마나 캐시할까 #

캐시 TTL의 기준은 다음과 같습니다.

TTL역할
0캐시 안 함. 모든 요청이 Origin에
60~300초자주 바뀌는 콘텐츠 (뉴스 헤드라인)
1시간~1일일반 정적 자산
1년 (max-age=31536000)hashed filename (app.abc123.js)

Cache-Control 헤더 #

Origin 응답의 Cache-Control 헤더가 우선합니다(Min/Default/Max TTL 안에 들어가면).

Origin의 Cache-Control 응답
Cache-Control: public, max-age=31536000, immutable   ← hashed asset (1년)
Cache-Control: public, max-age=300                   ← HTML 페이지 (5분)
Cache-Control: no-store                              ← 캐시 절대 안 함

운영 기준은 다음과 같습니다.

  • HTML은 짧게(60s ~ 5min) — 새 빌드를 빠르게 보이게 합니다.
  • JS / CSS / 이미지 with hash는 1년 + immutable — 콘텐츠가 바뀌면 새 파일명이 됩니다.
  • API는 보통 캐시 안 함(또는 짧게).

S3 + CloudFront 패턴 #

정적 사이트의 표준 셋업입니다.

S3 + CloudFront 표준 패턴
사용자
example.com (Route 53 Alias) ──▶ CloudFront
                                    ▼ (OAC)
                                  S3 my-bucket  ← Public Access Block 모두 켜짐
                                    ▼ Bucket Policy
                                  CloudFront 만 GetObject 허용

OAC — Origin Access Control #

옛 방식은 **OAI (Origin Access Identity)**입니다. 새 권장 방식은 OAC입니다.

OAC의 역할
- S3 의 PAB 모두 켬 (public 절대 안 됨)
- Bucket Policy 가 CloudFront 의 OAC 만 허용:

  {
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXXXXXX"
        }
      }
    }]
  }

이렇게 하면 다음과 같습니다.

  • S3 직접 접근은 모두 차단됩니다. s3.amazonaws.com/my-bucket/x로 들어오면 403입니다.
  • CloudFront만 통과합니다. Edge 캐시를 거쳐야만 응답합니다.
  • Egress 비용 절감과 캐시 hit 가속, WAF 적용을 동시에 얻습니다.

OAI vs OAC #

OAI (옛 방식)OAC (새 방식)
인증 방식CloudFront의 가상 IAM IdentitySigV4 서명
새 리전 / 기능부분 지원모두
KMS-encrypted S3추가 설정 필요매끄러움
권장마이그레이션 검토새 셋업 기본

새 셋업은 OAC를 쓰고, 옛 방식은 OAI를 쓰다가 마이그레이션합니다. S3의 PAB와 Bucket Policy는 10장 S3의 보안 평가 순서를 그대로 따릅니다.

무효화 (Invalidation) #

캐시된 객체를 만료 전에 강제로 비우는 동작입니다. 배포 직후 새 버전을 즉시 보이게 합니다.

Invalidation 만들기
aws cloudfront create-invalidation \
  --distribution-id EXXXXXX \
  --paths "/*"

쓰는 방식은 다음과 같습니다.

  • 정적 사이트 배포 후 /* 한 줄.
  • 특정 페이지만 갱신: /index.html /about.html.
  • 와일드카드: /blog/2026/*.

비용 함정 #

CloudFront의 invalidation은 첫 1000 path가 월 무료이고, 그 후 path 당 $0.005입니다. /* 한 번이 1 path 라 자주 호출해도 괜찮습니다.

Cache Versioning 패턴 — 무효화 안 쓰기 #

진짜 운영에서는 무효화를 안 씁니다. 대신 다음 패턴을 씁니다.

Hashed filename 패턴
빌드 결과:
  /static/app.abc123.js   ← 콘텐츠 해시
  /static/app.def456.js   ← 다음 빌드는 다른 해시

HTML 은 짧게 캐시 (1분), JS/CSS 는 1년:
  Cache-Control: public, max-age=31536000, immutable

새 배포 시:
  /index.html 이 새 해시의 JS 를 가리킴
  HTML 캐시 만료 (1분) → 새 JS 가 자동 로드

이 패턴이면 invalidation 없이 즉시 반영하면서 캐시 효율을 최대로 유지합니다.

Lambda@Edge / CloudFront Functions #

Edge에서 요청 / 응답을 가로채 코드를 실행하는 역할입니다.

CloudFront Functions #

  • JavaScript ES5만 됩니다.
  • 요청 / 응답의 viewer 단계만 처리합니다.
  • 5ms 이내, 1MB 이내 메모리입니다.
  • 매우 저렴합니다($0.10 / 백만).
  • 용도는 redirect, header 추가, A/B 테스트, 인증 토큰 검사입니다.
간단한 redirect
function handler(event) {
  var request = event.request;
  if (request.uri === '/old-page') {
    return {
      statusCode: 301,
      statusDescription: 'Moved Permanently',
      headers: { 'location': { value: '/new-page' } }
    };
  }
  return request;
}

Lambda@Edge #

  • Node.js / Python을 씁니다.
  • 네 단계(viewer request / origin request / origin response / viewer response)를 처리합니다.
  • 5초(viewer) / 30초(origin) 한도입니다.
  • 비싸고 느리지만 강력합니다.
  • 용도는 동적 콘텐츠 변환, 멀티 리전 라우팅, 복잡한 인증입니다.

요즘은 가능한 한 CloudFront Functions로 시작하고, 부족할 때만 Lambda@Edge를 씁니다.

Signed URL / Signed Cookie — 비공개 콘텐츠 #

유료 비디오나 전용 다운로드 같은 경우에 씁니다. CloudFront의 서명으로 시간과 IP를 제한합니다.

Signed URL의 모양
https://d111111abcdef8.cloudfront.net/video.mp4?
  Expires=1714992000&
  Signature=...
  Key-Pair-Id=APKAEX...
  • Signed URL — 한 자원에 대한 한 URL입니다.
  • Signed Cookie — 한 사용자가 여러 자원에 접근합니다(사이트 단위 정기 구독).

S3의 presigned URL과는 다릅니다. CloudFront Edge에서 검증하므로 더 글로벌하고 캐시가 가능합니다.

CloudFront와 ACM의 리전 규칙 #

13장 ALB / NLB와 ACM에서 다룬 ACM의 리전 규칙입니다.

CloudFront 인증서는 항상 us-east-1
ACM (us-east-1)
  ├── *.example.com         ← CloudFront 가 사용
  └── example.com

ACM (ap-northeast-2)
  ├── api.example.com       ← ALB 가 사용

콘솔에서 인증서를 받을 때 반드시 N. Virginia (us-east-1)로 리전을 전환합니다.

압축과 HTTP/2 / HTTP/3 #

CloudFront가 자동으로 처리합니다.

  • gzip / Brotli 압축 — 옵션을 켭니다.
  • HTTP/2 — 기본입니다.
  • HTTP/3 (QUIC) — 옵션입니다.
  • TLS 1.3 — Security Policy에서 정합니다.

이 처리는 ALB를 직접 노출하는 것보다 CloudFront가 더 매끄럽습니다.

자주 만나는 함정 #

  • S3가 여전히 Public — OAC를 셋업했는데 PAB를 안 켜서 S3가 직접 접근 가능한 경우입니다. PAB 네 개를 모두 켜고 Bucket Policy가 CloudFront만 허용하게 합니다.
  • CNAME mismatch — ACM 인증서가 *.example.com 인데 Distribution의 Alternate domain이 app.example.com (와일드카드 자식이라 OK)인 경우는 괜찮지만, 잘못 박으면 SSL 인증서 mismatch 오류가 납니다. 인증서 SAN과 도메인을 정확히 맞춥니다.
  • 캐시가 너무 강해 새 배포가 안 보임max-age=31536000 짜리 HTML을 그대로 캐시하면 새 배포 후에도 사용자가 옛 HTML을 받습니다. HTML은 짧게, hashed asset만 길게 둡니다.
  • 쿼리 캐시 키 잘못 — 쿼리를 모두 캐시 키에 넣으면 같은 파일도 ?v=1, ?v=2 마다 별도 캐시가 되어 히트율이 0%가 됩니다. 진짜 영향이 있는 쿼리만 캐시 키에 넣습니다.
  • Default root object 누락/로 요청하면 403/404가 납니다. Default root object를 index.html로 설정합니다.
  • Origin으로 보내는 Host 헤더 — S3 Origin은 CloudFront가 자동으로 Host 헤더를 처리합니다. 하지만 ALB나 임의 서버는 Origin Request Policy에서 Host 헤더 전달 옵션을 명시합니다.
  • Origin의 SSL 인증서 신뢰 안 됨 — Custom Origin(자체 서버)이 self-signed 면 CloudFront가 거부합니다. ACM 발급 인증서나 신뢰된 CA를 씁니다.
  • index.html redirect 안 됨 — S3 정적 사이트의 /about//about/index.html 자동 변환이 OAC 구성에서는 안 됩니다. CloudFront Function으로 path rewrite를 직접 합니다.
path rewrite Function
function handler(event) {
  var request = event.request;
  var uri = request.uri;
  if (uri.endsWith('/')) {
    request.uri = uri + 'index.html';
  } else if (!uri.includes('.')) {
    request.uri = uri + '/index.html';
  }
  return request;
}

연습문제 #

  1. §“S3 + CloudFront 패턴"의 그림을 보지 않고, 사용자 요청이 도메인에서 S3까지 닿는 경로를 12장 Route 53의 Alias부터 OAC, Bucket Policy까지 순서대로 적어 보세요. 그리고 10장 S3의 정적 호스팅에서 켰던 public read 정책을 여기서는 왜 반대로 해제하는지 한 줄로 설명해 보세요.
  2. 새 배포 후에도 사용자가 옛 화면을 본다는 신고가 들어왔습니다. §“Cache Versioning 패턴"을 근거로, 무효화를 매번 호출하는 대신 어떤 캐시 설정(HTML과 hashed asset의 TTL)을 둬야 하는지 구체적으로 적어 보세요.
  3. CloudFront 인증서를 서울에서 발급하면 안 되는 이유를 §“CloudFront와 ACM의 리전 규칙"을 근거로 적고, 이것이 13장 ALB / NLB와 ACM의 리전 매핑 표와 어떻게 일치하는지 연결해 보세요.

한 줄 요약: CloudFront는 Edge 600여 곳에서 캐시 응답하는 글로벌 CDN으로, Distribution + Origin + Cache Behavior로 구성된다. S3 + CloudFront + OAC가 정적 사이트 표준 패턴이며 이때 S3의 PAB 네 개를 모두 켜고 Bucket Policy로 CloudFront만 허용한다. TTL은 HTML 짧게 hashed asset 1년이 정석이라 무효화 없이 즉시 반영되고, CloudFront 인증서는 항상 us-east-1에서 발급한다.

다음 챕터 #

이로써 2부의 EC2 / VPC / S3 / RDS / Route 53 / ALB / CloudFront라는 기본 도구상자가 한곳에 모였습니다. 다음 15장 ECS와 Fargate부터 시작하는 3부에서는 그 위에 컨테이너 / 서버리스 / 이벤트 기반 영역을 더합니다. EC2 위에 직접 앱을 띄우던 방식을 컨테이너로 옮기고, ASG의 컨테이너 버전인 ECS / Fargate의 운영을 정리합니다.

X