들어가며
웹 서버를 운영하다 보면 SSL 인증서 설정, 리버스 프록시 구성, 도메인 관리 등 복잡한 작업들을 마주하게 됩니다. Nginx 설정 파일을 직접 편집하고, Certbot으로 SSL 인증서를 수동으로 발급받던 시대는 이제 지났습니다. Nginx Proxy Manager는 이 모든 과정을 직관적인 웹 UI로 해결해주는 강력한 도구입니다.
이 글에서는 Nginx Proxy Manager가 무엇인지, 어떤 기능을 제공하는지, 그리고 Docker Compose를 사용해 어떻게 구성하고 설정하는지 상세히 알아보겠습니다.
Nginx Proxy Manager란?
Nginx Proxy Manager는 Nginx 웹서버를 기반으로 한 리버스 프록시 관리 도구입니다. 복잡한 Nginx 설정 파일을 직접 작성할 필요 없이, 웹 브라우저에서 클릭 몇 번으로 프록시 설정, SSL 인증서 발급, 도메인 관리를 할 수 있습니다.
핵심 특징
사용자 친화적인 웹 UI
- 복잡한 CLI 명령어 대신 직관적인 그래픽 인터페이스 제공
- 실시간 설정 변경 및 즉시 적용
- 로그 및 통계를 한눈에 확인
Let’s Encrypt 자동 통합
- 무료 SSL 인증서 자동 발급
- 90일마다 자동 갱신 (수동 작업 불필요)
- 와일드카드 인증서 지원
리버스 프록시 관리
- 여러 도메인을 하나의 서버에서 관리
- 포트 포워딩 설정
- WebSocket 지원
- HTTP/2 및 HTTP/3 지원
보안 기능
- Access List를 통한 IP 기반 접근 제어
- Basic Authentication 지원
- Force SSL (HTTP → HTTPS 자동 리다이렉트)
- HSTS (HTTP Strict Transport Security)
왜 Nginx Proxy Manager를 사용해야 할까?
1. 시간 절약
전통적인 Nginx + Certbot 설정:
# 1. Nginx 설치
sudo apt install nginx
# 2. 설정 파일 작성
sudo nano /etc/nginx/sites-available/mysite
# 3. 심볼릭 링크 생성
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
# 4. Nginx 재시작
sudo systemctl restart nginx
# 5. Certbot 설치
sudo apt install certbot python3-certbot-nginx
# 6. SSL 인증서 발급
sudo certbot --nginx -d example.com
# 7. 자동 갱신 설정
sudo crontab -e
Nginx Proxy Manager 방식:
1. 웹 UI 접속
2. "Add Proxy Host" 클릭
3. 도메인, 포워딩 정보 입력
4. SSL 체크박스 클릭
5. 완료! (소요 시간: 1분)
2. 에러 감소
설정 파일의 오타나 문법 오류로 인한 서버 다운타임을 방지할 수 있습니다. 웹 UI는 입력값을 검증하고, 잘못된 설정을 즉시 알려줍니다.
3. 중앙 집중식 관리
여러 개의 웹사이트, API 서버, 애플리케이션을 하나의 대시보드에서 관리할 수 있습니다. 각 서비스별 SSL 상태, 접속 로그, 설정 상태를 한눈에 파악할 수 있습니다.
4. Docker 친화적
Docker Compose 파일 몇 줄로 전체 인프라를 코드로 관리할 수 있습니다. 버전 관리, 배포, 롤백이 간편해집니다.
주요 기능 상세 가이드
1. Proxy Hosts (프록시 호스트)
가장 기본이 되는 기능으로, 도메인을 내부 서버나 컨테이너로 연결합니다.
사용 예시:
blog.example.com→ WordPress 컨테이너 (포트 80)api.example.com→ Node.js 앱 (포트 3000)admin.example.com→ 관리 대시보드 (포트 8080)
설정 항목:
- Domain Names: 연결할 도메인 (여러 개 가능)
- Scheme: http 또는 https
- Forward Hostname/IP: 대상 서버 주소
- Forward Port: 대상 포트 번호
- Cache Assets: 정적 파일 캐싱
- Block Common Exploits: 보안 취약점 차단
- Websockets Support: WebSocket 프로토콜 지원
2. SSL Certificates (SSL 인증서)
Let’s Encrypt를 통한 무료 SSL 인증서를 자동으로 발급하고 관리합니다.
지원 기능:
- 단일 도메인 인증서: example.com
- 멀티 도메인 인증서: example.com, www.example.com
- 와일드카드 인증서: *.example.com (DNS 인증 필요)
- 자동 갱신: 만료 30일 전 자동 갱신
SSL 옵션:
- Force SSL: HTTP 접속 시 HTTPS로 자동 리다이렉트
- HTTP/2 Support: HTTP/2 프로토콜 활성화
- HSTS Enabled: 브라우저가 항상 HTTPS로만 접속하도록 강제
- HSTS Subdomains: 서브도메인까지 HSTS 적용
3. Access Lists (접근 제어)
특정 IP 주소나 사용자만 접근할 수 있도록 제한합니다.
사용 예시:
- 관리자 페이지를 사무실 IP에서만 접근 가능하게 설정
- 개발 서버를 팀원 IP에서만 접근 허용
- Basic Authentication으로 ID/PW 인증 추가
설정 방법:
1. Access Lists → Add Access List
2. 이름 입력 (예: "Office Only")
3. Authorization 추가:
- Username/Password 또는
- IP Whitelist/Blacklist
4. Proxy Host에 적용
4. Streams (스트림)
TCP/UDP 트래픽을 포워딩합니다. HTTP가 아닌 프로토콜에 유용합니다.
사용 예시:
- MySQL 데이터베이스 (포트 3306)
- PostgreSQL (포트 5432)
- SSH (포트 22)
- 게임 서버
5. Redirection Hosts (리다이렉션)
한 도메인에서 다른 도메인으로 자동 리다이렉트합니다.
사용 예시:
old-site.com→new-site.comwww.example.com→example.com- HTTP → HTTPS 리다이렉트
Docker Compose 구성 방법
1. 기본 구성 (독립 실행)
가장 간단한 형태의 구성입니다. SQLite를 사용하여 별도의 데이터베이스 없이 실행됩니다.
version: '3.8'
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80" # HTTP
- "443:443" # HTTPS
- "81:81" # 관리자 UI
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
environment:
# SQLite 사용 (별도 DB 불필요)
DB_SQLITE_FILE: "/data/database.sqlite"
폴더 구조:
nginx-proxy-manager/
├── docker-compose.yml
├── data/ (자동 생성)
└── letsencrypt/ (자동 생성)
실행 방법:
# 1. 프로젝트 폴더 생성
mkdir nginx-proxy-manager
cd nginx-proxy-manager
# 2. docker-compose.yml 파일 생성
nano docker-compose.yml
# (위 내용 붙여넣기)
# 3. 실행
docker compose up -d
# 4. 로그 확인
docker compose logs -f
# 5. 브라우저에서 접속
# http://SERVER_IP:81
장점:
- 설정이 매우 간단
- 데이터베이스 관리 불필요
- 소규모 프로젝트에 적합
단점:
- 대량 트래픽 처리 시 성능 저하 가능
- 백업이 파일 기반
2. MySQL과 함께 구성 (권장)
프로덕션 환경에서는 SQLite보다 MySQL을 사용하는 것이 안정적입니다.
version: '3.8'
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
environment:
DB_MYSQL_HOST: mysql
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: npm
DB_MYSQL_PASSWORD: npm_secure_password
DB_MYSQL_NAME: npm
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
depends_on:
- mysql
networks:
- npm-network
mysql:
image: mysql:8.0
container_name: npm-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_secure_password
MYSQL_DATABASE: npm
MYSQL_USER: npm
MYSQL_PASSWORD: npm_secure_password
volumes:
- ./mysql:/var/lib/mysql
networks:
- npm-network
networks:
npm-network:
driver: bridge
폴더 구조:
nginx-proxy-manager/
├── docker-compose.yml
├── data/
├── letsencrypt/
└── mysql/ (MySQL 데이터)
장점:
- 안정적인 성능
- 대량 트래픽 처리 가능
- 표준 데이터베이스 백업 도구 사용 가능
주의사항:
npm_secure_password와root_secure_password를 강력한 비밀번호로 변경하세요- 프로덕션 환경에서는
.env파일로 비밀번호를 분리하세요
3. 다른 서비스와 통합 구성
실제 프로젝트에서는 여러 서비스를 함께 운영하게 됩니다. 다음은 WordPress와 함께 구성하는 예제입니다.
version: '3.8'
services:
# Nginx Proxy Manager
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
environment:
DB_MYSQL_HOST: npm-mysql
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: npm
DB_MYSQL_PASSWORD: npm123
DB_MYSQL_NAME: npm
volumes:
- ./nginx-proxy-manager/data:/data
- ./nginx-proxy-manager/letsencrypt:/etc/letsencrypt
depends_on:
- npm-mysql
networks:
- app-network
# NPM용 MySQL
npm-mysql:
image: mysql:8.0
container_name: npm-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: npm_root123
MYSQL_DATABASE: npm
MYSQL_USER: npm
MYSQL_PASSWORD: npm123
volumes:
- ./nginx-proxy-manager/mysql:/var/lib/mysql
networks:
- app-network
# WordPress
wordpress:
image: wordpress:latest
container_name: wordpress
restart: unless-stopped
expose:
- "80"
environment:
WORDPRESS_DB_HOST: wordpress-mysql
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress123
WORDPRESS_DB_NAME: wordpress
volumes:
- ./wordpress/data:/var/www/html
depends_on:
- wordpress-mysql
networks:
- app-network
# WordPress용 MySQL
wordpress-mysql:
image: mysql:8.0
container_name: wordpress-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: wp_root123
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress123
volumes:
- ./wordpress/mysql:/var/lib/mysql
networks:
- app-network
networks:
app-network:
driver: bridge
폴더 구조:
project/
├── docker-compose.yml
├── nginx-proxy-manager/
│ ├── data/
│ ├── letsencrypt/
│ └── mysql/
└── wordpress/
├── data/
└── mysql/
핵심 포인트:
- WordPress는 포트를 외부에 노출하지 않음 (
expose사용) - 모든 외부 접속은 Nginx Proxy Manager를 통해서만
- 같은
app-network에서 컨테이너 이름으로 통신 - 각 서비스마다 독립적인 MySQL 사용
통신 방식:
인터넷 → Nginx Proxy Manager (포트 80, 443)
↓
WordPress 컨테이너 (내부 포트 80)
↓
WordPress MySQL
4. 멀티 애플리케이션 구성
여러 개의 서비스를 하나의 서버에서 운영하는 실전 예제입니다.
version: '3.8'
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
environment:
DB_MYSQL_HOST: npm-mysql
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: npm
DB_MYSQL_PASSWORD: secure_password
DB_MYSQL_NAME: npm
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
depends_on:
- npm-mysql
networks:
- shared-network
npm-mysql:
image: mysql:8.0
container_name: npm-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: npm
MYSQL_USER: npm
MYSQL_PASSWORD: secure_password
volumes:
- ./npm/mysql:/var/lib/mysql
networks:
- shared-network
# 블로그 (WordPress)
blog:
image: wordpress:latest
container_name: blog
restart: unless-stopped
expose:
- "80"
environment:
WORDPRESS_DB_HOST: blog-mysql
WORDPRESS_DB_NAME: blog
WORDPRESS_DB_USER: blog
WORDPRESS_DB_PASSWORD: blog_password
volumes:
- ./blog/data:/var/www/html
networks:
- shared-network
blog-mysql:
image: mysql:8.0
container_name: blog-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: blog_root
MYSQL_DATABASE: blog
MYSQL_USER: blog
MYSQL_PASSWORD: blog_password
volumes:
- ./blog/mysql:/var/lib/mysql
networks:
- shared-network
# API 서버 (Node.js 예시)
api:
image: node:18-alpine
container_name: api
restart: unless-stopped
expose:
- "3000"
working_dir: /app
command: npm start
volumes:
- ./api:/app
networks:
- shared-network
# 관리 대시보드 (예시)
admin:
image: nginx:alpine
container_name: admin
restart: unless-stopped
expose:
- "80"
volumes:
- ./admin/html:/usr/share/nginx/html
networks:
- shared-network
networks:
shared-network:
driver: bridge
Nginx Proxy Manager 설정:
blog.mysite.com→ blog:80api.mysite.com→ api:3000admin.mysite.com→ admin:80
모든 도메인에 SSL 인증서 자동 발급!
초기 설정 가이드
1. 첫 로그인
URL: http://YOUR_SERVER_IP:81
Email: admin@example.com
Password: changeme
반드시 첫 로그인 후 비밀번호를 변경하세요!
2. 첫 번째 Proxy Host 추가
사전 준비:
- 도메인이 서버 IP를 가리키도록 DNS 설정
- 백엔드 서비스가 실행 중
단계:
- Hosts → Proxy Hosts → Add Proxy Host
- Details 탭 입력:
Domain Names: example.com, www.example.com Scheme: http Forward Hostname/IP: wordpress Forward Port: 80 Cache Assets: ✓ Block Common Exploits: ✓ Websockets Support: (필요시 체크) - SSL 탭 설정:
SSL Certificate: Request a new SSL Certificate Force SSL: ✓ HTTP/2 Support: ✓ HSTS Enabled: ✓ Email Address for Let's Encrypt: your-email@example.com I Agree to the Let's Encrypt Terms of Service: ✓ - Save 클릭
- 완료! 이제
https://example.com으로 접속 가능
3. 와일드카드 SSL 인증서 발급
모든 서브도메인에서 사용할 수 있는 와일드카드 인증서를 발급하려면 DNS 인증이 필요합니다.
지원 DNS 제공업체:
- Cloudflare
- AWS Route53
- Google Cloud DNS
- 기타 Let’s Encrypt DNS 플러그인 지원 업체
설정 방법 (Cloudflare 예시):
- SSL Certificates → Add SSL Certificate
- Domain Names:
*.example.com example.com - Use a DNS Challenge 선택
- DNS Provider: Cloudflare
- Credentials 입력:
- API Token: Cloudflare에서 발급받은 토큰
- (Zone ID는 자동으로 가져옴)
- Save
- 검증 완료 후 Proxy Host에서 사용
실전 활용 시나리오
시나리오 1: 개인 블로그 + 포트폴리오
요구사항:
blog.mysite.com→ WordPressportfolio.mysite.com→ 정적 사이트- 모두 HTTPS 적용
docker-compose.yml:
services:
nginx-proxy-manager:
# ... (기본 설정)
blog:
image: wordpress:latest
expose: ["80"]
# ... (WordPress 설정)
portfolio:
image: nginx:alpine
expose: ["80"]
volumes:
- ./portfolio:/usr/share/nginx/html
Nginx Proxy Manager 설정:
blog.mysite.com→ blog:80 (SSL 활성화)portfolio.mysite.com→ portfolio:80 (SSL 활성화)
시나리오 2: 개발/스테이징/프로덕션 환경
요구사항:
dev.api.com→ 개발 서버staging.api.com→ 스테이징 서버api.com→ 프로덕션 서버- 개발 서버는 IP 제한
docker-compose.yml:
services:
nginx-proxy-manager:
# ... (기본 설정)
api-dev:
image: my-api:dev
expose: ["3000"]
api-staging:
image: my-api:staging
expose: ["3000"]
api-prod:
image: my-api:latest
expose: ["3000"]
Nginx Proxy Manager 설정:
dev.api.com→ api-dev:3000- Access List: 사무실 IP만 허용
staging.api.com→ api-staging:3000- Access List: 팀원 IP만 허용
api.com→ api-prod:3000- 공개 접근
시나리오 3: 마이크로서비스 아키텍처
요구사항:
api.myapp.com/users→ User Serviceapi.myapp.com/orders→ Order Serviceapi.myapp.com/payments→ Payment Service
Nginx Proxy Manager 설정:
- Proxy Host: api.myapp.com
- Forward: user-service:3000
- Custom Locations 추가:
/orders → order-service:3001 /payments → payment-service:3002
이렇게 하면 URL 경로에 따라 다른 서비스로 라우팅됩니다.
모니터링과 로그
접속 로그 확인
Proxy Hosts → 설정 → View Logs
실시간 접속 로그를 확인할 수 있습니다:
2025-01-03 10:23:45 [200] GET /api/users 1.234ms
2025-01-03 10:23:46 [404] GET /favicon.ico 0.123ms
2025-01-03 10:23:47 [500] POST /api/login 2.345ms
SSL 인증서 상태
SSL Certificates 페이지
각 인증서의 만료일과 자동 갱신 상태를 한눈에 확인할 수 있습니다.
Docker 로그
# 실시간 로그
docker compose logs -f nginx-proxy-manager
# 최근 100줄
docker compose logs --tail=100 nginx-proxy-manager
# 에러만 필터링
docker compose logs nginx-proxy-manager | grep -i error
백업 및 복원
백업 방법
# 1. 컨테이너 중지
docker compose down
# 2. 데이터 폴더 백업
tar -czf npm-backup-$(date +%Y%m%d).tar.gz \
data/ letsencrypt/ mysql/
# 3. 안전한 곳에 보관
mv npm-backup-*.tar.gz /backup/
# 4. 재시작
docker compose up -d
복원 방법
# 1. 컨테이너 중지
docker compose down
# 2. 기존 데이터 삭제
rm -rf data/ letsencrypt/ mysql/
# 3. 백업 복원
tar -xzf npm-backup-20250103.tar.gz
# 4. 재시작
docker compose up -d
자동 백업 스크립트
#!/bin/bash
# /etc/cron.daily/npm-backup
BACKUP_DIR="/backup/nginx-proxy-manager"
DATE=$(date +%Y%m%d-%H%M%S)
PROJECT_DIR="/path/to/nginx-proxy-manager"
cd $PROJECT_DIR
docker compose down
tar -czf $BACKUP_DIR/npm-$DATE.tar.gz data/ letsencrypt/ mysql/
docker compose up -d
# 7일 이상 된 백업 삭제
find $BACKUP_DIR -name "npm-*.tar.gz" -mtime +7 -delete
문제 해결
“The API is not healthy”
원인: MySQL 데이터베이스가 완전히 시작되지 않음
해결:
# MySQL 로그 확인
docker compose logs mysql
# "ready for connections" 메시지 확인 후
docker compose restart nginx-proxy-manager
# 또는 1-2분 대기 후 재접속
SSL 인증서 발급 실패
원인 1: DNS가 서버를 가리키지 않음
# DNS 확인
nslookup yourdomain.com
# 결과가 서버 IP와 일치해야 함
원인 2: 포트 80이 막혀있음
# 포트 확인
sudo netstat -tlnp | grep :80
# 방화벽 확인
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
원인 3: Rate Limit 초과
Let’s Encrypt는 시간당 발급 횟수 제한이 있습니다. 1시간 후 재시도하세요.
컨테이너가 재시작을 반복함
# 로그 확인
docker compose logs --tail=100 nginx-proxy-manager
# 주요 원인:
# 1. 데이터베이스 연결 실패 → MySQL 상태 확인
# 2. 포트 충돌 → 80, 443, 81 포트 사용 여부 확인
# 3. 볼륨 권한 문제 → 폴더 권한 확인
컨테이너 간 통신 불가
# 네트워크 확인
docker network ls
docker network inspect <network-name>
# 모든 컨테이너가 같은 네트워크에 있는지 확인
# 컨테이너 이름으로 통신하는지 확인 (localhost 아님!)
성능 최적화
1. 캐싱 설정
Proxy Host 설정에서 Cache Assets 활성화
추가 커스텀 Nginx 설정:
# Custom Nginx Configuration
proxy_cache_valid 200 1h;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
2. Rate Limiting
DDoS 공격 방지를 위한 요청 제한:
# Custom Nginx Configuration
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
limit_req zone=mylimit burst=20 nodelay;
3. 압축 활성화
# Custom Nginx Configuration
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript;
보안 권장사항
1. 관리자 UI 접근 제한
방법 1: 방화벽으로 포트 81 차단
# 외부에서 포트 81 접근 차단
sudo ufw deny 81/tcp
# SSH 터널로만 접속
ssh -L 8081:localhost:81 user@server-ip
# 브라우저: http://localhost:8081
방법 2: Access List로 IP 제한
관리자 UI 자체에 Access List 적용하여 특정 IP에서만 접속 가능하게 설정
2. 강력한 비밀번호 사용
docker-compose.yml의 모든 비밀번호를 강력하게 설정:
environment:
MYSQL_ROOT_PASSWORD: 'C0mpl3x!P@ssw0rd#2025'
MYSQL_PASSWORD: 'An0th3r$tr0ng!P@ss'
3. 환경 변수 분리
비밀번호를 .env 파일로 분리:
.env:
NPM_MYSQL_PASSWORD=secure_password_here
NPM_ROOT_PASSWORD=root_password_here
docker-compose.yml:
environment:
MYSQL_PASSWORD: ${NPM_MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${NPM_ROOT_PASSWORD}
.gitignore:
.env
data/
mysql/
letsencrypt/
4. 정기 업데이트
# 이미지 업데이트
docker compose pull
docker compose up -d
# 로그 확인
docker compose logs -f
마치며
Nginx Proxy Manager는 복잡한 웹 서버 관리를 놀라울 정도로 간단하게 만들어줍니다. CLI에 익숙하지 않은 개발자나, 빠른 프로토타이핑이 필요한 경우, 또는 여러 서비스를 효율적으로 관리하고 싶은 경우 완벽한 솔루션입니다.
Docker Compose를 사용하면 몇 분 안에 구성할 수 있고, Let’s Encrypt 통합으로 SSL 인증서 관리의 부담도 사라집니다. 직관적인 웹 UI는 팀원들과의 협업을 더욱 쉽게 만들어줍니다.
이제 Nginx 설정 파일과 씨름하던 시간을 실제 개발에 투자하세요. Nginx Proxy Manager가 나머지는 알아서 처리해줄 것입니다.