최신 웹 API(서비스 워커, 위치정보, 클립보드, 카메라)는 개발 중에도 HTTPS를 요구합니다. 이 가이드에서는 브라우저 경고 없이 localhost에서 신뢰할 수 있는 HTTPS를 설정하는 방법을 다룹니다.
Let’s Encrypt를 localhost에 사용할 수 없는 이유
Let’s Encrypt는 서버의 파일이나 DNS 레코드를 확인하여 도메인 소유권을 검증합니다. localhost는 실제 도메인이 아닙니다 — 사용자의 컴퓨터에서만 127.0.0.1로 연결됩니다. 누구도 localhost를 “소유”하고 있다는 것을 검증할 수 없으므로, 공개 인증 기관은 이에 대한 인증서를 발급하지 않습니다.
localhost HTTPS를 위한 선택지:
- mkcert — 로컬에서 신뢰할 수 있는 인증서 생성 (권장)
- 자체 서명 인증서 — 작동하지만 브라우저 경고가 표시됨
- 터널링 서비스 — ngrok, Cloudflare Tunnel (실제 도메인, 실제 인증서)
옵션 1: mkcert (권장)
mkcert는 컴퓨터에 로컬 인증 기관을 생성하고, 시스템 신뢰 저장소에 추가한 다음, 해당 CA로 서명된 인증서를 발급합니다. 브라우저는 이 인증서를 경고 없이 신뢰합니다.
mkcert 설치
# macOS
brew install mkcert
# Linux (Firefox의 경우 libnss3-tools 필요)
sudo apt install libnss3-tools
brew install mkcert # 또는 GitHub 릴리스에서 다운로드
# Windows
choco install mkcert
# 또는: scoop install mkcert
로컬 CA 설정
mkcert -install
# 출력: Created a new local CA
# The local CA is now installed in the system trust store
이 명령은 시스템 및 브라우저 신뢰 저장소에 루트 인증서를 추가합니다. 한 번만 실행하면 이후 모든 인증서에 적용됩니다.
인증서 생성
# localhost용
mkcert localhost 127.0.0.1 ::1
# 사용자 정의 로컬 도메인용
mkcert myapp.test "*.myapp.test" localhost 127.0.0.1
# 출력:
# Created a new certificate valid for the following names:
# - "localhost"
# - "127.0.0.1"
# - "::1"
# The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem"
개발 서버에서 사용
Node.js/Express:
const https = require('https');
const fs = require('fs');
const app = require('./app');
https.createServer({
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
}, app).listen(3000);
Nginx (로컬):
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /path/to/localhost+2.pem;
ssl_certificate_key /path/to/localhost+2-key.pem;
# ...
}
Vite / webpack dev server:
// vite.config.js
import fs from 'fs';
export default {
server: {
https: {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
},
},
};
보안 경고
mkcert 루트 CA 키를 절대 공유하지 마세요 (mkcert -CAROOT 경로의 rootCA-key.pem). 이 키를 가진 사람은 컴퓨터에서 모든 도메인에 대해 신뢰할 수 있는 인증서를 만들 수 있습니다 — 사실상 로컬 중간자 공격 능력을 갖게 됩니다.
mkcert는 개발 전용입니다. 프로덕션에서는 Let’s Encrypt를 사용하세요.
옵션 2: 자체 서명 인증서
mkcert를 설치할 수 없는 경우, OpenSSL로 자체 서명 인증서를 생성합니다:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-keyout localhost-key.pem -out localhost-cert.pem \
-days 365 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
단점: 자체 서명 인증서는 어떤 인증 기관에서도 신뢰하지 않으므로 브라우저에 보안 경고가 표시됩니다. 매번 경고를 클릭하여 넘어가야 합니다(또는 예외를 추가해야 합니다).
옵션 3: 터널링 (실제 도메인 + 실제 인증서)
터널링 서비스를 사용하여 로컬 서버를 실제 도메인과 인증서로 노출합니다:
# ngrok
ngrok http 3000
# 제공 URL: https://abc123.ngrok.io
# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000
# 제공 URL: https://random-name.trycloudflare.com
적합한 경우: 웹훅 테스트, 팀원과 공유, HTTPS가 필요한 OAuth 콜백 테스트.
mkcert vs 자체 서명 vs 터널링
| mkcert | 자체 서명 | 터널링 | |
|---|---|---|---|
| 브라우저 경고 | 없음 | 있음 (매 세션마다) | 없음 |
| 설정 난이도 | 낮음 (한 번 설치) | 낮음 (명령 하나) | 낮음 |
| 오프라인 사용 | ✅ | ✅ | ❌ |
| 성능 | 로컬 속도 | 로컬 속도 | 네트워크 지연 |
| 사용자 정의 도메인 | ✅ (*.test 등) | ✅ | 서비스 제공자가 할당 |
| 추천 용도 | 일상 개발 | 일회성 빠른 작업 | 웹훅, 공유 |
자주 묻는 질문
팀 전체에서 같은 mkcert 인증서를 사용할 수 있나요?
루트 CA 키는 공유하지 마세요. 각 개발자가 자신의 컴퓨터에서 mkcert -install을 실행하여 자신만의 로컬 CA를 만들어야 합니다. 생성된 인증서(.pem 파일)는 공유할 수 있지만, 각 사람이 CA를 설치해야 인증서를 신뢰할 수 있습니다.
mkcert는 Docker에서 작동하나요?
네. 인증서 파일을 컨테이너에 마운트하고 서버 설정에서 참조하면 됩니다. 컨테이너 자체에는 mkcert를 설치할 필요가 없고 .pem 파일만 있으면 됩니다. 브라우저가 실행되는 호스트 컴퓨터에 mkcert CA가 설치되어 있어야 합니다.
어떤 로컬 도메인을 사용해야 하나요?
.test를 사용하세요 (예: myapp.test) — IETF에서 테스트용으로 예약한 도메인이므로 실제 도메인과 절대 충돌하지 않습니다. .dev(Google 소유)와 .local(mDNS에서 사용)은 피하세요. /etc/hosts에 항목을 추가하여 .test 도메인을 127.0.0.1로 연결하세요.
localhost HTTPS에서 프로덕션 HTTPS로 전환하려면?
완전히 별개의 과정입니다. localhost 인증서(mkcert/자체 서명)는 개발 컴퓨터에 남습니다. 프로덕션에서는 Let’s Encrypt를 통해 GetHTTPS에서 실제 인증서를 발급받으세요. 코드에서 인증서 경로를 환경 변수에서 읽도록 하면 설정 변경만으로 전환이 가능합니다.