혼합 콘텐츠는 HTTPS 페이지가 하위 리소스(이미지, 스크립트, 스타일시트, 폰트)를 평문 HTTP를 통해 로드할 때 발생합니다. 브라우저는 이를 차단하거나 경고하는데, 보안 페이지에 안전하지 않은 리소스가 있으면 전체 페이지의 보안이 무력화되기 때문입니다.
두 가지 유형의 혼합 콘텐츠
| 유형 | 예시 | 브라우저 동작 |
|---|---|---|
| 능동적 (위험) | 스크립트, iframe, XHR, CSS | 모든 브라우저에서 차단 |
| 수동적 (표시) | 이미지, 오디오, 비디오 | 경고 표시 / 자물쇠 아이콘 저하 |
능동적 혼합 콘텐츠는 페이지를 수정할 수 있으므로(중간자 공격으로 악성 JavaScript를 삽입할 수 있음) 브라우저가 완전히 차단합니다. 수동적 혼합 콘텐츠는 덜 위험하지만 보안 표시기를 저하시킵니다.
혼합 콘텐츠 찾는 방법
브라우저 개발자 도구
- Chrome/Firefox에서 사이트를 엽니다
- 개발자 도구(F12) → 콘솔 탭을 엽니다
- 다음과 같은 오류를 찾습니다:
Mixed Content: The page at 'https://example.com' was loaded over HTTPS, but requested an insecure resource 'http://example.com/image.jpg'.
커맨드라인 스캔
curl -s https://yourdomain.com | grep -oP 'http://[^"'"'"'> ]+' | sort -u
일반적인 원인과 해결 방법
HTML에 하드코딩된 http:// URL
<!-- 문제 -->
<img src="http://example.com/logo.png">
<!-- 해결: 프로토콜 상대 경로 또는 HTTPS 사용 -->
<img src="https://example.com/logo.png">
<img src="//example.com/logo.png">
<img src="/logo.png">
모범 사례: 가능한 한 상대 경로(/images/logo.png)를 사용하세요.
아직 HTTP인 서드파티 리소스
<!-- 문제 -->
<script src="http://cdn.example.com/library.js"></script>
<!-- 해결 -->
<script src="https://cdn.example.com/library.js"></script>
서드파티가 HTTPS를 지원하지 않으면 대체 제공자를 찾거나 리소스를 직접 호스팅하세요.
데이터베이스에 하드코딩된 URL
WordPress 및 기타 CMS는 데이터베이스에 절대 URL을 저장합니다. HTTPS로 마이그레이션한 후:
-- WordPress: 게시물의 URL 업데이트
UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://example.com', 'https://example.com');
UPDATE wp_options SET option_value = REPLACE(option_value, 'http://example.com', 'https://example.com');
또는 Better Search Replace 플러그인을 사용하세요.
HTTP 참조가 있는 CSS
/* 문제 */
background-image: url('http://example.com/bg.jpg');
/* 해결 */
background-image: url('https://example.com/bg.jpg');
background-image: url('/images/bg.jpg');
Content Security Policy (CSP) — 향후 혼합 콘텐츠 방지
HTTP 리소스를 차단하는 CSP 헤더를 추가합니다:
Nginx:
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Apache:
Header always set Content-Security-Policy "upgrade-insecure-requests"
upgrade-insecure-requests는 브라우저에게 http:// 요청을 자동으로 https://로 업그레이드하도록 지시합니다 — 소스 URL을 수정하는 동안의 안전망입니다.
체계적인 해결: 전체 사이트 감사
철저한 정리를 위해 전체 사이트를 감사합니다:
1. HTTP 참조 크롤링
# 모든 HTML 파일에서 http:// URL 검색
find /var/www/html -name '*.html' -o -name '*.php' | xargs grep -l 'http://' 2>/dev/null
# CSS 파일 검색
find /var/www/html -name '*.css' | xargs grep -l 'http://' 2>/dev/null
# JavaScript 파일 검색
find /var/www/html -name '*.js' | xargs grep -l 'http://' 2>/dev/null
2. 카테고리별 URL 수정
| 출처 | 해결 방법 |
|---|---|
| 자체 리소스 | 상대 경로로 변경 (/images/logo.png) |
| CDN 리소스 | https://cdn.example.com/...으로 업데이트 |
| 서드파티 스크립트 | URL 업데이트 또는 HTTPS 대안 찾기 |
| 인라인 CSS | http:// → https:// 일괄 교체 |
| 데이터베이스 콘텐츠 (WordPress) | wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' |
| 테마/템플릿 파일 | 소스 파일 직접 편집 |
3. 브라우저 개발자 도구로 확인
수정 후 각 페이지에서 개발자 도구(F12) → 콘솔 탭을 엽니다. 깨끗한 페이지에는 혼합 콘텐츠 경고가 없어야 합니다. 자물쇠 아이콘이 온전하게(깨지거나 경고 삼각형 없이) 표시되어야 합니다.
4. 향후 혼합 콘텐츠 방지
영구적인 안전망으로 HTML <head>에 다음 메타 태그를 추가하세요:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
또는 Nginx/Apache 설정을 통해 서버 전체에 적용합니다(위에 표시된 방법).
혼합 콘텐츠를 자주 유발하는 콘텐츠 유형
| 콘텐츠 유형 | 확인 위치 | 예시 |
|---|---|---|
| 이미지 | <img src="http://..."> | 업로드된 이미지, 아바타, 로고 |
| 스크립트 | <script src="http://..."> | 분석, 채팅 위젯, 광고 스크립트 |
| 스타일시트 | <link href="http://..."> | 외부 폰트, CSS 프레임워크 |
| 폰트 | @font-face { src: url("http://...") } | Google Fonts(보통 문제 없음), 커스텀 폰트 |
| iframe | <iframe src="http://..."> | 임베디드 비디오, 지도, 위젯 |
| XHR/Fetch | fetch("http://...") | JavaScript의 API 호출 |
| 배경 이미지 | background-image: url("http://...") | CSS 배경 |
자주 묻는 질문
혼합 콘텐츠가 SEO에 영향을 미치나요?
직접적으로는 아닙니다. Google은 페이지의 URL 프로토콜을 기준으로 크롤링하며, 하위 리소스를 기준으로 하지 않습니다. 그러나 혼합 콘텐츠가 브라우저 경고를 유발하거나 보이는 콘텐츠를 차단하면 사용자 경험이 저하되고, 이는 높은 이탈률을 통해 간접적으로 순위에 영향을 줄 수 있습니다.
upgrade-insecure-requests만 사용하고 URL을 수정하지 않아도 되나요?
임시 방편으로는 작동하지만, 소스 URL을 수정하는 것이 더 좋습니다. CSP 헤더는 브라우저 지원에 의존하며(모든 최신 브라우저는 지원하지만 일부 이전 클라이언트는 지원하지 않음), 모든 응답에 추가 헤더를 붙입니다.
사이트에 하드코딩된 HTTP URL이 수백 개 있습니다. 가장 빠른 해결 방법은?
upgrade-insecure-requests CSP 헤더를 즉시 추가합니다(서버 설정 한 줄). 그런 다음 코드베이스와 데이터베이스에서 전체 검색 및 교체를 수행합니다. grep -r 'http://' src/로 코드에 하드코딩된 URL을 찾으세요.