3학년 2학기 웹서비스설계및실습 수업에서 Nginx Proxy Manager를 다뤘다.
사용해보니 간단한 리버스프록시 환경을 구축하기에는 NginX의 sites-available, sites-enabled 내 구성 파일을 직접 설정하는 것보다 편리하고, 웹 대시보드 형태로 접속해 구성을 만질 수 있는 등 배포 편의성이 느껴지는 점이 많았기에 EC2 환경에서 다시 한 번 실습하면서 정리를 해보려고 한다.
NginX를 사용해 웹 서비스를 배포하기 위해서는 위에서 언급했듯 /etc/nginx/sites-available 경로에 구성 파일을 작성하고, /etc/nginx/sites-enabled 경로에 심볼릭 링크를 걸어준 이후 NginX 프로세스를 재시작해 변경 사항을 적용하기 위해 sudo nginx -s reload 명령어도 입력해줘야 한다.
나는 홈 서버에 여러 토이프로젝트를 배포하는 과정에서 익숙해지긴 했으나, 귀찮기도 하고 생각보다 절차가 많기 때문에 실수하기 좋다. 그래서 Notion에 메모해둔 내용을 보며 배포해왔다.
실제 배포중인 웹서비스의 구성 파일을 하나 까보며 살펴보자.
server {
server_name imagetale.mooo.com;
root /var/www/imagetale/frontend/dist;
index index.html;
location / {
try_files $uri /index.html;
}
location /api/ {
rewrite ^/api(.*) $1 break;
proxy_pass http://127.0.0.1:7001;
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;
}
}
위 구성 파일은 ImageTale 프로젝트를 배포하기 위한 블록의 일부분이다.
NginX 구성 파일은 directive 방식으로 작성되며
두 가지 방식으로 작성된다.
하나의 도메인에 대한 구성은 하나의 server 블록으로 묶으며, server_name에 도메인을 입력한다.
해당 도메인으로 접속하게 되면 해당 server 블록에 명시된대로 서빙하도록 하는 것이다.
root는 서빙할 파일, 프론트 배포라면 index.html이 위치한 경로를 설정하면 된다.
참고로 NginX 계정이 접근 가능한 폴더는 /var/www이므로, 이 하위 경로에 배포 파일을 위치시키면 된다.
NginX 계정에 더 높은 권한을 부여하면 홈 디렉토리의 파일도 서빙할 수 있지만, 이는 보안상 권장되지 않는다.
NginX 계정이 탈취될 경우 전체 서버 권한이 노출될 위험이 있기 때문이다.
index에는 어떤 파일을 찾을지 명시하게 된다. index.html을 찾으라고 지정해주자.
location에는 도메인 뒤에 붙는 경로에 따라 어떤 동작을 수행할 지 결정할 수 있다.
위 예시에서 location / { }은 도메인 루트 /에 접속했을 때, location /api/ { }는 도메인 뒤에 /api를 붙였을 때 NginX가 어떻게 서빙해야 할지 명시할 수 있다.
try_files: 정적 파일 서빙 (주로 FE)proxy_pass: 서버 내부의 다른 포트로 연결을 이어줌 (주로 BE)전부 다 언급하기에는 길기도 하고, 나도 모두 이해하고 쓰기보다 이곳 저곳에서 찾아보며 작성했다보니, 핵심적인 서버 블록만 다뤄보았다.
그리고 이 정도만 이해해도 토이프로젝트 FE, BE 배포에는 충분했다.
NginX의 캐시와 같은 기능들은 다루지도 않았는데 분량이 꽤 되는 것을 볼 수 있다. 그렇다면 Nginx Proxy Manager를 사용하면 얼마나 편해질까?
우선 충돌을 방지하기 위해 새로운 EC2 인스턴스를 만들고, 여기서 작업해보도록 하겠다. 참고로 Nginx Proxy Manager의 어드민 페이지는 81번 포트를 사용하므로, AWS의 보안 그룹에서 81번 포트를 열어줘야 한다.

보안 그룹의 인바운드 규칙 편집을 클릭하고

