apex와 www를 함께 서비스할 때 — Origin 분리와 오래된 Frontend Cache 진단
example.com과 www.example.com을 같은 서비스로 열었을 때 브라우저 관점에서는 별도 Origin이라는 점을 기준으로, 특정 Host에서만 오래된 HTML·Asset 문제가 나는 상황을 진단하고 canonical redirect로 통일하는 방법을 정리한다.
example.com과 www.example.com이 같은 Backend를 바라본다고 해서 Browser에서도 하나의 상태 공간이 되는 것은 아니다.
1
2
3
4
5
https://example.com
→ Origin A
https://www.example.com
→ Origin B
따라서 한쪽 Host만 오래된 Frontend 상태를 갖고 있거나 특정 Host에서만 문제가 재현된다면 Server Process보다 먼저 Host별 Browser State와 Cache Policy를 분리해서 볼 필요가 있다.
이 글은 실제로 www는 정상인데 apex에서만 Browser 문제가 반복된 상황을 기준으로 진단 흐름을 정리한다.
먼저 사실과 가설을 분리한다
관찰된 증상이 예를 들어 다음과 같다고 하자.
1
2
3
4
5
6
7
8
9
10
11
www.example.com
→ 정상
example.com
→ Browser에서만 실패
curl
→ 두 Host 모두 Server Response 정상
Browser Data 삭제
→ 다시 정상
여기서 바로:
1
2
3
90일마다 발생
→ Let's Encrypt가 90일짜리 Certificate
→ Certificate Renewal이 원인
이라고 결론 내리면 안 된다.
Certificate 갱신은 TLS Certificate를 바꾸는 작업이고, 그 자체가 Frontend HTML/JS Version을 변경하거나 Browser HTTP Cache를 무효화하는 원인은 아니다.
주기가 우연히 Deployment, Maintenance, Restart 같은 다른 운영 이벤트와 겹쳤을 수 있으므로 시간 상관관계와 인과관계를 분리한다.
1. 같은 서비스여도 Host가 다르면 Origin이 다르다
Origin은:
1
scheme + host + port
로 구분된다.
따라서:
1
2
3
https://example.com
≠
https://www.example.com
이다.
대표적으로 다음 상태가 독립될 수 있다.
1
2
3
4
5
Local Storage
Session Storage
Service Worker Scope
Cookie Scope(설정에 따라)
HTTP Cache Entry(URL 기준)
HTTP Cache도 Resource URL이 다르면 별도 Entry다.
1
2
https://example.com/main.js
https://www.example.com/main.js
은 서로 다른 URL이다.
따라서 평소 www만 사용하고 apex는 가끔 접근한다면 apex 쪽 Browser State가 훨씬 오래 남아 있을 수 있다.
2. 먼저 Server 문제와 Browser 문제를 분리한다
TLS Certificate 확인
두 Host가 Certificate SAN에 포함되는지 본다.
1
2
3
4
openssl s_client \
-connect example.com:443 \
-servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -ext subjectAltName
www도 별도로 확인한다.
DNS 확인
1
2
dig example.com +short
dig www.example.com +short
같은 IP여야 한다는 규칙은 아니지만, 현재 의도한 Routing과 일치하는지 본다.
HTTP Response 확인
1
2
curl -sI https://example.com
curl -sI https://www.example.com
Browser에서는 실패하지만 fresh curl 요청은 정상이라면 다음 질문이 생긴다.
1
2
3
Server가 현재 잘못된 응답을 만드는가?
↓ 아니면
Browser가 과거 상태를 재사용하는가?
3. Cache는 HTML과 Hash Asset을 같은 정책으로 두지 않는다
Frontend Build에서 흔한 구조는:
1
2
3
4
index.html
↓
main.a1b2c3.js
styles.d4e5f6.css
Hash가 붙은 Asset은 Content가 바뀌면 URL도 바뀌므로 긴 Cache를 주기 좋다.
1
Cache-Control: public, max-age=31536000, immutable
반대로 index.html은 새 Asset URL을 가리키는 진입점이므로 긴 Fresh Cache를 주면 문제가 생길 수 있다.
1
2
3
4
5
6
7
오래된 index.html
↓
과거 main.oldhash.js 참조
↓
Server에서 과거 Asset 제거됨
↓
404 / Application Load 실패
따라서 진단할 때는 “Cache TTL이 길다” 한 줄보다 어떤 Resource에 긴 Cache가 붙었는지를 본다.
1
2
curl -I https://example.com/
curl -I https://example.com/main.a1b2c3.js
HTML과 Hash Asset의 Cache-Control을 따로 확인한다.
4. Browser Data 삭제로 해결된다면 어떤 상태가 사라졌는지 좁힌다
“Browser Data 삭제 → 해결”은 Browser-side State 문제라는 강한 단서지만 정확한 원인을 하나로 확정해주진 않는다.
삭제되는 것은 환경에 따라:
1
2
3
4
5
HTTP Cache
Cookie
Local Storage
Service Worker / Cache Storage
기타 Site Data
등 여러 종류일 수 있다.
따라서 DevTools에서 다음을 따로 확인한다.
1
2
3
4
5
6
7
8
Network
→ Disable cache 상태와 비교
Application
→ Local/Session Storage
→ Service Workers
→ Cache Storage
→ Cookies
특히 Service Worker가 있는 Application은 일반 HTTP Cache 외에 별도 Cache Storage를 사용할 수 있다.
5. apex와 www를 둘 다 서비스해야 하는 이유가 없다면 Canonical Host로 통일한다
같은 Application을 두 Host에서 똑같이 서비스할 필요가 없다면 한쪽을 Canonical Host로 정하는 것이 단순하다.
예를 들어 www.example.com으로 통일한다.
HTTP
1
2
3
4
5
6
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
HTTPS apex → www
1
2
3
4
5
6
7
8
9
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://www.example.com$request_uri;
}
HTTPS www — 실제 Application
1
2
3
4
5
6
7
8
9
10
11
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
적용 전후:
1
2
3
4
sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com/foo
Redirect의 Location이 의도한 Canonical Host를 가리키는지 확인한다.
6. Redirect는 Cache Policy 자체를 고치는 대체재는 아니다
Canonical Redirect는:
1
사용자가 앞으로 어느 Host를 쓰는지
를 하나로 통일한다.
하지만 index.html에 잘못된 장기 Cache Policy가 있다면 그 문제는 별도로 수정해야 한다.
1
2
3
4
5
Canonical Host
→ Origin/URL 다양성 축소
적절한 Cache-Control
→ Version별 Resource 수명 관리
두 문제를 분리한다.
7. TLS Renewal과 Application Deployment도 분리한다
운영 자동화가 다음처럼 묶여 있을 수는 있다.
1
2
3
4
5
Certificate Renewal
↓ 어떤 Script/Job
Nginx Reload
+
Application Deploy
하지만 이것은 해당 운영 Pipeline의 구현이지 Let’s Encrypt 자체의 동작이 아니다.
주기적 장애가 Certificate 갱신 시점과 겹친다면:
1
2
3
4
5
Certbot Log
Deploy Log
Nginx Reload 시간
Frontend Build Artifact 변경 시간
Browser Cache Header
를 같은 Timeline에 놓고 실제 인과를 확인한다.
진단 순서
1
2
3
4
5
6
7
1. apex와 www의 DNS/TLS가 정상인가?
2. fresh HTTP Client에서 두 Host 응답이 다른가?
3. Browser에서 Disable Cache하면 정상인가?
4. HTML과 Hash Asset의 Cache-Control은 각각 무엇인가?
5. Service Worker / Cache Storage가 있는가?
6. Canonical Host 없이 두 Origin을 계속 서비스할 이유가 있는가?
7. 반복 주기가 특정 Deploy/Renewal Job과 실제로 연결되는가?
이 순서로 보면 “90일마다 뭔가 이상하다”는 관찰을 특정 원인으로 성급하게 고정하지 않을 수 있다.
정리
apex와 www 문제의 핵심은 Nginx에서 같은 server에 적었느냐가 아니라 Browser에서는 Host가 다르면 별도의 Origin과 URL 공간이라는 것이다.
1
2
3
4
5
6
7
8
9
10
11
두 Host 운영
→ Browser State도 두 벌
오래된 HTML Cache
→ 과거 Asset 참조 위험
Canonical Redirect
→ 사용자 진입 Host 통일
Cache-Control
→ Resource 수명 별도 해결
증상 주기와 Certificate 유효기간이 같아 보여도 먼저 상관관계와 인과관계를 분리하고, 실제 Cache Entry·Deployment Timeline으로 확인하는 것이 중요하다.
nginx HTTPS 시리즈
| 글 | 다루는 것 |
|---|---|
| Let’s Encrypt + Nginx — 인증서 수명주기 | Challenge 선택 → 발급 → Renewal → Reload → 외부 TLS 검증 |
| nginx SSL 인증서 운영 | 인증서 배치, reload/restart, process 복구 |
| 특정 IP에서 HTTPS 강제 우회하기 | 내부 Probe 같은 예외 Routing |
| apex/www Origin과 Cache (현재 글) | Host별 Browser State와 Canonical Redirect |