본문 바로가기
클라우드 아키텍처·전략

Azure에서 FE/BE VM 분리했을 때 외부 접속이 안 되는 이유와 Reverse Proxy 설정법

by joe2026 2026. 3. 2.

 Azure에서 VM을 2대로 구성했는데 내부 IP로 외부 접속이 되지 않는 문제를 해결한 과정입니다. 공인 IP와 사설 IP 차이, IIS Reverse Proxy 설정, ERR_CONNECTION_TIMED_OUT 원인까지 정리했습니다.


1. 실습 목적 — 왜 이 구성을 시도했는가

단순히 챗봇 하나를 만드는 게 목적이었다면 VM 한 대로도 충분했을 것이다. 하지만 이번 실습에서는 실제 서비스와 유사한 3계층 아키텍처(Frontend / Backend / API) 를 직접 경험하는 것이 목표였다.

구체적으로는 다음 네 가지를 확인하고 싶었다.

  • Azure VM 2대를 역할별로 분리해 서비스 구조를 구성할 수 있는가
  • 공인 IP와 사설 IP(Private IP)의 실질적인 차이를 체감할 수 있는가
  • 내부망 서버를 외부에서 안전하게 사용하는 방법을 직접 적용할 수 있는가
  • Reverse Proxy 구조를 실습 수준이 아닌 실제 흐름으로 이해할 수 있는가

Azure OpenAI API를 연동한 간단한 질의응답 웹 서비스를 구성했고, 프런트엔드(FE)는 Windows VM + IIS, 백엔드(BE)는 Linux VM + Node.js로 나눴다. 그런데 외부 브라우저에서 질문을 보내는 순간 다음 오류가 출력됐다.

ERR_CONNECTION_TIMED_OUT

처음에는 방화벽 설정이나 포트 오류라고 생각했다. 하지만 실제 원인은 네트워크 구조 자체에 대한 이해 부족이었다.


2. 설계 핵심 개념 — 구조를 이해해야 오류를 읽을 수 있다

리버스프록시구조 설명

공인 IP vs 사설 IP — 가장 먼저 짚어야 할 차이

BE 서버의 IP 주소는 172.16.x.x 형태였다. 이는 Azure VNet(Virtual Network) 내부에서만 유효한 사설 IP(Private IP) 다. 인터넷에 직접 노출되지 않으며, 같은 VNet에 속한 리소스끼리만 통신할 수 있다.

반면 FE 서버에는 공인 IP(Public IP) 가 부여되어 있어 외부 브라우저가 직접 접근할 수 있다.

문제는 여기서 발생했다. 프런트엔드 JavaScript 코드에 BE의 사설 IP를 직접 적어두었던 것이다.

// 잘못된 방식 — 외부 브라우저에서는 172.16.x.x에 절대 도달할 수 없다
fetch("http://172.16.1.4:3000/api/ask")

외부 사용자의 브라우저는 Azure VNet 내부에 존재하지 않기 때문에, 이 주소로 향하는 요청은 응답 자체를 받지 못하고 타임아웃된다. 포트 문제도, 코드 문제도 아니었다.

Reverse Proxy가 필요한 이유

해결책은 구조를 바꾸는 것이었다. 외부 사용자가 BE 서버에 직접 접근하는 대신, FE 서버를 경유하도록 설계하는 방식이다.

외부 사용자
  → FE VM (공인 IP, IIS)        ← 외부에서 접근 가능한 유일한 진입점
    → BE VM (사설 IP, Node.js)  ← 내부망에서만 존재, 외부에 직접 노출되지 않음
      → Azure OpenAI API        ← API Key를 서버에서만 관리

이 구조에서 FE 서버의 IIS는 단순히 정적 파일을 제공하는 것 이상의 역할을 한다. /api로 시작하는 요청을 받아 내부망의 BE 서버로 중계(Proxy) 하는 역할을 맡는다. 이것이 Reverse Proxy다.

OpenAI API Key는 반드시 서버에서만 관리해야 한다

초보 단계에서 자주 저지르는 실수가 있다. 프런트엔드 JavaScript에서 직접 OpenAI API를 호출하는 방식이다.

// 절대 하면 안 되는 방식 — API Key가 브라우저 개발자 도구에 그대로 노출된다
const response = await fetch("https://api.openai.com/v1/chat/completions", {
  headers: { "Authorization": "Bearer sk-..." }
});

API Key가 브라우저에 노출되면 누구든 해당 키를 탈취해 무단으로 사용할 수 있다. 올바른 구조는 다음과 같다.

  • 브라우저는 BE 서버에 요청만 보낸다
  • BE 서버가 API Key를 .env 환경변수로 보관하고, OpenAI API와 직접 통신한다
  • API Key는 서버 외부로 절대 노출되지 않는다

3. 실습 과정 설명 — 단계별로 구성한 방법

① VM 구성

역할 OS 주요 소프트웨어 IP 유형

FE VM Windows Server IIS 공인 IP
BE VM Ubuntu Linux Node.js 사설 IP (VNet 내부)

두 VM은 동일한 Azure VNet에 속해 있어 내부 통신이 가능하다.

② BE 서버 설정 (Node.js)

// server.js 핵심 구성
const express = require('express');
const app = express();
app.use(express.json());

