
0. 들어가며
SSAFY 13기 2번째 프로젝트에서 인프라 담당을 맡아 전반적인 프로젝트 환경 설정 및 Jenkins 기반 CI/CD 파이프라인을 구축했습니다. SSAFY 프로젝트 특성상 하나의 GitLab Repository에서 백엔드와 프론트를 모두 담아야 했고, EC2 서버에서 백엔드 서버와 프론트 서버를 모두 띄워야 했습니다. Jenkins와 Nginx를 EC2 서버 자체 내에 설치를 했고, 배포까지 성공적으로 이어졌습니다.
배포까지 별다른 이슈나 에러 사항은 없었는데, 프론트 화면이 나타나지 않는 현상이 발생했습니다. 개발자 모드에도 아무런 에러도 나타나지 않고, 그저 흰 화면만 나타나는 것을 확인했습니다. 당시에는 생성형 AI의 도움을 받아 프론트 폴더 내에 nginx.conf 파일을 만들어서 정적 파일을 서빙할 수 있게끔 해주면 된다고 하여 급하게 해결했지만, 찝찝함은 계속 남아있었습니다.
그래서! 이번 글에서는 1) 프론트엔드 폴더 내에 nginx.conf가 필요했던 이유와 더불어 2) nginx reverse proxy 개념까지 한번 전체 흐름을 완벽하게 이해하는 시간을 가져보도록 하겠습니다. (항상 nginx폴더 내에 default.conf를 작성은 했지만, 이 파일이 어떤 문제들을 해결해 주는지 정확하게는 이해하고 있지는.... 않았습니다....)
그럼 우선, 개념부터 잡고 가보겠습니다.
1. Proxy(프록시) 란, 무엇인가?
프록시(Proxy)를 가장 단순하게 설명하면 “대리(代理)”입니다. 클라이언트 또는 서버를 대신하여 요청을 수행해 주는 중간 서버입니다. 즉, 원래 누군가 해야 할 요청이나 응답을 프록시가 대신 처리해 준다고 생각하면 됩니다. 프록시라는 이름은 다양한 곳에서 쓰이지만, 네트워크 프록시(Network Proxy)를 중심으로 설명해보도록 하겠습니다.
아래와 같은 상황에서 프록시가 활용됩니다.
- 1. 회사 내부 PC -> 인터넷 접속 시 회사 프록시 서버를 통과
- 2. VPN 사용 시 프록시 서버를 거쳐 IP변경
이러한 Proxy 종류 중 Forward Proxy와 Reverse Proxy에 대해서 알아보도록 하겠습니다.
1. 1 포워드 프록시(Forward Proxy)

Forward Proxy는 위 그림과 같이 클라이언트 편에서 대리 역할을 하는 프록시입니다.
쉽게 말하면, “사용자 → Forward Proxy → 인터넷” 구조입니다. 즉, 사용자가 직접 백엔드 서버로 요청을 보내지 않고, 프록시 서버가 대신 요청을 보내주는 것이죠. 이렇게 인터넷을 거치기 전에 프록시 서버를 거치면 어떤 점이 좋을까요?
1) 캐싱
많은 사람들이 동일한 사이트를 방문할 때, forward proxy가 응답을 캐싱했다가 빠르게 전달해 줄 수 있습니다. 자주 요청되는 데이터를 일시적으로 저장해 두었다가 백엔드 서버까지 들리지 않고, 빠르게 데이터를 반환할 수 있어 응답 시간을 줄일 수 있고, 네트워크 부하도 줄일 수 있다는 장점이 있습니다.
2) 익명성
해커들이 VPN, Proxy를 쓰는 이유도 여기에 있습니다. 실제 클라이언트의 IP가 서버에 보이지 않고, 프록시 서버 IP만 노출됩니다.
즉, 클라이언트와 인터넷 사이에 위치하여 클라이언트 요청을 대신 처리하기 때문에 백엔드 서버 단에서는 클라이언트의 IP는 알 수 없게 되는 것이죠.
1.2 리버스 프록시(Reverse Proxy)

