Nginx Proxy Manager vs 수동 설정: Docker 프록시 구축, 나에게 맞는 방법은?
Docker로 서비스 여러 개를 돌리다 보면 결국 이 문제에 부딪힌다. 포트를 일일이 외부에 열어두는 방식은 언젠가 한계가 온다. app1:3000, app2:5000, app3:8080 — 각자 포트가 다르고, HTTPS는 따로 처리해야 하고, 뭔가 정리가 안 되는 느낌이 든다.
그때 보통 두 가지 선택지가 앞에 놓인다. Nginx Proxy Manager(이하 NPM)를 쓰느냐, 직접 Nginx 설정 파일을 건드리느냐.
써봤는데, 둘 다 쓸 만하다. 다만 상황에 따라 맞는 쪽이 확연히 갈린다. "NPM이 무조건 편하다"거나 "수동이 진짜 방식이다"는 식의 편 가르기보다, 실제로 어떤 구조이고 어디서 차이가 나는지를 먼저 보는 게 낫다.

리버스 프록시가 뭔지부터 짚고 가자
용어가 낯설면 선택 자체가 힘들다. 리버스 프록시(Reverse Proxy)는 간단히 말하면 외부 요청을 받아서 적절한 내부 서버로 넘겨주는 중간 관리자다.
예를 들어 blog.example.com으로 오는 요청은 내부의 localhost:3000으로, api.example.com으로 오는 요청은 localhost:5000으로 보내준다. 외부에서는 도메인만 보이고, 내부 포트나 컨테이너 구조는 감춰진다.
Docker 환경에서 이게 중요한 이유는 세 가지다.
첫째, 포트를 외부에 다 열어두지 않아도 된다. 80번과 443번 포트만 열고, 나머지는 Docker 내부 네트워크에서만 통신한다. 보안 측면에서 훨씬 낫다.
둘째, HTTPS 처리를 한 곳에서 할 수 있다. 각 앱마다 SSL 설정을 따로 하지 않고, 리버스 프록시 레벨에서 인증서를 처리한 뒤 내부로는 HTTP로 전달한다.
셋째, 도메인 기반 라우팅이 가능하다. 서버 IP 하나에 여러 도메인을 붙이고, 어떤 도메인으로 왔느냐에 따라 다른 컨테이너로 보내는 구조를 깔끔하게 만들 수 있다.
이 역할을 담당하는 도구가 Nginx고, 이걸 직접 설정하느냐 아니면 NPM이라는 UI 래퍼를 쓰느냐가 이번 글의 핵심이다.
Nginx Proxy Manager, 실제로 써보면 어떤가
NPM은 Nginx 위에 웹 UI를 올린 Docker 이미지다. 정확히는 Nginx + Node.js + Certbot이 하나의 컨테이너로 묶여 있고, 설정을 웹 브라우저에서 클릭으로 처리할 수 있게 만들어졌다.
설치 자체는 생각보다 빠르다. docker-compose.yml 하나 작성하면 끝이다.
services:
npm:
image: jc21/nginx-proxy-manager:latest
ports:
- "80:80"
- "81:81" # 관리 UI 포트
- "443:443"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
restart: unless-stopped
컨테이너가 뜨고 나서 http://서버IP:81에 접속하면 관리 UI가 나온다. 기본 계정으로 로그인한 뒤 이메일·비밀번호를 바꾸고, Proxy Host 메뉴에서 도메인과 포워드 주소를 입력하면 된다.
blog.example.com → http://앱컨테이너:3000 이런 식으로 설정하는 데 30초도 안 걸린다. SSL 탭에서 "Request a new SSL Certificate"를 클릭하고 이메일 입력하면 Let's Encrypt 인증서 발급까지 자동으로 처리된다.
실제로 처음에 이게 되나 싶었는데, 진짜 10초 안에 HTTPS가 붙었다.
NPM이 편한 이유:
- SSL 발급과 갱신이 자동이다 — Certbot을 직접 건드릴 필요가 없다
- 설정 변경이 파일 편집 없이 UI에서 가능하다
- 접근 제어 리스트(Access List), 리디렉션, 404 처리도 UI에서 설정 가능
- 설정 백업이
./data폴더만 보관하면 된다
NPM의 아쉬운 점:
설정이 SQLite DB에 저장된다. 즉 Nginx 설정 파일을 직접 관리하는 것이 아니라, UI에서 변경하면 내부적으로 설정 파일이 생성되는 구조다. 이 때문에 세밀한 Nginx 커스터마이징이 필요할 때는 한계가 있다.
Advanced 탭에서 커스텀 Nginx 설정을 추가할 수 있긴 한데, 이게 생각보다 범위가 제한적이다. proxy_cache 세부 설정, 복잡한 업스트림 로드밸런서 구성, 특수한 헤더 처리 같은 작업은 Advanced 탭만으로 해결이 안 되는 경우가 있다.
메모리 오버헤드도 있다. 관리 UI 레이어가 추가되면서 약 100MB 수준의 RAM을 더 쓴다. 요청 처리 성능 자체는 Nginx와 동일하지만, 리소스가 빡빡한 환경에서는 이 차이가 걸릴 수 있다.
수동 Nginx 설정 방식 — 얼마나 복잡한가
수동 방식이라고 하면 보통 두 가지를 말한다. 하나는 공식 nginx Docker 이미지를 직접 쓰는 것, 다른 하나는 nginx-proxy라는 자동화 이미지를 쓰는 것이다.
여기서는 직접 nginx.conf와 conf.d/ 파일을 관리하는 방식을 기준으로 설명한다.
기본 구조는 이렇다.
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/ssl/nginx
restart: unless-stopped
그리고 conf.d/ 폴더 안에 도메인별로 설정 파일을 만든다.
# conf.d/blog.conf
server {
listen 80;
server_name blog.example.com;
location / {
proxy_pass http://blog-app:3000;
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;
}
}
서비스가 하나이고 HTTP만 쓴다면 이 정도면 된다. 문제는 HTTPS다.
Let's Encrypt 인증서를 받으려면 Certbot 컨테이너를 따로 돌리거나, Certbot을 Nginx 컨테이너 안에서 실행해야 한다. 도메인 인증(HTTP-01 challenge)을 위해 .well-known/acme-challenge/ 경로도 설정에 포함시켜야 하고, 갱신 스크립트도 별도로 만들어야 한다.
이게 익숙하면 아무것도 아니지만, 처음 해보는 사람한테는 꽤 번거롭다. 설정 파일 하나가 잘못되면 Nginx가 아예 안 뜨고, 에러 로그를 파고들어야 한다.
수동 방식의 장점은 자유도다. Nginx가 할 수 있는 것은 전부 할 수 있다. 캐시 설정, 업스트림 로드밸런싱 세부 조정, WebSocket 처리, 조건별 리디렉션, 속도 제한 등 모든 Nginx 기능을 그대로 쓸 수 있다.
또 Git으로 설정 파일을 버전 관리할 수 있다. 팀 환경이나 인프라를 코드로 관리하는 IaC(Infrastructure as Code) 흐름과 잘 맞는다. NPM처럼 UI 상태를 DB에 저장하는 방식은 Git 관리가 어렵다.
두 방식의 실질적 차이 — 상황별 비교

