VPS 한 대에 서비스를 여러 개 띄웠는데, 접속할 때마다 포트 번호를 외워야 한다면? app.example.com:3000, api.example.com:8080, admin.example.com:9000… 사용자는 물론이고 개발자 본인도 헷갈립니다. Nginx reverse proxy 하나면 이걸 80번 포트 하나로 깔끔하게 묶을 수 있습니다.
문제: 포트 번호를 외우는 것은 비밀번호를 외우는 것과 같다
VPS에 웹 서비스를 처음 배포할 때, 보통은 이렇게 합니다. 서비스 A는 3000번 포트에서 돌리고, 서비스 B는 8080번에서 돌립니다. 그리고 방화벽에서 각 포트를 열어줍니다.
# 서비스마다 포트가 다름
app.example.com:3000 # 프론트엔드
api.example.com:8080 # 백엔드 API
admin.example.com:9000 # 관리자 페이지
이 방식의 문제는 명확합니다. 첫째, 사용자가 포트 번호를 URL에 입력해야 합니다. 둘째, HTTPS 인증서를 서비스마다 따로 발급받아야 합니다. 셋째, 방화벽 규칙이 포트마다 따로 생겨서 관리가 복잡해집니다. 서비스가 5개, 10개로 늘어나면 포트 번호 노트를 따로 들고 다녀야 할 지경입니다.
더 큰 문제는 보안입니다. 8080, 3000번 같은 비표준 포트는 기업 방화벽에서 차단하는 경우가 흔합니다. 회사 네트워크나 공공 와이파이에서 접속이 안 되는 것은 이 때문입니다. 반면 80번(HTTP)과 443번(HTTPS) 포트는 거의 모든 네트워크에서 열려 있습니다. reverse proxy로 80번이나 443번 하나만 열어두면, 사용자가 어떤 네트워크에 있든 접속할 수 있습니다.
해결책: Nginx reverse proxy로 80번 하나로
Nginx reverse proxy의 핵심 아이디어는 단순합니다. 들어오는 모든 요청을 80번(또는 443번) 포트에서 받고, 도메인 이름이나 경로를 보고 알맞은 뒷단 서비스로 전달하는 것입니다. 이 방식은 Nginx 공식 문서의 reverse proxy 가이드에서 권장하는 표준 패턴이기도 합니다.
가장 기본이 되는 설정은 이렇습니다:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
한 줄씩 뜯어보겠습니다. listen 80은 Nginx가 80번 포트에서 들어오는 요청을 받겠다는 의미입니다. server_name app.example.com은 이 server 블록이 어떤 도메인에 반응할지 지정합니다. location /은 모든 경로에 대해 아래 설정을 적용하겠다는 뜻입니다.
그리고 proxy_pass http://127.0.0.1:3000이 핵심입니다. 들어온 요청을 로컬의 3000번 포트로 전달합니다. proxy_set_header 두 줄은 클라이언트의 실제 IP와 호스트 정보를 뒷단 서비스가 알 수 있게 해줍니다 — 이게 없으면 뒷단 서비스는 모든 요청이 127.0.0.1에서 온 것으로 착각합니다.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for와 proxy_set_header X-Forwarded-Proto $scheme 두 줄을 추가하는 것도 권장합니다. 전자는 요청이 거쳐 온 프록시 체인을 기록해서 IP 추적을 가능하게 하고, 후자는 원래 요청이 HTTP였는지 HTTPS였는지를 뒷단에 전달합니다. 많은 웹 프레임워크가 이 헤더를 참조해서 redirect URL을 올바르게 생성합니다.
서비스 여러 개: server 블록만 추가하면 된다
서비스가 여러 개일 때 복잡해 보이지만, 실제로는 server 블록을 복사해서 server_name과 proxy_pass만 바꾸면 끝입니다.
# 프론트엔드
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# 백엔드 API
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# 관리자 페이지
server {
listen 80;
server_name admin.example.com;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
이제 사용자는 app.example.com, api.example.com, admin.example.com으로 접속하면 됩니다. 포트 번호를 입력할 필요가 없습니다. 전부 80번 포트로 들어오고, Nginx가 도메인 이름을 보고 알맞은 서비스로 라우팅합니다.
DNS 설정에서 각 서브도메인(app, api, admin)이 전부 같은 서버 IP를 가리키하게 만들면 됩니다. 와일드카드 A 레코드(*.example.com)를 쓰거나, 서브도메인마다 개별 A 레코드를 추가하면 됩니다. 새 서비스를 추가할 때 DNS와 Nginx server 블록만 추가하면 되니까 확장성도 좋습니다.
HTTPS 인증서도 한 곳에서
reverse proxy의 또 다른 큰 장점은 SSL/TLS 인증서 관리입니다. 각 서비스에서 인증서를 따로 관리하려면 갱신 로직도 따로 만들어야 하고, 인증서 파일 위치도 서비스마다 다릅니다. Nginx에서 한 번에 처리하면 이 모든 복잡함이 사라집니다.
Certbot을 쓰면 됩니다:
# 인증서 발급 + Nginx 설정 자동 갱신
sudo certbot --nginx -d app.example.com -d api.example.com -d admin.example.com
Certbot이 Nginx 설정 파일을 자동으로 수정해서 443번 포트를 열고, HTTP 요청을 HTTPS로 리다이렉트하게 만들어 줍니다. 갱신도 certbot renew 한 줄이면 3개 도메인 전부 갱신됩니다. 서비스 뒷단은 여전히 HTTP로 돌아가고 있어도 됩니다 — Nginx가 클라이언트와의 암호화를 담당하니까요.
갱신 자동화는 crontab에 한 줄 추가하면 끝입니다. Certbot은 인증서 만료 30일 전부터 갱신을 시도하므로, 매일 새벽에 실행되게 설정해 두면 인증서 만료로 서비스가 멈추는 일은 없습니다. Nginx 프로세스에 reload 신호를 보내는 것까지 Certbot이 자동화해 줍니다.
WebSocket / 실시간 통신 지원
웹소켓이나 Server-Sent Events를 쓰는 서비스라면, 프록시 설정에 Upgrade 헤더를 추가해야 합니다. 그렇지 않으면 Nginx가 연결을 일반 HTTP 요청으로 취급해서 실시간 통신이 끊깁니다.
location /ws {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
proxy_http_version 1.1과 함께 Upgrade/Connection 헤더를 설정하면 Nginx가 WebSocket 연결을 정상적으로 프록시합니다. 채팅, 실시간 알림, 스트리밍 서비스를 운영 중이라면 반드시 필요한 설정입니다. 이 설정을 빼먹으면 클라이언트에서는 “WebSocket connection failed” 에러가 나고, Nginx 에러 로그에는 아무것도 남지 않아서 원인을 찾기 어렵습니다.
접속 로그와 보안 헤더도 한 곳에서
서비스마다 로깅을 따로 구현하는 것보다 Nginx에서 통합 로깅하는 것이 훨씬 효율적입니다. 접속 로그, 에러 로그, 레이트 리밋, IP 차단까지 Nginx 설정 한 곳에서 처리할 수 있습니다.
# 레이트 리밋 (IP당 초당 10요청)
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
listen 80;
server_name api.example.com;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:8080;
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;
}
}
이렇게 하면 DDoS나 무한 크롤링으로부터 뒷단 서비스를 보호할 수 있습니다. 뒷단 애플리케이션 코드를 고칠 필요 없이 Nginx 설정만으로 트래픽 제어가 가능합니다.
보안 헤더 추가도 Nginx에서 통합 관리하는 것이 편리합니다. X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security 같은 헤더를 각 서비스마다 설정하는 대신, Nginx에서 한 번에 추가할 수 있습니다. 이렇게 하면 새 서비스를 추가할 때 보안 헤더를 깜빡하는 일이 없습니다.
로드 밸런싱: 트래픽을 여러 인스턴스로 분산
서비스가 커지면 하나의 백엔드 인스턴스로 트래픽을 감당할 수 없게 됩니다. Nginx는 upstream 블록으로 여러 백엔드 서버를 묶어서 라운드로빈 방식으로 요청을 분산할 수 있습니다. 이 기능 덕분에 Nginx는 단순 reverse proxy를 넘어 로드 밸런서로도 널리 쓰입니다.
upstream 블록에 서버를 3개 등록하면 Nginx가 요청을 순차적으로 1번, 2번, 3번, 다시 1번… 하면서 분산합니다. weight 매개변수로 비율을 조정할 수도 있어서, 성능이 다른 서버를 섞어 쓸 때 유용합니다. max_fails와 fail_timeout을 설정하면, 일정 횟수 이상 실패한 서버를 자동으로 분산 대상에서 제외합니다. 수동으로 서버를 빼고 넣는 작업이 필요 없으니 무중단 배포나 오토스케일링 환경에서 특히 유용합니다.
헬스 체크는 Nginx의 기능만으로는 수동이지만, proxy_next_upstream 지시자로 요청 실패 시 자동으로 다음 서버로 재시도하게 만들 수 있습니다. 사용자는 백엔드 서버 하나가 일시적으로 응답하지 않아도 서비스 장애를 겪지 않습니다. 상용 환경에서는 이 설정만으로 가용성이 크게 올라갑니다.
자주 발생하는 에러와 해결법
reverse proxy를 설정하다 보면 몇 가지 에러가 반복적으로 나타납니다. 가장 흔한 두 가지를 정리합니다.
502 Bad Gateway — 뒷단 서비스가 응답하지 않을 때 발생합니다. 원인은 세 가지입니다. 첫째, 서비스가 아직 실행되지 않았거나 크래시된 상태입니다. systemctl status나 docker ps로 서비스 상태를 확인합니다. 둘째, proxy_pass의 포트 번호가 틀렸습니다. 서비스가 실제로 listening하는 포트와 Nginx 설정의 포트가 같은지 확인합니다. 셋째, 서비스가 127.0.0.1이 아니라 다른 인터페이스에 바인딩되어 있습니다. 서비스가 0.0.0.0:3000이 아니라 localhost:3000으로만 바인딩된 경우, Docker 컨테이너 내부에서는 접속되지만 호스트의 Nginx에서는 접속되지 않을 수 있습니다.
504 Gateway Timeout — 뒷단 서비스가 응답은 하지만 너무 느릴 때 발생합니다. 기본 타임아웃은 60초입니다. 대용량 파일 업로드나 무거운 연산을 하는 API라면 proxy_read_timeout 300s로 연장해야 합니다. 하지만 타임아웃을 무한정 늘리는 것은 위험합니다 — 근본적으로 응답이 느린 원인을 해결하거나, 비동기 처리(큐잉)로 전환하는 것이 좋습니다.
실전 배포: Docker Compose와 Nginx 연동
실제 프로덕션 환경에서는 Nginx와 Docker를 조합해서 쓰는 경우가 많습니다. Docker Compose로 여러 서비스 컨테이너를 띄우고, Nginx 컨테이너가 앞단에서 트래픽을 분배하는 구성입니다. 이 구성의 장점은 서비스 추가가 컨테이너 하나 추가로 끝난다는 것입니다. 컨테이너를 추가하고 Nginx server 블록에 proxy_pass만 지정하면 됩니다.
# docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- frontend
- backend
frontend:
image: myapp-frontend
# 외부 포트 노출 없음
backend:
image: myapp-backend
# 외부 포트 노출 없음
여기서 주목할 점은 frontend와 backend 컨테이너가 포트를 호스트에 노출하지 않는다는 것입니다. 외부에서는 Nginx의 80번과 443번만 접근 가능하고, Nginx가 내부 Docker 네트워크를 통해 각 컨테이너로 요청을 전달합니다. 보안 관점에서 매우 바람직한 구성입니다. 뒷단 서비스의 포트가 인터넷에 직접 노출되지 않으니 공격 면적이 줄어듭니다.
Nginx 설정에서 proxy_pass 주소를 컨테이너 이름으로 지정할 수 있습니다. proxy_pass http://frontend:3000처럼 쓰면 Docker의 내장 DNS가 컨테이너 이름을 해당 IP로 해석해 줍니다. 컨테이너의 IP는 재시작 때마다 바뀔 수 있지만 컨테이너 이름은 고정되어 있으므로 Nginx 설정을 수정할 필요가 없습니다. 이것이 Docker Compose와 Nginx를 함께 쓰는 핵심 이점입니다.
성능 튜닝: keepalive와 버퍼 설정
기본 Nginx 설정으로도 충분히 빠르지만, 트래픽이 많아지면 몇 가지 튜닝이 필요합니다. 가장 효과적인 설정은 upstream keepalive입니다. 이 설정은 Nginx와 뒷단 서비스 사이의 TCP 연결을 재사용해서 매 요청마다 새 연결을 맺는 오버헤드를 없앱니다. 초당 수백 요청을 처리하는 API 서버라면 이 설정만으로 응답 시간이 눈에 띄게 개선됩니다.
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
keepalive 32는 Nginx가 뒷단과 유지할 최대 유휴 연결 수입니다. proxy_set_header Connection ""을 설정하면 Nginx가 기본적으로 보내는 Connection: close 헤더가 제거되어 keepalive 연결이 정상 작동합니다. 숫자 32는 일반적으로 충분하지만, 트래픽에 따라 64나 128로 늘릴 수 있습니다.
버퍼 크기도 확인해야 합니다. 기본값이 작아서 큰 응답을 받을 때 디스크에 임시 파일을 쓰는 경우가 있습니다. 디스크 쓰기가 발생하면 응답 속도가 급격히 떨어집니다. proxy_buffer_size 16k와 proxy_buffers 8 16k 정도로 설정하면 대부분의 응답을 메모리에서 처리할 수 있습니다. Nginx 에러 로그에 upstream response is temporarily paused 메시지가 보이면 버퍼 부족을 의심할 수 있습니다.
이러한 튜닝은 처음부터 모두 적용할 필요는 없습니다. 트래픽이 늘고 응답 시간이 느려지는 시점에 로그를 단서로 점진적으로 적용하는 것이 실용적인 접근입니다. Nginx는 설정 변경 후 nginx -t로 문법을 검증하고 nginx -s reload로 무중단 적용할 수 있어 튜닝 사이클이 빠릅니다.
정리: 포트 번호 노트는 이제 버리세요
Nginx reverse proxy의 핵심은 “클라이언트는 포트를 몰라도 되고, 서비스는 인증서를 직접 관리하지 않아도 된다”는 것입니다. 80번 포트 하나로 들어와서 도메인별로 분배되고, HTTPS 인증서도 Nginx가 알아서 처리합니다. 서비스를 추가할 때는 server 블록 하나 더 만들고 server_name과 proxy_pass만 바꾸면 됩니다.
VPS에서 서비스가 늘어날수록 reverse proxy 하나로 깔끔하게 정리해 보세요. 포트 번호를 외우는 일은 이제 그만하셔도 됩니다. SSH로 서버에 접속해서 설정 파일을 수정할 때는 tmux 세션 안에서 작업하면 연결이 끊겨도 안전하게 마칠 수 있습니다.
관련 글
- docker compose로 1분 만에 셀프 호스팅 서비스 띄우기 (2026 완벽 가이드)
- Nginx 리버스 프록시 + Let's Encrypt SSL 완벽 가이드: VPS 한 대로 다중 서비스 (2026)
- SSH 끊겨도 작업 유지 — tmux 세션 사용법 완벽 가이드 (2026)
## Nginx 성능 최적화 – 실전 영상 가이드



















