-
로컬 개발 환경에서 가상호스트(Virtual Host)를 써야 하는 이유와 설정 방법 🙏웹 개발 2026. 7. 26. 18:24
localhost:3000으로만 개발하고 있다면, 이 글을 읽고 나서 생각이 바뀔 수도 있어요.1. 왜 가상호스트가 필요한가요?
개발 초반에는
localhost:3000,127.0.0.1:8080같은 포트 기반 방식으로 충분하게 느껴져요. 그런데 프로젝트가 조금만 복잡해지면 생각지 못한 곳에서 문제가 생기기 시작해요.포트 기반 방식의 한계
- 쿠키 domain 분리가 안 돼요.
localhost는 서브도메인을 지원하지 않아서,domain=.myapp.local같은 방식으로 쿠키를 공유할 수가 없어요. - CORS / SameSite 정책이 달라져요. 브라우저가
localhost를 특별하게 취급하는 경우가 있어서, 운영 환경에서 터지는 CORS 문제를 로컬에서 재현하기 어렵습니다. - OAuth redirect_uri 문제. 인증 서버에 포트 번호가 포함된 URI를 등록해야 하는 번거로움이 생겨요.
- 포트 충돌 관리가 피곤해요. 프로젝트가 여러 개면 포트 번호를 외우고 관리해야 하죠.
가상호스트 방식의 이점
api.myapp.local,admin.myapp.local처럼 운영 환경과 동일한 도메인 구조를 로컬에서 그대로 재현할 수 있어요.- 쿠키
domain=.myapp.local설정으로 서브도메인 간 쿠키 공유가 가능해져요. - Nginx upstream, 리버스 프록시 같은 인프라 설정을 로컬에서 직접 테스트할 수 있어요.
- 프로젝트마다 도메인으로 구분하니 포트 충돌 걱정도 없어요.
💡 핵심 원칙 — 로컬 개발 환경이 운영 환경과 구조적으로 다를수록, 배포 직후 터지는 버그가 늘어납니다. 가상호스트는 그 구조적 간극을 좁히는 가장 저비용 방법이에요.
2. 어떻게 동작하나요? — DNS 해석 흐름
브라우저가
myapp.local을 요청할 때 내부적으로 이런 순서로 처리돼요.브라우저 요청 → /etc/hosts 조회 (DNS 서버보다 먼저!) → 127.0.0.1 매핑 발견 → 로컬 웹서버 도달 → Host 헤더로 ServerName 매칭 → DocumentRoot 응답운영체제는 DNS 서버에 쿼리를 보내기 전에
/etc/hosts파일을 먼저 확인해요. 여기서 도메인-IP 매핑을 찾으면 외부 DNS 쿼리 자체가 발생하지 않습니다.웹서버는 HTTP 요청의
Host헤더를 읽어서 어떤 가상호스트 블록을 사용할지 결정해요. 이게 바로 하나의 IP로 여러 도메인을 처리할 수 있는 원리예요.3. 1단계: /etc/hosts 파일 등록
macOS / Linux
sudo nano /etc/hosts아래 내용을 추가해 주세요.
# 로컬 개발 프로젝트 127.0.0.1 myapp.local 127.0.0.1 api.myapp.local 127.0.0.1 admin.myapp.local저장 후 DNS 캐시를 초기화해야 해요.
# macOS Monterey 이상 sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder # Linux (systemd-resolved) sudo systemctl restart systemd-resolvedWindows
메모장을 관리자 권한으로 실행한 다음 아래 파일을 열어주세요.
C:\Windows\System32\drivers\etc\hosts내용을 추가하고 저장한 뒤, cmd에서 DNS 캐시를 초기화해요.
ipconfig /flushdns4. 2단계: 웹서버 가상호스트 설정
Apache (macOS / Linux)
먼저
httpd.conf에서 가상호스트 관련 설정이 활성화되어 있는지 확인해 주세요.# 아래 두 줄의 주석(#)이 제거되어 있어야 해요 LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so Include /private/etc/apache2/extra/httpd-vhosts.conf그다음
httpd-vhosts.conf파일을 편집해요.# 기본 호스트 — 매칭 실패 시 fallback <VirtualHost *:80> ServerName localhost DocumentRoot /var/www/html </VirtualHost> # 프론트엔드 앱 <VirtualHost *:80> ServerName myapp.local DocumentRoot /Users/you/projects/myapp/public <Directory "/Users/you/projects/myapp/public"> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> ErrorLog /var/log/apache2/myapp-error.log CustomLog /var/log/apache2/myapp-access.log combined </VirtualHost> # API 서버 — Node/Python 등 백엔드 리버스 프록시 <VirtualHost *:80> ServerName api.myapp.local ProxyPreserveHost On ProxyPass / http://127.0.0.1:3000/ ProxyPassReverse / http://127.0.0.1:3000/ ErrorLog /var/log/apache2/api-error.log CustomLog /var/log/apache2/api-access.log combined </VirtualHost>설정을 검증하고 재시작해 주세요.
apachectl configtest # Syntax OK 확인 sudo apachectl restart # Homebrew Apache를 쓰고 있다면 brew services restart httpd⚠️ 리버스 프록시를 쓰려면
httpd.conf에서mod_proxy,mod_proxy_http모듈 주석을 해제해야 해요.Nginx (macOS / Linux)
/etc/nginx/sites-available/경로에 설정 파일을 만들어요.# 정적 파일 서빙 server { listen 80; server_name myapp.local; root /Users/you/projects/myapp/public; index index.html; location / { try_files $uri $uri/ /index.html; # SPA가 아니라면 =404 로 변경해요 } access_log /var/log/nginx/myapp-access.log; error_log /var/log/nginx/myapp-error.log; } # 리버스 프록시 — Node/FastAPI 등 server { listen 80; server_name api.myapp.local; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }symlink를 연결하고 재시작해요.
# sites-available → sites-enabled 심링크 생성 sudo ln -s /etc/nginx/sites-available/myapp.local \ /etc/nginx/sites-enabled/ sudo nginx -t # 설정 검증 sudo nginx -s reload # 재시작 # Homebrew Nginx를 쓰고 있다면 brew services restart nginx💡 macOS Homebrew Nginx 경로
- Apple Silicon:
/opt/homebrew/etc/nginx/ - Intel Mac:
/usr/local/etc/nginx/
servers/폴더에 conf 파일을 직접 넣으면 자동으로 include돼요.5. Windows 환경 (XAMPP / IIS)
XAMPP (Apache)
C:\xampp\apache\conf\extra\httpd-vhosts.conf파일을 열어 아래 내용을 추가해요.<VirtualHost *:80> ServerName myapp.local DocumentRoot C:/xampp/htdocs/myapp/public <Directory "C:/xampp/htdocs/myapp/public"> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> </VirtualHost>httpd.conf에서Include conf/extra/httpd-vhosts.conf줄의 주석이 해제되어 있는지 먼저 확인하고, XAMPP Control Panel에서 Apache를 재시작해 주세요.IIS
IIS 관리자에서 사이트 추가 시 호스트 이름 필드에
myapp.local을 입력하면 GUI만으로 설정이 끝나요. 별도 conf 파일 편집이 필요 없어서 비교적 간단해요.⚠️ IIS 바인딩 주의 — 호스트 이름을 지정하면 IP 직접 접근이 차단돼요. 기본 사이트와 포트가 겹치면 충돌하니, 기본 사이트를 중지하거나 포트를 분리하는 게 좋아요.
6. 고급 팁 — 쿠키, CORS, 도메인 네이밍
쿠키 domain 공유
로그인 토큰을
myapp.local과api.myapp.local이 함께 공유하려면, 쿠키 발급 시domain=.myapp.local(앞에 점 포함)으로 설정해야 해요.localhost는 dot-prefix를 허용하지 않기 때문에 서브도메인 간 쿠키 공유가 구조적으로 불가능해요. 이게 가상호스트를 써야 하는 가장 대표적인 이유 중 하나예요.CORS 헤더 처리 (Nginx)
add_header Access-Control-Allow-Origin "http://myapp.local" always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; add_header Access-Control-Allow-Credentials "true" always; location / { if ($request_method = OPTIONS) { return 204; } proxy_pass http://127.0.0.1:3000; }도메인 네이밍 전략
TLD 특징 .local가장 일반적으로 쓰여요. mDNS와 충돌 가능성이 있지만 실무에서 가장 널리 사용돼요. .testIANA가 테스트용으로 예약한 TLD예요 (RFC 2606). DNS 충돌이 없어서 안전해요. .localhost최신 브라우저에서 HTTPS-only 처리를 면제받는 경우가 있어요. 🚫
.devTLD는 피하세요!
Google이 실제 TLD로 등록해 두었고, 브라우저가 HSTS preload를 적용해요.http://myapp.dev접속이 강제로 HTTPS 리다이렉트되어 로컬 HTTP 개발이 불가능해져요.7. 한 걸음 더 — mkcert로 로컬 HTTPS
Service Worker, Geolocation, WebRTC, Clipboard API 등은 HTTPS 환경에서만 동작해요.
mkcert를 쓰면 인증서 경고 없는 로컬 HTTPS를 5분 안에 구성할 수 있어요.# 설치 (macOS Homebrew) brew install mkcert nss # nss는 Firefox 지원을 위해 필요해요 mkcert -install # 로컬 CA를 시스템 키체인에 등록 # 인증서 발급 mkcert myapp.local api.myapp.local # 결과: myapp.local+1.pem, myapp.local+1-key.pem 생성Nginx에 SSL을 적용해요.
server { listen 443 ssl; server_name myapp.local; ssl_certificate /path/to/myapp.local+1.pem; ssl_certificate_key /path/to/myapp.local+1-key.pem; root /Users/you/projects/myapp/public; index index.html; } # HTTP → HTTPS 리다이렉트 server { listen 80; server_name myapp.local; return 301 https://$host$request_uri; }마무리
가상호스트는 단순한 편의 기능이 아니라, 로컬과 운영 환경 사이의 구조적 격차를 줄이는 중요한 선택이에요.
설정 순서 요약:/etc/hosts(또는 Windows hosts 파일)에 도메인-IP 매핑 추가- 웹서버(Apache 또는 Nginx)에 VirtualHost / server block 설정
- (선택)
mkcert로 로컬 HTTPS 인증서 적용
한 번만 해두면 이후 프로젝트마다 server block 하나 복사로 끝나요.
처음 설정이 조금 번거롭게 느껴질 수 있지만, 운영 환경과 동일한 구조로 개발할 수 있다는 장점은 충분히 그 수고를 보상해 준답니다. 😊
📌 공식 문서- Apache VirtualHost 공식 문서 — VirtualHost 전체 가이드
- Apache VirtualHost 설정 예제 모음 — 다양한 설정 패턴
- Apache Name-based VirtualHost — 이름 기반 가상호스트 원리
- Nginx 공식 Beginner's Guide — server block 기초
- Nginx server_names 공식 문서 — server_name 지시어 상세
📌 심화 참고
- RFC 2606 — Reserved Top Level DNS Names (IETF) — .test, .localhost 예약 TLD 공식 스펙
- mkcert GitHub (FiloSottile) — 로컬 HTTPS 인증서 도구 공식 저장소
📌 실용 가이드
- DigitalOcean — Nginx server blocks (Ubuntu) — 단계별 Nginx 설정 가이드
- Servers for Hackers — Apache VirtualHost 설정 — Apache 실무 설정 팁
- Tecmint — mkcert로 로컬 SSL 인증서 만들기 — Linux 환경 mkcert 실습 가이드
'웹 개발' 카테고리의 다른 글
pnpm + Corepack 조합, 왜 요즘 표준처럼 쓰일까? 🤔 (0) 2026.05.31 Vitest로 테스트 커버리지 확인하고 챙기기 🆙 (0) 2026.05.22 window.isSecureContext — 브라우저가 “안전한 페이지”인지 판단하는 방법 👍 (0) 2026.05.11 웹에서 “실시간”은 어떻게 구현될까? (1) 2026.04.25 Tofu, 모지바케(文字化け), 대체문자(�) — 문자 깨짐 🧐 정리하기 (1) 2026.04.19 - 쿠키 domain 분리가 안 돼요.