말로만 비교하면 와닿지 않으니, 실제 상황을 기준으로 따져보자.
상황 1: 개인 홈랩, VPS 하나에 여러 서비스
여기서는 NPM이 훨씬 낫다. 설치 10분, 설정 5분이면 여러 도메인에 HTTPS까지 붙여서 서비스를 올릴 수 있다. 서버 한 대에서 Nextcloud, Vaultwarden, Home Assistant 같은 서비스를 동시에 돌리는 사람들 사이에서 NPM이 많이 쓰이는 이유가 이거다. 설정 파일을 건드릴 필요 없이 UI에서 클릭 몇 번으로 끝난다.
상황 2: 소규모 스타트업, 팀 단위 서버 관리
여기서는 선택이 갈린다. 팀에 Nginx에 익숙한 사람이 있다면 수동 설정이 낫다. 설정 변경 이력을 Git으로 추적할 수 있고, 리뷰 프로세스를 넣을 수 있다. 반면 서버 관리 담당자가 자주 바뀌거나 인프라 전담자가 없는 팀이라면 NPM의 UI가 인수인계를 쉽게 만들어준다.
상황 3: 트래픽이 많거나 성능이 중요한 서비스
이 경우는 수동 Nginx나 Traefik 쪽을 보는 게 맞다. NPM의 요청 처리 성능 자체는 Nginx와 같지만, 세밀한 캐시 정책이나 업스트림 로드밸런싱이 필요하면 NPM의 Advanced 설정만으로는 한계가 있다.
상황 4: Kubernetes 또는 컨테이너 오케스트레이션 환경
NPM은 이 환경에 맞지 않는다. Kubernetes에서는 Ingress Controller로 Nginx Ingress나 Traefik을 쓰는 것이 표준이다. NPM은 단일 서버 Docker 환경에 최적화된 도구다.
이 두 방식의 핵심 차이를 요약하면 이렇다.
| 항목 | NPM | 수동 Nginx |
|---|---|---|
| 초기 설정 난이도 | 낮음 | 보통~높음 |
| SSL 관리 | 자동 (UI) | 수동 (Certbot) |
| 설정 버전 관리 | 어려움 | Git 사용 가능 |
| 커스터마이징 자유도 | 제한적 | 완전 자유 |
| 메모리 사용 | 약 100MB 추가 | 최소 |
| 팀 인수인계 | 용이 | Nginx 지식 필요 |
SSL 인증서 관리 차이가 생각보다 크다
프록시 도구를 고를 때 SSL 관리 방식의 차이를 많이 간과하는데, 실무에서는 이게 꽤 중요하다.
NPM 방식:
UI에서 도메인을 추가하고 SSL 탭에서 "Force SSL"과 "HTTP/2 Support"를 체크하면 끝이다. Let's Encrypt가 HTTP-01 챌린지를 자동으로 수행하고, 인증서 발급·갱신 전체가 자동화된다. 와일드카드 인증서(DNS-01 챌린지)도 지원하는데, 이때는 DNS 프로바이더 API 키 설정이 필요하다.
주의할 점 하나 — Let's Encrypt에는 rate limit이 있다. 동일 도메인으로 일주일에 발급 요청 횟수가 정해져 있어서, 설정을 잘못 건드리며 반복 요청하면 일주일간 잠길 수 있다. 테스트할 때는 Let's Encrypt 스테이징 환경을 쓰는 걸 권장한다.
수동 Nginx + Certbot 방식:
Certbot 컨테이너를 별도로 구성하고, --webroot 또는 --standalone 방식으로 인증서를 발급한다. 갱신은 크론잡이나 systemd 타이머로 자동화한다.
# certbot 수동 발급 예시
docker run --rm \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/www/certbot:/var/www/certbot \
certbot/certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
-d blog.example.com \
--email admin@example.com \
--agree-tos
이게 한 번 세팅되면 잘 돌아가는데, 처음에 경로 설정이 조금 까다롭다. Nginx 설정에서 .well-known/acme-challenge/ 경로를 Certbot이 접근 가능하도록 열어줘야 하고, 인증서 경로도 Nginx 설정에 직접 명시해야 한다. 이 과정이 처음엔 헷갈릴 수 있다.
인증서 자동 갱신이 제대로 작동하는지 처음에 확인하는 것도 중요하다. certbot renew --dry-run으로 사전 테스트가 가능하다.
2026년에는 Traefik도 고려해볼 만하다
NPM과 수동 Nginx만 선택지가 아니다. Docker 환경에서 점점 사용이 늘고 있는 Traefik도 비교 대상에 넣을 만하다.
Traefik의 핵심 특징은 Docker 컨테이너 레이블 기반 자동 라우팅이다. 컨테이너에 레이블만 붙여주면 Traefik이 자동으로 라우팅 규칙을 생성한다. 설정 파일을 따로 만들 필요가 없다.
services:
blog-app:
image: my-blog-app
labels:
- "traefik.enable=true"
- "traefik.http.routers.blog.rule=Host(`blog.example.com`)"
- "traefik.http.routers.blog.entrypoints=websecure"
- "traefik.http.routers.blog.tls.certresolver=letsencrypt"
컨테이너를 띄울 때 레이블을 붙이면 Traefik이 자동으로 도메인을 인식하고 SSL까지 처리한다. Docker Compose 파일 자체가 인프라 설명서가 된다.
반면 처음 Traefik 설정 파일(정적 설정 + 동적 설정 개념)을 이해하는 데 시간이 걸린다. NPM처럼 클릭으로 되는 것도 아니고, 수동 Nginx처럼 이미 익숙한 것도 아니라서 러닝커브가 있다.
2026년 기준으로 세 도구의 포지셔닝을 정리하면 이렇다.
- NPM: Docker 초보자, 홈랩, 빠른 셋업이 우선일 때
- 수동 Nginx: Nginx에 이미 익숙하고 커스터마이징이 필요할 때
- Traefik: Docker Compose를 코드로 관리하고 싶을 때, 마이크로서비스 환경
실전 팁 — 자주 막히는 부분 정리
NPM 관련
컨테이너 간 통신이 안 될 때: NPM과 대상 컨테이너가 같은 Docker 네트워크에 있어야 한다. docker-compose.yml에서 같은 networks: 블록을 공유하거나, docker network connect로 연결해야 한다. 포워드 주소를 localhost:3000으로 입력하면 NPM 컨테이너 내부를 가리키게 되어서 연결이 안 된다. 컨테이너 이름이나 서비스 이름(blog-app:3000)으로 입력해야 한다.
SSL 발급 실패: 도메인의 DNS A 레코드가 서버 IP를 정확히 가리키고 있는지 먼저 확인한다. NPM 컨테이너의 80번 포트가 외부에서 접근 가능한지도 확인해야 한다. 방화벽에서 80, 443 포트가 열려 있어야 HTTP-01 챌린지가 통과된다.
업데이트 후 설정이 사라졌을 때: ./data와 ./letsencrypt 볼륨을 마운트하지 않았거나 경로가 바뀐 경우다. 컨테이너 업데이트 전에 이 폴더를 백업하는 습관이 필요하다.
수동 Nginx 관련
설정 변경 후 반영이 안 될 때: docker exec nginx nginx -s reload 명령으로 재로드한다. 설정 파일에 오류가 있으면 nginx -t로 문법 검사를 먼저 한다. nginx -s reload 전에 반드시 nginx -t를 실행하는 습관을 들이는 게 좋다.
upstream 연결 거부: proxy_pass에 입력하는 주소가 Docker 내부 DNS로 해석 가능한지 확인한다. resolver 127.0.0.11 valid=30s;를 server 블록 안에 추가하면 Docker 내부 DNS를 사용하게 된다.
WebSocket 연결이 끊길 때: WebSocket을 통과시키려면 다음 헤더를 추가해야 한다.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
어떤 사람에게 맞는가
NPM이 맞는 사람:
- Docker는 쓰지만 Nginx 설정 파일을 직접 건드린 경험이 많지 않은 사람
- 개인 서버나 홈랩에서 여러 서비스를 올리는 사람
- 인프라 관리보다 서비스 개발·운영에 집중하고 싶은 1인 개발자
- 팀에서 인수인계가 자주 있는 환경
수동 Nginx가 맞는 사람:
- Nginx 설정에 익숙하고, 세밀한 컨트롤이 필요한 경우
- 인프라를 Git으로 버전 관리하는 팀 환경
- 캐시, 로드밸런싱, 복잡한 라우팅이 필요한 서비스
- NPM의 제약에 막혀본 경험이 있는 사람
솔직히 말하면 개인 프로젝트나 소규모 환경에서는 NPM을 먼저 쓰는 게 낫다. 관리 시간을 아낄 수 있고, 나중에 수동으로 옮겨야 할 상황이 생겨도 그때 이전하면 된다. 반대로 처음부터 "나는 Nginx를 제대로 배우겠다"는 목적이 있다면 수동 설정을 직접 해보는 게 더 오래 남는다.
두 방식 모두 Docker 환경에서 충분히 실용적이다. 어느 쪽이 더 낫다기보다, 지금 내 상황에서 무엇이 더 관리하기 편한가를 기준으로 고르면 된다.
Sources:
- Nginx Proxy Manager 공식 사이트
- Nginx Proxy Manager Setup Guide
- Nginx Proxy Manager vs Nginx: Which to Self-Host?
- Nginx Proxy Manager Tutorial 2026: Complete Setup Guide
- Getting Started with Nginx Proxy Manager for Home Labs
- Traefik vs Nginx Proxy Manager: Which Reverse Proxy is Best?
- Nginx Proxy Manager vs Traefik vs Caddy: 2026
- Automate SSL and Reverse Proxies with Nginx Proxy Manager
- How to Deploy NGINX Reverse Proxy on Docker - phoenixNAP
- Docker Nginx Reverse Proxy Easiest Installation Guide 2026
'개발' 카테고리의 다른 글
| Docker 컨테이너 외부 접속 안 될 때? 초보 개발자를 위한 네트워크 문제 해결 가이드 (0) | 2026.06.09 |
|---|---|
| GitHub Copilot Chat, 개발 효율 2배 높이는 첫걸음: VS Code에서 바로 시작하는 방법 (0) | 2026.06.09 |
| Python FastAPI 앱 자동 배포, GitHub Actions CI/CD 파이프라인 만들기 (0) | 2026.06.09 |
| 1인 개발자 프로젝트 아이디어, 막막할 때 시도하는 5단계 발상법 (0) | 2026.06.08 |
| Python으로 ColBERT RAG 시스템 직접 구축하기: 핵심 모듈별 구현 가이드 (0) | 2026.06.08 |