규칙 추가 -> 사용자 지정 TCP, 81, 0.0.0.0/0 형태로 작성한 뒤 저장해 EC2 인스턴스의 81번 포트를 개방한다.
이제 본격적으로 EC2와 Nginx Proxy Manager를 활용해 두 개의 웹앱 배포를 실습해보자.
Nginx Proxy Manager는 Docker compose 형태로 제공되므로 먼저 Docker를 설치해야 한다.
공식문서를 따라가면 되지만 간단히 요약하자면, 아래 명령어를 순차적으로 실행해주면 된다.
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run hello-world
마지막까지 실행했을 때 Hello from Docker! ..가 출력되면 정상적으로 설치된 것이다.
Nginx Proxy Manager 공식 문서를 살펴보면, docker-compose.yml 파일이 나와있다. 이걸 작성하고 Docker compose로 실행시키기만 하면 끝난다.
홈 디렉토리에 nginx라는 폴더를 만들고, 그 내부에 docker-compose.yml 파일을 생성한 뒤, 아래 내용을 붙여넣자.
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
# These ports are in format <host-port>:<container-port>
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
# Add any other Stream port you want to expose
# - '21:21' # FTP
# Uncomment the next line if you uncomment anything in the section
# environment:
# Uncomment this if you want to change the location of
# the SQLite DB file within the container
# DB_SQLITE_FILE: "/data/database.sqlite"
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
docker-compose.yml을 읽어보면, Nginx Proxy Manager에 대해 추가로 이해할 수 있는 점들이 있다.
80,81,443포트를 점유한다. 만약 다른 웹서버가 포트를 점유하고 있는 경우 충돌이 발생하므로, 다른 웹서버를 내린 뒤 Nginx Proxy Manager 내에서 프록시를 수행하도록 변경해줘야 한다.
restart옵션이unless-stopped로 되어있다. 따라서sudo docker compose down을 수행하기 전까지는 무슨 일이 있어도 재실행된다. 재부팅 또는 컨테이너 사망 시 자동으로 컨테이너를 다시 띄운다.
data와letsencrypt를 컨테이너 밖에 볼륨 마운트를 수행한다. 관리자 계정 정보와 프록시 구성값, SSL/TLS 인증서 등이 유지될 수 있는 이유다.
그후, nginx 폴더에서 sudo docker compose up -d 명령어를 통해 Nginx Proxy Manager 컨테이너를 실행하자.

이렇게 뜨면 Nginx Proxy Manager가 정상적으로 설치 및 실행된 것이다.
제대로 설치되었는지 확인하고 싶다면, http://외부IP로 접속했을 때 아래처럼 나오는지 보면 된다.

http://외부IP:81로 접속해보면, 아래처럼 로그인 창이 나온다.

초기 Email / Password를 입력해 로그인하자.
Email: admin@example.com
Password: changeme
![]() | ![]() |
|---|
로그인한 뒤 이름, Email, Password 등을 입력해 계정을 변경한다. 외부IP만 알면 누구나 접속할 수 있으므로 반드시 변경해야 한다.

이제 어드민 페이지에서 리버스프록시 구성을 할 수 있다. 주로 Hosts, SSL Certificates 메뉴를 사용하게 된다.
실제로 해보기 전, 먼저 리버스프록시 개념을 간단하게 정리하고 넘어가자.
프록시 서버는 "대리인"과 유사하다.
포워드 프록시: 클라이언트가 프록시 서버에 접근해 요청하면, 프록시 서버가 인터넷에 접속해 요청을 수행하고 클라이언트에게 전달하는 방식
리버스 프록시: 클라이언트가 인터넷에 접속해 프록시 서버에 접근해 요청하면 프록시 서버가 웹 애플리케이션 서버에 접근해 요청을 전달하는 방식
라고 말하니 비슷해보이지만, "인터넷"을 기준으로 프록시 서버의 위치에 따라 결정되는 것이다.

위 이미지를 보면 더 이해하기 쉽다.
포워드 프록시는 "인트라넷" 과 같은 구조를 떠올리면 된다.
만약 접속이 차단된 웹페이지를 클라이언트가 접속하면, 포워드 프록시가 이를 확인하고 접속을 차단할 수 있다.
리버스 프록시는 "로드 밸런싱"을 생각하면 된다.
로드 밸런싱은 여러 클라이언트가 서비스에 접속하는 경우, 리버스 프록시가 서버1, 2, 3에 분산시켜 요청을 전달해 서버의 부하를 감소시키는 기술이다.
리버스 프록시는 로드 밸런싱 이외에도 캐싱, 여러 서비스를 하나의 서버에서 배포하는 역할 등을 수행할 수 있다.
리버스 프록시 서버가 도메인1로 요청을 받으면, 서버에서 1번 서비스를 배포하는 웹 애플리케이션 서버로 요청을 전달하고, 도메인2로 요청을 받으면, 2번 서비스를 배포하는 웹 애플리케이션 서버로 요청을 전달하는 식으로 구성할 수 있다.
이제 Nginx Proxy Manager로 직접 하나의 서버에서 두 개의 도메인에 두 개의 웹앱을 배포해보자.
배포를 위해 두 개의 도메인이 필요하다. Duck DNS나 FreeDNS 등의 무료 도메인 서비스를 활용하면 된다.
참고로 나는 홈 서버에서는 FreeDNS를 사용 중이고, 이번에는 Duck DNS를 사용할 예정이다.