Forward Proxy가 클라이언트 편에서 대리 역할을 했다면, Reverse Proxy는 서버를 대신해 클라이언트 요청을 받아주는 "서버 측 대리인"입니다. 외부와 직접 맞닿는 것은 Reverse Proxy 하나뿐이며, 내부 서버(Spring, Node, Python, DB 등)는 외부에 직접적으로 노출되지 않습니다. 흐름 구조는 다음과 같이 같습니다.
“사용자 → 인터넷 -> Reverse Proxy → 여러 백엔드 서버들”
기능적으로도 Forward Proxy가 “클라이언트 보호” 역할이라면, Reverse Proxy는 “서버 보호 + 트래픽 분배” 역할을 수행합니다.
Reverse Proxy는 인터넷과 실제 서버들 사이에 위치하여 다음 기능을 수행합니다.
Reverse Proxy의 핵심 기능
1) 서버 보안 (실제 서버 IP 숨김)
Reverse Proxy는 클라이언트가 직접 내부 서버를 보지 못하게 막아주는 역할을 합니다.
- 클라이언트는 항상 Reverse Proxy 서버와만 통신합니다.
- 실제 서버(Spring Boot API, Node 서버, 별도 백엔드)의 IP는 외부에 공개되지 않습니다.
- 보안 취약성이 감소합니다.
- IP 기반 공격을 차단할 수 있습니다. (IP White List로 관리 가능)
즉, 외부와 직접 마주하는 부분이 Reverse Proxy 하나이기 때문에 보안이 크게 강화됩니다.
➡ 이 기능은 Reverse Proxy의 대표적이고 본질적인 기능입니다.
2) 정적 파일 서빙(Static File Serving) – 프론트 전용 Nginx가 하는 역할
Nginx는 정적 파일(html, css, js, 이미지)을 매우 빠르게 제공할 수 있습니다.
왜 중요할까요?
- WAS(Spring Boot)에서 굳이 static 파일을 처리할 필요가 없습니다.
- Nginx가 성능과 캐싱 측면에서 훨씬 효율적입니다.
- SPA 환경에서 try_files를 통해 라우팅 문제를 해결합니다.
즉, 웹 앱에서 가장 첫 화면을 띄우는 역할을 Nginx가 맡아주는 것이죠.
3) 캐싱 (정적 파일 및 응답 캐싱)
Reverse Proxy는 Forward Proxy처럼 캐싱 기능도 제공할 수 있습니다.
예를 들어:
- JS / CSS 정적 파일
- 이미지, 폰트
- 반복적으로 호출되는 API 응답(선택적)
위와 같은 정적 파일들을 캐싱할 때, 효과는 다음과 같습니다.
- 서버 요청 수 감소
- 응답 속도 대폭 증가
- 트래픽 비용 절감
4) 로드 밸런싱(Load Balancing)
Reverse Proxy는 한 개의 서버처럼 보이지만, 뒤에서는 여러 서버로 요청을 분산할 수 있습니다.

트래픽 폭증 시 서버 장애를 막아주는 핵심 아키텍처로, 요즘 서비스 기업에서는 필수라고 생각합니다.
서버에 들어오는 요청이 많아져서 Scale Out(수평 확장 -> 서버 개수 증가)했을 때, 로드 밸런싱을 활용해서 Scale Out을 적용한 여러 서버들로 요청을 분산시킬 수 있는 것이죠.
5) 보안 계층(Security Layer) 역할
Reverse Proxy는 보안 관련 작업을 API 서버 대신 처리합니다.
예시:
- SSL 종료(HTTPS 인증서 관리)
- DDOS 방어
- Rate Limit(악성 요청 차단)
- 접근 제어 정책
- WAF(Web Application Firewall) 역할 일부 수행 가능
“보안 정책을 모두 Nginx에서 처리하고, 내부 서버는 비즈니스 로직에만 집중한다.”
이 구조 덕분에 운영 효율이 크게 올라갑니다.
6) URL 라우팅 / 도메인 라우팅 (proxy_pass)
URL 라우팅은 Reverse Proxy의 가장 대표적인 기능 중 하나이며, 진행했던 프로젝트에서도 거의 100% 활용하는 기능입니다. 더 자세하게 설명한다면, 들어오는 요청의 URL, Header, Domain 등의 패턴을 기반으로 내부 서버로 트랙픽을 라우팅 하는 역할을 한다는 것이죠.
/api → Spring Boot 서버(8080)
/auth → 인증 서버(8081)
/static → 정적 파일 서버(frontend Nginx)
/ → index.html로 SPA 라우팅
이 구조 덕분에 백엔드와 프론트를 한 서버에서 분리해 운영할 수 있었던 것이죠.
2. Reverse Proxy 구조를 한눈에 보기

