
Mixed Content 오류란?
HTTPS로 로드된 웹페이지에서 HTTP 리소스(API, 이미지, 스크립트 등)를 요청할 때 발생하는 브라우저 보안 오류입니다.
Mixed Content: The page at 'https://example.com' was loaded over HTTPS,
but requested an insecure XMLHttpRequest endpoint 'http://api.example.com'.
This request has been blocked; the content must be served over HTTPS.
왜 차단될까?
- 보안: HTTPS는 암호화된 연결, HTTP는 평문 연결
- 중간자 공격 방지: HTTP 요청은 중간에 가로채기/변조 가능
- 브라우저 정책: 모던 브라우저는 Mixed Content를 기본적으로 차단
프론트엔드 해결 방법
1. API URL을 HTTPS로 변경 (권장)
// ❌ 잘못된 예
const API_BASE_URL = 'http://api.example.com';
// ✅ 올바른 예
const API_BASE_URL = 'https://api.example.com';
2. 환경변수로 URL 관리
# .env.development (로컬 개발)
VITE_API_URL=http://localhost:8080
# .env.production (배포 환경)
VITE_API_URL=https://api.example.com
// src/config/api.js
const API_BASE_URL = import.meta.env.VITE_API_URL || 'https://api.example.com';
export default API_BASE_URL;
3. 프로토콜 상대 URL 사용 (임시방편)
// 현재 페이지의 프로토콜을 따라감
const API_BASE_URL = '//api.example.com';
// https://example.com → https://api.example.com으로 요청
// http://example.com → http://api.example.com으로 요청
⚠️ 주의: 이 방법은 로컬 개발(http)과 배포(https) 모두에서 작동하지만, 백엔드가 HTTPS를 지원해야 합니다.
4. 개발 환경에서 프록시 설정
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://43.201.107.27:8080',
changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, '/api')
}
}
}
}
// 프론트엔드 코드에서는 상대 경로 사용
const response = await fetch('/api/auth/login', {
method: 'POST',
// ...
});
장점:
- CORS 문제도 함께 해결
- 로컬 개발 시 편리
단점:
- 배포 환경에서는 다른 설정 필요
- 프로덕션에서는 실제 URL로 요청해야 함
5. 전체 프로젝트에서 HTTP URL 검색
# HTTP URL이 남아있는지 확인
grep -r "http://" src/
find . -name "*.js" -o -name "*.jsx" | xargs grep "http://"
# 특정 도메인 검색
grep -r "http://43.201.107.27" .
6. 배포 후 캐시 클리어
# 1. 새 커밋 푸시
git add .
git commit -m "fix: Change API URL to HTTPS"
git push origin main
# 2. 브라우저 강력 새로고침
# Windows/Linux: Ctrl + Shift + R
# Mac: Cmd + Shift + R
# 3. 시크릿 모드로 테스트
백엔드 해결 방법
1. SSL/TLS 인증서 설치 (근본적 해결)
A. Let's Encrypt (무료, 권장)
# Certbot 설치
sudo apt-get update
sudo apt-get install certbot
# 도메인이 있는 경우
sudo certbot certonly --standalone -d api.example.com
# IP 주소만 있는 경우 (제한적)
# Let's Encrypt는 IP 주소에 대한 인증서 발급 불가
B. AWS Certificate Manager (AWS 사용 시)
- AWS ELB/ALB에 HTTPS 리스너 추가
- ACM에서 무료 SSL 인증서 발급
- 백엔드는 HTTP로 유지 가능 (ELB가 SSL 처리)
C. 자체 서명 인증서 (개발/테스트용)
# 자체 서명 인증서 생성
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
⚠️ 주의: 자체 서명 인증서는 브라우저에서 경고 발생
2. 웹 서버 HTTPS 설정
Nginx 설정
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
Node.js (Express) HTTPS 설정
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
const options = {
key: fs.readFileSync('/path/to/private-key.pem'),
cert: fs.readFileSync('/path/to/certificate.pem')
};
https.createServer(options, app).listen(443, () => {
console.log('HTTPS Server running on port 443');
});
Spring Boot HTTPS 설정
# application.yml
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: your-password
key-store-type: PKCS12
key-alias: tomcat
3. CORS 설정 함께 해결
HTTPS로 전환하면 Origin이 변경되므로 CORS 설정도 업데이트해야 합니다.
Express.js
const cors = require('cors');
app.use(cors({
origin: [
'http://localhost:5173', // 로컬 개발
'http://localhost:4173', // 로컬 프리뷰
'https://yourapp.vercel.app', // 프로덕션
'https://www.yourdomain.com' // 커스텀 도메인
],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
Spring Boot
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:5173",
"http://localhost:4173",
"https://yourapp.vercel.app"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
4. 클라우드 서비스 활용
A. AWS ELB/ALB
[브라우저] --HTTPS--> [ELB:443] --HTTP--> [EC2:8080]
- ELB에서 SSL/TLS 종료
- 백엔드는 HTTP로 유지 가능
- 가장 간단하고 권장되는 방법
B. Cloudflare
- DNS를 Cloudflare로 변경
- 자동으로 HTTPS 적용
- 무료 SSL 인증서 제공
C. ngrok (개발/테스트용)
# ngrok 실행
ngrok http 8080
# HTTPS URL 생성됨
# https://abc123.ngrok.io → http://localhost:8080
단계별 해결 전략
빠른 임시 해결 (개발 중)
- 프론트엔드에서 프록시 설정 (vite.config.js)
- 상대 경로로 API 호출
정식 해결 (배포 환경)
- 백엔드팀: SSL 인증서 설치 + HTTPS 설정
- 프론트엔드팀: API URL을 HTTPS로 변경
- 양쪽 모두: CORS 설정 업데이트
IP 주소만 있는 경우
- 도메인 구매 (무료: Freenom, 저렴: Namecheap)
- AWS ELB 사용 (도메인 없이 HTTPS 가능)
- 또는 개발 환경에서만 프록시 사용
체크리스트 >
프론트엔드
- [ ] 모든 API 호출이 HTTPS인지 확인
- [ ] 환경변수로 URL 관리
- [ ] 배포 후 캐시 클리어
- [ ] 브라우저 콘솔에서 Mixed Content 오류 확인
- [ ] 개발 환경에서 프록시 설정 (선택)
백엔드
- [ ] SSL 인증서 설치
- [ ] HTTPS 포트(443) 오픈
- [ ] 웹 서버 HTTPS 설정
- [ ] HTTP → HTTPS 리다이렉트 설정
- [ ] CORS 설정에 HTTPS origin 추가
- [ ] 방화벽/보안 그룹 설정 확인
자주 하는 실수
1. 일부 URL만 변경
// ❌ 나쁜 예
const API_URL = 'https://api.example.com';
const IMAGE_URL = 'http://cdn.example.com'; // 여기도 HTTPS 필요!
2. 환경변수 미적용
// ❌ 코드에 하드코딩
const API_URL = 'https://43.201.107.27:8080';
// ✅ 환경변수 사용
const API_URL = import.meta.env.VITE_API_URL;
3. 배포 후 캐시 미클리어
- 코드를 변경했는데도 오류가 계속 발생
- 브라우저 캐시나 CDN 캐시 때문
4. CORS 설정 누락
- HTTPS로 변경하면 Origin이 바뀜
- CORS 설정도 함께 업데이트 필요
디버깅 팁
1. 실제 요청 URL 확인
// API 파일에 임시 로그 추가
console.log('🔍 API_BASE_URL:', API_BASE_URL);
console.log('🔍 Full URL:', `${API_BASE_URL}/api/auth/login`);
2. 네트워크 탭 확인
- F12 → Network 탭
- 실제 요청된 URL과 프로토콜 확인
- Status Code 확인
3. curl로 백엔드 테스트
# HTTP 테스트
curl -I http://43.201.107.27:8080/api/health
# HTTPS 테스트
curl -I https://43.201.107.27:8080/api/health
# 자세한 정보
curl -v https://43.201.107.27:8080/api/health
4. 전체 프로젝트 검색
# HTTP URL 찾기
grep -r "http://" src/ --include="*.js" --include="*.jsx"
# 특정 도메인 찾기
find . -type f \( -name "*.js" -o -name "*.jsx" \) -exec grep -l "43.201.107.27" {} \;
결론
Mixed Content 오류는 HTTPS 페이지에서 HTTP 리소스를 요청할 때 발생합니다.
근본적 해결책: 백엔드를 HTTPS로 전환 임시 해결책: 프론트엔드에서 프록시 사용 (개발 환경만)
프론트엔드와 백엔드 팀이 협업하여 HTTPS 전환을 완료하면, 보안과 사용자 경험 모두를 개선할 수 있습니다.
(추가 글)
Mixed Content 에러 해결하기 (feat: Vercel Serverless Function)
HTTPS로 배포된 React 애플리케이션에서 HTTP API(서울시 문화행사 정보)를 호출하려고 할 때 Mixed Content 에러가 발생한다.하필 서울시 open api는 https 프로토콜을 제공하지 않기 때문에 (https로 변환하
velog.io
mixed content error 처리하기 (with. Nginx)
mixed content란?"혼합 콘텐츠 오류"는 웹 페이지에서 보안 연결(HTTPS)을 통해 제공되는 페이지에 비해 보안되지 않은 연결(HTTP)을 통해 로드되는 콘텐츠가 있는 경우 발생한다. 일반적으로 HTTPS로 보
velog.io
'개발 이모저모' 카테고리의 다른 글
| cors 에러는 무엇인가. (4) | 2025.11.05 |
|---|---|
| 무중단 배포란? (0) | 2025.10.26 |
| CodeRabbit이 무엇인가? (0) | 2025.10.24 |
| 드래그 막아둔 블로그 뚫기 (1) | 2025.10.01 |
| 제대로 이해하는 RESTful - API (10) | 2025.09.02 |