testweb-1.duckdns.org와 testweb-2.duckdns.org 도메인을 EC2 외부 IP와 연결했다.

Nginx Proxy Manager에서 각 도메인에 연결될 웹 애플리케이션 서버의 포트를 연결해주면 된다.
어드민 페이지에서 Hosts -> Proxy Hosts를 클릭한 후 Add Proxy Host를 클릭하자.

Domain Names에 아까 만든 도메인 이름을 넣어주고, Forward Hostname/IP에는 내부 IP를 작성해준 뒤, Forward Port에는 웹을 배포하고 있는 포트를 작성해준다.
Cache Assets, Block Common Exploits, Websockets Support 등은 필요 시 체크하면 된다.

만약 Forward Hostname/IP에 외부 IP를 넣게 되면 서버 내부로 들어와놓고, 다시 서버 밖으로 나갔다가 들어오게 된다.
그리고 아마 다시 들어오는 과정에서 8000번 포트로 접속을 시도하므로 방화벽에 막혀 504 Gateway Timeout이 발생할 것이다.
여러 물리 서버를 두고 로드 밸런스 환경을 구성한다면 프록시 시킬 물리 서버의 외부 IP 주소를 넣는 것이 맞겠지만, 하나의 서버에서 리버스 프록시를 활용해 여러 웹을 배포하려는 경우 적합하지 않으며 포트포워딩도 뚫어줘야 한다.
이러한 이유로 내부 IP를 넣어줘야만 한다.
외부IP, 내부IP가 헷갈린다면, 네트워크의
NAT(Network Address Translation)에 대해 알아보면 좋다.간단하게 말하면 인터넷 선 하나로 연결된 공유기가 여러 기기들에게 인터넷을 연결해주기 위해 하나의 외부 IP를 여러 내부 IP로 변환해 각 기기에 배정해주는데, 이걸
NAT이라고 한다.즉,
NAT을 통해 1번 집의 192.168.0.1과 2번 집의 192.168.0.1이 다를 수 있는 것이다.만약 공유기를 쓰고 있다면, 공유기 어드민 페이지에 접속해보면 좋다. 본인 기기는 아마 192로 시작하는 IP를 배정받았을 것이고, 페이지에 나와있는 외부 IP는 192로 시작하지 않는 IP일 것이다.

Custom locations에 들어가서 Add location을 클릭하면 location과 Forward Hostname/IP, Forward Port를 입력할 수 있다. 이를 통해 도메인의 하위 경로별로 리버스 프록시를 설정할 수도 있다.
SSL은 HTTPS를 위한 SSL/TLS 인증서를 연결하는 부분이며, 잠시 뒤에 볼 예정이다.
Advanced에서는 NginX 서버 블록에 작성하는 구성 코드를 추가할 수 있다. Nginx Proxy Manager가 지원하지 않는 구성을 추가해야 할 때 활용하면 된다.
설정을 마쳤다면, Save 버튼을 누르면 된다.

위와 같이 testweb-1은 8000번 포트와, testweb-2는 9000번 포트와 연결했다.
이렇게 프록시를 연결하는 법을 살펴보았다.
NginX만 사용하는 것보다 배포 과정이 훨씬 간편하다고 생각한다.
이 과정은 뭘 써도 상관없다. 나는 두 개의 FE 프로젝트를 PM2와 serve를 활용해 배포해볼 예정이지만, 어떤 방식으로 배포하든 Nginx Proxy Manager의 도메인과 포트만 맞춰주면 된다.
새로 EC2 인스턴스를 생성했으므로, nodeJS, PM2, serve를 설치해야 한다.
우분투 서버에서 nodeJS를 설치하기 위해 공식문서를 참고해 nvm으로 설치해보겠다.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 22
node -v
npm -v
이 명령어를 순서대로 실행하면 nodeJS와 npm을 설치할 수 있다.
추가로 PM2와 serve를 설치해보자.
npm install -g pm2
npm install -g serve
배포할 FE 프로젝트를 빌드한다. 이 과정은 언급하지 않고, 빌드된 이후 dist(build) 폴더가 존재하는 경로에 위치했다고 가정한다.
serve로 React 웹앱을 배포하면서 PM2로 무중단 서비스를 제공하기 위한 명령어는 아래와 같다.
pm2 serve build --port 8000 --spa
# 또는
pm2 serve dist --port 9000 --spa
serve 뒤에 index.html이 존재하는 경로를 입력하고, --port 뒤에 배포할 포트를 입력하고, --spa로 Single Page Application으로 배포하도록 해주면 된다.
이렇게 배포하고 http://testweb-1.duckdns.org과 http://testweb-2.duckdns.org에 각각 접속해보면 실제로 배포가 잘 된 것을 확인할 수 있다.