이 구조 덕분에 클라이언트는 실제 서버가 여러 대인지 알 수도 없고, 오직 Reverse Proxy만을 바라보며 원하는 응답을 얻어낼 수 있습니다.
3. 프론트엔드 폴더 내에 nginx.conf 파일이 필요했던 이유
이제 개념들을 한번 알아봤으니, 이 글의 목적 중 하나인 1) 프론트엔드 폴더 내에 nginx.conf 파일이 필요했던 이유에 대해서 설명해 보도록 하겠습니다. 우선, 당시 환경에 대해서 다시 설명드리도록 하겠습니다.
1. 하나의 Repo에서 be, fe 폴더가 공존
2. 두 서버를 docker container로 배포
3. Jenkins, Nginx 모두 컨테이너화 하지 않고, EC2 서버 자체 내 설치
당시 EC2 서버 자체 내에 설치되어 있는 nginx 관련 설정 중 "/"로 라우팅 하는 코드는 다음과 같았습니다.
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name j13a104.p.ssafy.io;
location / {
proxy_pass http://127.0.0.1:5173; # FE 컨테이너 (호스트 포트 매핑)
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;
}
}
이 코드의 흐름에 대해서 설명해 보도록 하겠습니다.
"j13a104.p.ssafy.io/*" -> http://127.0.0.1:5173 (EC2 서버 내 로컬 호스트 5173 포트) -> FE 컨테이너 (포트 포워딩)
1. SSAFY에서 제공해 준 도메인을 주소창에 입력하면 EC2 서버에 먼저 도달하게 됩니다.
2. EC2 서버 내에서 Nginx Reverse Proxy 설정에 따라서 EC2 로컬 환경 내에서 5173 포트로 연결합니다.
3. 로컬의 5173 포트와 연결되어 있는 Front Container 포트포워딩을 통해서 배포된 프론트 서버로 연결됩니다.
그럼 상황은 이해했으니, 원인 분석 및 해결 과정, Vercel 환경과의 비교까지 알아보도록 하겠습니다.
3.1 원인 분석
처음 이해가 가지 않았던 부분은 "프론트 서버까지 연결이 됐는데, 왜 흰 화면만 나오는 걸까?" 였습니다. 프론트 서버에서 알아서 프론트 팀원들이 구현해 준 UI 페이지가 나와야 한다는 생각을 가지고 있었는데, 지금 생각해보면 너무 세상을 아름답게만 바라본 것 같습니다.
EC2에 설치된 Nginx 설정에 따라서 프론트 서버 컨테이너까지 도달했다면, "npm run build"를 통해서 생성된 "dist 폴더"(Vite 기준) 내의 정적 파일까지 접근해야 합니다. 그러나, 프론트 서버 컨테이너에서 dist 폴더로 접근하는 법을 모르기 때문에 아무런 화면도 나오지 않았던 것입니다. 즉, 프론트 서버 내에서도 nginx 서버 설치가 필수적이었고, 프론트 nginx 서버가 dist 폴더로 안내해줘야 UI 페이지들을 클라이언트에서 볼 수 있게 되는 것이죠.
3.2 해결 과정
그럼 어떻게 해결하면 될까요? 해결 과정을 순차적으로 생각하면 다음과 같습니다.
1. 프론트 컨테이너 생성 시, Nginx 설치
2. "/" 요청 시, Nginx 서버가 npm run build 명령어로 생성된 dist/index.html 전달
3. index.html에서 SPA 라우터가 클라이언트에서 페이지 이동 처리
(SPA 환경에서는 index.html 파일 하나만 잘 반환하면 전체 라우터가 알아서 동작합니다.)
당시에 이 문제를 해결했던 커밋 순간을 확인해 봅시다.

Dockerfile을 살펴봅시다. 멀티 스테이지 빌드를 실행하는 것을 확인할 수 있고, 상세한 내용은 다음과 같습니다.
1. "build"라는 스테이지에서 node를 설치하고, package*.json 내용들을 설치하고, 프론트 코드들을 빌드합니다.
2. "npm run build"로 해당 스테이지에 "dist" 폴더가 생성되었을 것입니다.
3. 위 1, 2 과정의 결과물들을 하나의 이미지로 생성합니다.
4. 그다음, 새로운 "FROM절"을 통해서 nginx 이미지를 생성하는 스테이지가 시작됩니다.
5. "--from=build /app/dist ./" 명령어를 통해서 이전 build 스테이지에서 생성된 /app/dist 폴더의 내용들을 현재 루트 즉, /usr/share/nginx/html 하위로 옮깁니다.
6. "nginx.conf" 파일을 /etc/nginx/conf.d/default.conf 파일로 내용을 복사해서 /app/dist의 index.html을 서빙할 수 있도록 설정합니다.
7. 이 과정을 통해서 "/" 요청 시, 프론트 컨테이너 내부에서 index.html 파일을 클라이언트로 서빙할 수 있게 됩니다.
위 7개의 과정을 시퀀스 다이어그램으로 시각화해보면 다음과 같습니다.

