AWS 중급 #7 CloudFront로 정적 사이트 배포

8 분 소요

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

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

이 글에선 CloudFront의 구조 → S3 + CloudFront 패턴 → OAC → 캐시 정책 → 무효화까지 한 줄로 정리합니다.

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이 되는 방식입니다. 서버리스 정적 + 동적 사이트에 씁니다.

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 = 보통 캐시 안 함 (또는 short)

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 그대로 쓰다 마이그레이션합니다.

무효화 (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 라 자주 호출 OK.

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
  • 4 단계 (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 #

#6에서 다룬 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가 더 매끄럽습니다.

자주 만나는 함정 #

1) S3가 여전히 Public #

OAC 셋업했는데 PAB를 안 켜서 S3가 직접 접근 가능. PAB 4개 모두 켜기 + Bucket Policy가 CloudFront만 허용.

2) CNAME mismatch #

ACM 인증서가 *.example.com인데 Distribution의 Alternate domain이 app.example.com (와일드카드 자식이라 OK). 잘못 적으면 SSL 인증서 mismatch 오류. 인증서 SAN과 도메인을 정확히 맞추기.

3) 캐시가 너무 강해 새 배포가 안 보임 #

max-age=31536000 짜리 HTML을 그대로 캐시 → 새 배포 후에도 사용자가 옛 HTML 받음. HTML은 짧게 / hashed asset만 길게.

4) 쿼리 캐시 키 잘못 #

쿼리 모두 캐시 키에 → 같은 파일도 ?v=1, ?v=2마다 별도 캐시 → 히트율 0%. 진짜 영향이 있는 쿼리만 캐시 키에.

5) Default root object 누락 #

/로 요청하면 403/404. Default root object = index.html 설정.

6) Origin으로 보내는 Host 헤더 #

S3 Origin의 경우 CloudFront가 자동으로 Host 헤더 처리. 하지만 ALB / 임의 서버는 Origin Request Policy에서 Host 헤더 전달 옵션를 명시.

7) Origin의 SSL 인증서 신뢰 안 됨 #

Custom Origin (자체 서버)이 self-signed → CloudFront가 거부. ACM 발급 인증서 또는 신뢰된 CA.

8) 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;
}

정리 #

이번 글에서 잡은 것:

  • CloudFront = 글로벌 CDN. Edge 600+ 곳에서 캐시 응답
  • Distribution + Origin + Cache Behavior의 셋
  • Origin = S3 / ALB / Custom HTTP / Lambda URL / Origin Group
  • S3 + CloudFront + OAC가 정적 사이트 표준 패턴
  • OAC가 새 권장. PAB 4개 모두 켜고 Bucket Policy로 CloudFront만 허용
  • Cache Policy로 캐시 키 (쿼리/헤더/쿠키) + TTL. Origin Request Policy는 Origin으로 보낼 대상
  • TTL 전략. HTML 짧게, hashed asset 1년 + immutable
  • 무효화는 가끔. Hashed filename 패턴이 무효화 안 쓰는 길
  • CloudFront Functions = 빠르고 저렴. Lambda@Edge = 강력하지만 무거움
  • Signed URL / Cookie로 비공개 콘텐츠
  • ACM 인증서는 반드시 us-east-1
  • 압축 / HTTP/2 / HTTP/3 자동
  • 함정. S3 public, CNAME mismatch, 캐시 너무 강함, 쿼리 키 잘못, default root, Host 헤더, Origin SSL, path rewrite

다음: AWS 고급 시작 #

이로써 AWS 중급 7편이 마무리됐습니다. EC2 / VPC / S3 / RDS / Route 53 / ALB / CloudFront라는 기본 도구상자가 하나로 모였습니다.

이제 그 위에 컨테이너 / 서버리스 / 이벤트 기반 영역이 더해집니다. AWS 고급 #1 ECS와 Fargate에서는 EC2 위에 직접 앱을 띄우던 방식을 컨테이너로 옮기고, ASG의 컨테이너 버전인 ECS / Fargate의 운영 방식을 정리하겠습니다.

X