app.post('/api/ask', async (req, res) => {
  // Azure OpenAI 호출 로직
  // API Key는 process.env.AZURE_OPENAI_KEY 로 참조
});

// 0.0.0.0으로 바인딩 — VNet 내부 요청을 모두 수신
app.listen(3000, '0.0.0.0');

0.0.0.0으로 바인딩하지 않으면 127.0.0.1(localhost)에서 온 요청만 처리되어 FE에서 보낸 내부망 요청도 거부될 수 있다.

③ IIS Reverse Proxy 설정

IIS에서 Reverse Proxy를 구성하려면 두 가지 확장 모듈이 필요하다.

  • URL Rewrite: 요청 URL 패턴에 따라 라우팅 규칙 적용
  • ARR (Application Request Routing): 실제 Proxy 통신 처리

설치 후 web.config에 다음 규칙을 추가했다.

<rewrite>
  <rules>
    <rule name="Proxy to Backend" stopProcessing="true">
      <match url="^api/(.*)" />
      <action type="Rewrite" url="http://172.16.1.4:3000/api/{R:1}" />
    </rule>
  </rules>
</rewrite>

/api로 시작하는 요청은 모두 내부 BE 서버로 전달되고, 나머지 요청은 정적 파일(HTML, CSS, JS)로 처리된다.

④ 프런트엔드 코드 수정

// 수정 전 — 사설 IP 직접 호출, 외부에서 동작 불가
fetch("http://172.16.1.4:3000/api/ask", { ... })

// 수정 후 — 상대 경로 사용, FE 서버가 Proxy 처리
fetch("/api/ask", { ... })

이 한 줄 변경이 핵심이었다. 상대 경로를 사용하면 요청이 FE 서버(공인 IP)로 향하고, IIS가 이를 받아 내부 BE로 중계한다.


4. 실습 중 발생한 문제와 해결 (Q&A)

Q1. 172.16.x.x로 직접 접속하면 왜 오류가 나는가?

사설 IP는 Azure VNet 내부에서만 유효하다. 외부 브라우저는 이 주소 대역에 라우팅 경로가 없기 때문에 요청이 전달 자체가 되지 않는다. 응답이 늦는 것이 아니라, 요청이 목적지에 도달하지 못하고 버려진다. 그 결과가 ERR_CONNECTION_TIMED_OUT이다.

Q2. BE VM에 공인 IP를 붙이면 안 되는가?

기술적으로는 가능하다. 하지만 BE 서버를 직접 인터넷에 노출하면 공격 표면이 넓어지고, API Key와 같은 민감한 정보가 포함된 서버를 방어하는 부담이 커진다. Reverse Proxy를 통해 FE VM 하나만 외부에 노출하는 구조가 보안상 더 안전하다.

Q3. ARR 활성화 후에도 502 오류가 발생한다면?

BE 서버가 실제로 실행 중인지 먼저 확인한다. pm2 list 또는 node server.js 상태를 점검하고, FE VM에서 BE VM으로 curl http://172.16.1.4:3000/api/ask를 직접 실행해 내부 통신이 가능한지 확인한다. Azure NSG(네트워크 보안 그룹)에서 내부 통신 포트가 허용되어 있는지도 점검해야 한다.

Q4. .env 파일을 서버에 올려도 안전한가?

.env 파일 자체는 Git 등 버전 관리 시스템에 절대 포함하지 않아야 한다. VM에 직접 파일을 생성하거나, Azure Key Vault와 같은 비밀 관리 서비스를 사용하는 것이 권장된다. 이번 실습에서는 VM에 직접 파일을 작성하는 방식을 택했다.


5. 최종 정리 — 연결 오류 하나가 알려준 것들

ERR_CONNECTION_TIMED_OUT 하나를 해결하는 과정에서 다음 개념들이 자연스럽게 연결됐다.

공인 IP와 사설 IP의 차이는 단순히 주소 체계의 문제가 아니라, 네트워크 접근 범위와 보안 설계의 기초다. 사설 IP를 외부에서 직접 호출하려는 시도 자체가 구조에 대한 이해 없이 코드만 작성했을 때 발생하는 실수다.

Reverse Proxy는 단순한 요청 전달 이상의 의미를 가진다. 내부 서버를 숨기고, 외부 접점을 단일화하며, 인증이나 로드 밸런싱 같은 공통 처리를 한 곳에서 담당할 수 있게 해준다. 실제 서비스에서 Nginx나 IIS ARR이 이 역할을 맡는 이유가 여기에 있다.

API Key 관리 원칙은 클라우드 서비스를 다루는 순간부터 선택이 아닌 필수가 된다. 프런트엔드에 키를 노출하는 것은 자물쇠 옆에 열쇠를 붙여두는 것과 같다.

최종적으로 구성된 흐름은 다음과 같다.

외부 사용자 (브라우저)
  → FE VM (IIS, 공인 IP) — 유일한 외부 진입점
    → BE VM (Node.js, 사설 IP) — API Key 보관, 비즈니스 로직
      → Azure OpenAI API — 실제 AI 응답 생성

단순해 보이는 이 흐름 안에, 클라우드 기반 웹 서비스가 동작하는 방식의 기본 원리가 담겨 있다. 오류를 만나고 원인을 추적하는 과정이 결국 가장 빠른 학습이었다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 클라우드학습기