처음에는 EC2 서버 내의 Nginx와 프론트 컨테이너 내부에 있는 Nginx의 역할이 헷갈렸는데, 이렇게 정리해볼 수 있을 것 같습니다.
1. EC2 서버 내의 Nginx
어느 서버로 보내줄지 즉, 도메인/URL에 따라 어느 컨테이너로 보낼지 책임집니다.
2. 프론트 컨테이너 Nginx
받은 요청에 어떤 파일을 줄지 즉, 프론트 빌드 결과물을 어떻게 서빙할지 책임집니다.
3.3 Vercel 구조와 비교
"Vercel"은 “Nginx + Reverse Proxy + CDN + 빌드 시스템 + SSL + 라우팅을 다 대신해주는 매니지드 플랫폼”입니다. 개발자가 직접 nginx.conf를 안 쓰는 대신에 다음과 같은 작업을 자동으로 해줍니다.
- 프레임워크(Next.js, React, Svelte 등)를 인식합니다.
- 빌드 + 정적 파일 서빙 + 라우팅 + 캐싱 정책을 자동으로 구성해 줍니다.
Vercel의 전체 플로우는 어떻게 흘러갈까요?
- git push 또는 Vercel에 프로젝트를 연결합니다.
- Vercel이 이를 인식하고 npm install, npm run build를 진행합니다.
- 빌드 결과물을 CDN에 올려서 전 세계 edge에서 서빙할 수 있도록 합니다.
- 프레임워크에 따라서
- SPA라면 → index.html fallback 라우팅을 자동 처리
- Next.js라면 → 페이지/라우트 별로 함수/정적 파일로 배치
- HTTPS, 압축, 캐싱 정책도 기본값으로 설정해 줍니다.
즉, 우리가 nginx.conf 파일에 직접 작성했던 이 설정들
- root /usr/share/nginx/html;
- try_files $uri /index.html;
- 정적 파일 확장자 매칭 + 캐싱
이런 것들을 Vercel이 내부 인프라에서 알아서 구성해 줍니다. 우리는 그 내부의 nginx.conf를 볼 수 없을 뿐, 역할 자체는 분명 존재하고 있을 겁니다.
Vercel 배포 방식과 직접 프론트 서버를 컨테이너화 배포하는 방식을 비교 및 정리해 보면 다음과 같습니다.
- 직접 배포하는 경우
→ 프론트도 결국 하나의 웹 서버(Nginx)를 띄우는 것입니다.
→ 즉, 그 서버의 행동 방식을 정의하는 nginx.conf가 반드시 필요합니다. - Vercel 배포하는 경우
→ “웹 서버 + Reverse Proxy + CDN” 역할을 전부 Vercel이 맡습니다.
→ 우리는 코드와 간단한 설정(vercel.json)만 제공하면 됩니다.
→ nginx.conf와 같은 파일이 보이지 않을 뿐, 실제로는 뒤에서 그와 비슷한 설정이 자동으로 존재할 것입니다.
4. Reverse Proxy 전체 흐름 및 구조 이해

전체 과정을 시퀀스 다이어그램으로 시각화해 보면 위 사진과 같습니다.
5. 정리 및 배운 점
1) 프론트엔드 폴더 내에 nginx.conf가 필요했던 이유
2) nginx reverse proxy 개념까지 한번 전체 흐름
지금까지 이 2가지 큰 주제에 대해서 알아보았습니다. 이 2가지 내용을 정확하게 이해하기 위해서 Proxy란 무엇인지, Proxy 중에서도 Forward Proxy, Reverse Proxy에 대해서 알아보았습니다. 또한, 그 각각이 어떤 기능을 가졌는지도 상세하게 알아본 후에, 본격적인 트러블 슈팅 과정으로 넘어갔습니다.
이 문제를 해결하는 과정에서 다음과 같은 부분들을 배울 수 있었습니다.
1. 프론트 서버를 EC2 내부에서 도커 컨테이너로 배포했을 때, 프론트 컨테이너 내에 Nginx 서버가 필요한 이유
2. 프론트 배포 과정에서 npm run build 이후 dist 폴더가 생성되고, SPA 라우팅이 이루어진다는 점
3. EC2 Nginx 서버의 역할, Front Nginx 서버의 역할 및 Vercel 배포 방식과의 차이점
4. Reverse Proxy가 이루어지는 전체 흐름
시간이 없어 급한 나머지 작동하게만 만들고, 넘어갔던 과정들을 다시 뜯어보는 과정에서 정말 큰 재미를 느낄 수 있었습니다.
6. Reference
- https://www.youtube.com/watch?v=YxwYhenZ3BE
- https://narup.tistory.com/238
- https://www.youtube.com/watch?v=1WfdUtMxTLE
- https://nginxstore.com/training/nginx-reverse-proxy-%EB%A1%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0/