하지만, HTTP는 네트워크상에서 데이터가 오갈 때 해커가 중간에서 가로채면 내용을 전부 읽을 수 있다는 보안상 문제점이 존재한다. 그래서 거의 모든 웹페이지들은 SSL/TLS 인증서를 활용한 HTTPS를 통해 보안을 향상시킨다.
먼저 HTTPS가 어떻게 보안을 향상시키는지 정리해보자.
HTTPS는 HyperText Transfer Protocol Secure의 약자로 HTTP에 보안 기능을 추가한 것이다.
간단하게 말하면 Application 계층과 Transport 계층 사이에 존재하는 TLS(Transport Layer Security)에서 데이터를 알아보지 못 하게 암호화해 데이터를 가로채도 읽어볼 수 없도록 하는 기술이다.
이를 위해 클라이언트가 서버와 통신하기 전 Handshake를 수행한다. Handshake 과정에서는 대칭키와 공개키 방식을 활용하게 되고, Handshake가 끝나면 만들어지는 세션키를 사용해 이후 통신에서 암호화를 수행하게 된다.
그림으로 쉽게 보는 HTTPS, SSL, TLS에 개념과 동작 과정 정리가 잘 되어있으므로 한 번 정독해보면 좋을 것 같다.
HTTPS의 Handshake 과정은 TCP의 3-Way Handshake보다 더 복잡하지만, HTTP로 배포한 서비스를 HTTPS로 전환하는 것은 크게 어렵지 않다. NginX라면 Certbot을 활용하면 되고, Nginx Proxy Manager의 경우 어드민 대시보드에서 더 편리하게 적용할 수 있다.
이제 Nginx Proxy Manager로 HTTPS를 적용해보자.

Duck DNS의 token을 활용해 SSL/TLS 인증서를 발급할 것이다.

어드민 페이지에서 SSL Certificates -> Add SSL Certificate로 이동한다.

Domain Names에 인증서를 발급할 도메인을 입력한다. 그 후 Use a DNS Challenge를 체크한 뒤 DuckDNS를 선택하고, your-duckdns-token을 실제 값으로 교체한다.
마지막으로 Let's Encrypt 약관 동의 후 Save를 누르면 잠시 뒤 인증서가 발급된다.


2개의 도메인에 대해 모두 인증서를 발급했으니 이제 Hosts -> Proxy Hosts로 이동해 인증서를 적용해주면 된다.

현재는 HTTP only인 것을 알 수 있다. HTTPS를 적용하기 위해 우측의 점 세개를 누르고 Edit을 눌러 구성을 수정할 수 있다.

SSL 설정으로 이동해 이전에 발급받은 인증서를 선택하고, Force SSL과 HTTP/2 Support를 체크해준 뒤 Save 하면 끝이다.
인증서를 선택할 때 당연히 프록시 도메인과 인증서 도메인이 같아야 한다.
Force SSL을 체크하면, HTTP로 연결 시 자동으로 HTTPS로 리다이렉션 시키므로 체크해주는게 좋다.

인증서 적용을 마치면 HTTP only에서 Let's Encrypt로 변경된 것을 확인할 수 있으며

접속 또한 HTTPS로 잘 되는 것을 볼 수 있다.
Nginx Proxy Manager를 통한 배포를 처음부터 끝까지 정리하면서 다시 한 번 공부하는 기회로 삼기 위해 2주 전부터 계획하고 있던 포스팅이 끝났다.
학교에서 제공받은 AWS Learner Lab의 남은 자원을 이런 곳에 쓰게 될 줄은 몰랐는데.. ㅋㅋ
이번 학기에 웹 관련 전공 수업을 2개 들으면서 인상깊었던 주제들을 복습하면서 하나씩 정리해두려는 계획을 가지고 있다. 12월이 지나기 전에 계획해둔 주제들을 모두 포스팅한 뒤 24년을 회고하고, 본격적으로 취업 준비에 들어가게 될 것 같다.
작성하다 보니 호흡이 꽤 길어졌다. 글 쓰는걸 좋아하긴 하지만 문장을 깔끔하고 가독성 좋게 쓰는 것은 참 어려운 것 같다. 많이 연습해야 할 것 같다.
현재 홈 서버에서 배포되는 프로젝트가 좀 많아서 당장은 못 건들지만, 기회가 된다면 싹 갈아엎고 Nginx Proxy Manager로 옮기고 싶다는 생각이 들었다. Nginx Proxy Manager와 Docker compose를 잘 사용하면 토이 프로젝트 배포 과정이 매우 짧아져 편리할 것 같기 때문이다.
Nginx Proxy Manager를 처음 사용해보려는 분이나, 미래의 내가 도움받길 바라며 글을 마무리해본다.