blog

로컬 개발 서버에서 모바일 카메라 테스트하기: HTTP에서 getUserMedia가 막히는 이유

Front-end / Next.js#getUserMedia#HTTPS#ngrok#secure context#모바일

모바일에서 웹 카메라를 쓰는 기능을 개발할 때, 로컬이나 사내망에서 http://IP:포트로 띄운 서버로는 카메라에 접근할 수 없다. 왜 그런지, 그리고 개발 중에 HTTPS를 가장 쉽게 얻는 방법을 정리했다.

1. 왜 HTTP에서는 카메라가 안 될까?

카메라·마이크 API인 navigator.mediaDevices.getUserMedia는 보안 컨텍스트(secure context) 에서만 동작한다. 보안 컨텍스트가 아니면 navigator.mediaDevices 자체가 undefined다.

보안 컨텍스트로 인정되는 경우:

  • https://로 제공되는 페이지
  • http://localhost, http://127.0.0.1 (로컬 루프백은 주요 브라우저 모두 예외로 허용)

http://192.168.0.10:3000처럼 사설 IP나 일반 도메인의 HTTP 주소는 보안 컨텍스트가 아니다. PC에서는 localhost로 열어 잘 되는데, 같은 서버를 휴대폰에서 IP로 열면 안 되는 이유가 이것이다.

2. 데스크톱 Chrome에서 특정 HTTP 주소를 secure로 취급하기

# Windows
chrome.exe --user-data-dir=C:\temp\chrome-test --unsafely-treat-insecure-origin-as-secure="http://192.168.0.10:3000"

# macOS
open -na "Google Chrome" --args --user-data-dir=/tmp/chrome-test --unsafely-treat-insecure-origin-as-secure="http://192.168.0.10:3000"

지정한 HTTP 출처를 보안 컨텍스트처럼 취급한다. --user-data-dir로 별도 프로필을 쓰면 평소 프로필에 영향을 주지 않는다.

데스크톱 개발자용 우회책일 뿐이고, 모바일 브라우저에는 이런 실행 옵션이 없다.

3. Android: USB 포트 포워딩으로 localhost 쓰기

Android Chrome이라면 HTTPS 없이도 방법이 있다. PC Chrome의 chrome://inspect → Port forwarding에서 3000 → localhost:3000을 설정하고 USB 디버깅으로 기기를 연결하면, 휴대폰에서 http://localhost:3000으로 PC의 개발 서버에 접속할 수 있다. 주소가 localhost이므로 보안 컨텍스트로 인정된다.

iOS Safari에는 이런 방법이 없으므로 HTTPS 주소가 필요하다.

4. 터널링: 로컬 서버에 공개 HTTPS 주소 붙이기

ngrok, Cloudflare Tunnel 같은 터널링 서비스는 로컬 서버를 외부에서 접근 가능한 HTTPS 주소로 노출해 준다.

ngrok config add-authtoken YOUR_AUTHTOKEN
ngrok http 3000
Forwarding  https://abc123.ngrok-free.app -> http://localhost:3000

휴대폰에서 출력된 https:// 주소로 접속하면 카메라 권한 요청이 정상적으로 뜬다. 신뢰된 인증서가 붙어 있으므로 iOS를 포함한 모든 기기에서 경고 없이 동작한다.

터널 주소는 외부에서 누구나 접근할 수 있다는 점을 기억한다. 사내 데이터가 연결된 개발 서버를 노출할 때는 접근 제한(ngrok의 OAuth·IP 제한 등)을 건다.

5. Next.js 개발 서버를 HTTPS로 띄우기

next dev --experimental-https

Next.js가 mkcert로 로컬 CA와 인증서를 만들어 https://localhost:3000으로 개발 서버를 띄운다. 같은 PC의 브라우저에서는 이 CA를 신뢰하므로 경고 없이 열린다.

주의할 점:

  • 인증서가 localhost 기준이라 https://192.168.x.x:3000으로 접속하면 이름 불일치 경고가 난다. LAN IP까지 포함하려면 mkcert로 직접 발급해 --experimental-https-key, --experimental-https-cert로 지정한다.
  • 다른 기기(휴대폰)는 이 로컬 CA를 모르므로, 기기에 mkcert의 루트 CA를 설치하고 신뢰 설정을 해야 경고가 사라진다.
  • experimental 옵션이므로 버전에 따라 동작이 바뀔 수 있다.
  • 같은 프로젝트를 HTTP와 HTTPS로 번갈아 띄우면 쿠키(Secure 속성)와 Service Worker 상태가 꼬일 수 있다. 개발 중에는 한쪽으로 통일한다.

6. PC에서는 되는데 모바일에서만 안 될 때

  1. 쿠키 속성: 터널 도메인으로 바뀌면서 쿠키 Domain, SameSite, Secure 설정이 맞지 않으면 로그인 세션이 유지되지 않는다. OAuth 콜백 URL도 터널 도메인으로 등록되어 있는지 확인한다.
  2. Mixed Content: 페이지는 https://인데 프론트 코드가 http://192.168...로 API를 호출하면 브라우저가 차단한다. API 주소도 같은 HTTPS 출처나 상대 경로로 맞춘다.
  3. Service Worker·캐시: 예전에 등록된 Service Worker가 이전 API 경로로 요청을 가로채고 있을 수 있다. 시크릿 모드(쿠키·캐시·SW가 없는 깨끗한 세션)에서만 잘 된다면, 해당 사이트의 데이터를 지우고 Service Worker를 해제한다.

결론

  • 데스크톱은 localhost로 열면 된다.
  • Android는 USB 포트 포워딩으로 localhost를 쓸 수 있다.
  • iOS까지 포함하면 결국 신뢰된 HTTPS 주소가 필요하고, 개발 중에는 터널링이 가장 간단하다.
  • 운영 환경은 정식 도메인과 인증서(Let's Encrypt 등)로 HTTPS를 구성한다.