/인프라/Nginx 리버스 프록시 설정: API·관리자·프론트 한 도메인 라우팅 복붙 예제
인프라nginx 리버스 프록시proxy_pass 설정

Nginx 리버스 프록시 설정: API·관리자·프론트 한 도메인 라우팅 복붙 예제

단일 서버에서 API(3000)·관리자(4000)·정적 프론트를 Nginx 리버스 프록시로 분기하는 법을 복붙 예제로 정리. proxy_pass 슬래시 규칙, WebSocket, Let's Encrypt SSL과 80→443 리다이렉트까지 검증 명령 포함.

Nginx 리버스 프록시 설정: API·관리자·프론트 한 도메인 라우팅 복붙 예제

Nginx 리버스 프록시 설정 완전 가이드: API·관리자·프론트를 한 도메인으로 라우팅하기

백엔드는 3000번에 잘 떠 있는데, 도메인으로 붙이려니 막막하다

VPS 한 대에 API 서버(:3000), 관리자 백오피스(:4000), 그리고 빌드된 정적 프론트엔드를 올려뒀다고 해봅시다. 로컬에서 curl http://127.0.0.1:3000은 잘 응답하는데, 막상 https://example.com/api로 붙이려니 어디서부터 손대야 할지 막막합니다.

이때 필요한 게 리버스 프록시(Reverse Proxy) 입니다. 외부에서 들어오는 80/443 요청을 Nginx가 한 번에 받아서, 경로에 따라 적절한 내부 포트로 넘겨주는 역할이죠. 백엔드 포트는 외부에 노출하지 않고 127.0.0.1에만 바인딩한 뒤, Nginx만 방화벽을 열어두는 게 요즘 1인 개발/사이드프로젝트의 표준 구성입니다.

이 글의 최종 목표는 명확합니다. 하나의 도메인(example.com)으로 API·관리자·프론트를 모두 서비스하고, WebSocket과 HTTPS까지 자력으로 구성하는 것. "일단 복붙 → 동작 확인 → 왜 그런지 이해" 순서로 갑니다.

⚠️ 이 글은 "설정을 올바르게 작성" 하는 관점입니다. 설정은 맞는데 502 Bad Gateway가 뜬다면 그건 백엔드가 죽었거나 포트가 안 맞는 경우로, 진단 관점의 별도 글(👉 nginx 502 Bad Gateway 해결)을 참고하세요. 역할을 분리해두면 문제 추적이 훨씬 빠릅니다.


1단계. 가장 기본형: proxy_pass로 백엔드 1개 연결하기

먼저 백엔드 하나만 프록시하는 최소 server 블록입니다. /etc/nginx/sites-available/example.com에 저장하고 심볼릭 링크를 걸면 됩니다.

Nginx
# /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com;

    location / {
        # 들어온 요청을 내부 3000번 백엔드로 그대로 넘긴다
        proxy_pass http://127.0.0.1:3000;
    }
}

proxy_pass http://127.0.0.1:3000; 이 한 줄이 핵심입니다. "클라이언트가 보낸 요청을 그대로 127.0.0.1:3000으로 전달하라"는 뜻이죠. Nginx가 클라이언트와 백엔드 사이에 중계 서버로 끼어드는 겁니다.

▸ 왜 필요한가: 백엔드를 외부에 직접 노출하지 않고 Nginx 한 곳에서 트래픽을 받기 위해서입니다.

▸ 검증:

Bash
# 설정 문법 검사 (반드시 OK가 떠야 reload)
sudo nginx -t

# 무중단 재적용
sudo systemctl reload nginx

# 응답 헤더 확인 (200 또는 백엔드 상태코드가 떠야 정상)
curl -I http://example.com/

curl -I에서 백엔드가 보낸 Server, Content-Type 헤더가 보이면 프록시가 정상 동작하는 겁니다.


2단계. location 경로별 다중 백엔드 분기

이제 본론입니다. /api는 3000번, /admin은 4000번, 나머지 /는 정적 프론트로 분기합니다.

Nginx
server {
    listen 80;
    server_name example.com;

    # 정적 프론트엔드 (빌드 결과물 디렉터리)
    root /var/www/frontend/dist;
    index index.html;

    # /api/* → API 서버(3000)
    location /api/ {
        proxy_pass http://127.0.0.1:3000/;   # 끝 슬래시 주의!
    }

    # /admin/* → 관리자 서버(4000)
    location /admin/ {
        proxy_pass http://127.0.0.1:4000/;
    }

    # 그 외 모든 경로 → SPA 라우팅 (없으면 index.html로)
    location / {
        try_files $uri $uri/ /index.html;
    }
}

trailing slash 하나가 경로를 바꾼다 — 404의 가장 흔한 원인

복붙했는데 404가 뜨거나 경로가 깨지는 사고의 90%는 proxy_pass 끝의 슬래시(/) 때문입니다. location /api/ 기준으로 /api/users 요청이 백엔드에 어떻게 도착하는지 비교해봅시다.

proxy_pass 설정요청 URL백엔드 도착 경로설명
proxy_pass http://127.0.0.1:3000; (슬래시 없음)/api/users/api/userslocation 경로를 그대로 붙여 전달
proxy_pass http://127.0.0.1:3000/; (슬래시 있음)/api/users/userslocation 매칭 부분(/api)을 떼고 전달

규칙은 간단합니다. proxy_pass에 URI(끝 슬래시 포함)가 있으면 location에 매칭된 앞부분을 잘라내고 나머지를 붙입니다. API 서버 내부 라우트가 /users로 시작한다면 슬래시를 붙여야 하고, /api/users로 정의돼 있다면 슬래시를 빼야 합니다. 둘 다 멀쩡한 설정이지만 결과가 정반대라 헷갈리기 쉽습니다.

▸ 검증:

Bash
sudo nginx -t && sudo systemctl reload nginx

curl -I http://example.com/api/health   # API 백엔드 응답?
curl -I http://example.com/admin/        # 관리자 응답?
curl -I http://example.com/              # index.html 응답?

3단계. 실서비스 필수 설정: 원본 정보 헤더 + WebSocket

여기까지만 하면 동작은 하지만, 백엔드 입장에서는 "모든 요청이 127.0.0.1(Nginx)에서 왔다" 고 보입니다. 실제 클라이언트 IP, 원본 호스트, http/https 여부가 전부 사라지죠. 이걸 헤더로 전달해줘야 합니다.

Nginx
location /api/ {
    proxy_pass http://127.0.0.1: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;
}

각 헤더가 백엔드에서 결정하는 것

헤더백엔드에서 결정하는 것
Host $host백엔드가 인식하는 요청 도메인. 멀티 도메인 분기·리다이렉트 URL 생성에 사용
X-Real-IP $remote_addr실제 클라이언트 IP 한 개. 접속 로그·차단 목록에 사용
X-Forwarded-For $proxy_add_x_forwarded_for프록시를 거친 클라이언트 IP 체인. 다단 프록시 환경에서 원본 IP 추적
X-Forwarded-Proto $scheme클라이언트가 http/https 중 무엇으로 왔는지. 백엔드의 https 인식·리다이렉트 URL 결정

이 헤더들이 없으면 백엔드 로그에는 모든 사용자가 127.0.0.1로 찍히고, https로 접속했는데도 백엔드가 http로 착각해 리다이렉트 루프가 도는 사고가 납니다. 실무에서 "로그인 후 무한 리다이렉트" 이슈를 며칠 잡았더니 범인이 X-Forwarded-Proto 누락이었던 적이 있는데, 이 네 줄은 처음부터 세트로 넣는 습관을 권합니다.

WebSocket 프록시 (채팅·알림·HMR 개발 서버)

WebSocket은 일반 HTTP 요청을 Upgrade로 전환하는 핸드셰이크가 필요합니다. Nginx는 기본적으로 이 헤더를 백엔드로 넘기지 않으므로 명시해줘야 합니다. 먼저 http 컨텍스트map을 추가합니다.

Nginx
# /etc/nginx/nginx.conf 의 http { } 블록 안에 추가
# (server 블록 안이 아니라 http 컨텍스트여야 함!)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

그리고 WebSocket 엔드포인트 location에 Upgrade 헤더를 넘깁니다.

Nginx
location /ws/ {
    proxy_pass http://127.0.0.1:3000/;

    # WebSocket 핸드셰이크 필수 3종
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    # 원본 정보 헤더도 동일하게
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

▸ 왜 필요한가: Upgrade/Connection 헤더가 있어야 HTTP 연결이 WebSocket으로 승격(101 Switching Protocols)됩니다.

▸ 검증: WebSocket은 curl로 핸드셰이크 응답(101)을 직접 확인할 수 있습니다.

Bash
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
  -H "Sec-WebSocket-Version: 13" \
  http://example.com/ws/
# → HTTP/1.1 101 Switching Protocols 가 떠야 성공

4단계. Let's Encrypt SSL 종단(443) + 80→443 리다이렉트

마지막은 HTTPS입니다. certbot의 nginx 플러그인이 사실상 표준이라, 발급과 설정 자동 삽입을 한 번에 처리합니다.

Bash
# certbot 설치 (Ubuntu/Debian 예시)
sudo apt install certbot python3-certbot-nginx

# 인증서 발급 + nginx 설정 자동 수정
sudo certbot --nginx -d example.com

certbot --nginx를 돌리면 위에서 만든 server 블록에 다음과 같은 443 설정과 80→443 리다이렉트가 자동으로 추가됩니다.

Nginx
server {
    listen 443 ssl http2;          # HTTP/2 활성화
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;   # TLS 1.3 등 권장값
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # ... (여기에 2~3단계의 location 블록들이 들어감)
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;   # 모든 http → https 리다이렉트
}

▸ 검증 + 자동 갱신 점검:

Bash
curl -I https://example.com/api/health   # https로 정상 응답?
sudo certbot renew --dry-run             # 자동 갱신 시뮬레이션 (에러 없어야 함)

최종 통합 server 블록 전체

위 모든 단계를 하나로 합친 완성본입니다. 그대로 복붙하고 도메인·경로·포트만 바꿔 쓰세요.

Nginx
# /etc/nginx/nginx.conf 의 http { } 안에 한 번만
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# 80 포트: 전부 https로 리다이렉트
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

# 443 포트: 실제 서비스
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/frontend/dist;
    index index.html;

    # 공통 헤더 세트 (재사용)
    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;

    # API 서버
    location /api/ {
        proxy_pass http://127.0.0.1:3000/;
    }

    # 관리자 서버
    location /admin/ {
        proxy_pass http://127.0.0.1:4000/;
    }

    # WebSocket
    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 $connection_upgrade;
    }

    # 정적 프론트(SPA)
    location / {
        try_files $uri $uri/ /index.html;
    }
}

흔한 실수 체크리스트

증상원인해결
/api/users가 404proxy_pass 끝 슬래시 유무 잘못2단계 표 보고 슬래시 맞추기
unknown directive "map"mapserver 안에 넣음http 컨텍스트(nginx.conf)로 이동
로그에 모든 IP가 127.0.0.1원본 헤더 누락X-Real-IP/X-Forwarded-For 추가
https인데 무한 리다이렉트X-Forwarded-Proto 누락헤더 추가 후 백엔드 재시작
WebSocket 연결 끊김Upgrade/Connection 미설정3단계 WS 블록 적용
인증서 만료로 접속 불가갱신 자동화 미점검certbot renew --dry-run 정기 확인

💡 설정은 다 맞는데 502 Bad Gateway가 뜬다면, 그건 Nginx 문제가 아니라 백엔드(3000/4000)가 죽었거나 포트가 안 맞는 신호입니다. 진단 절차는 nginx 502 Bad Gateway 해결 글에서 별도로 다룹니다. 이 글(설정 작성)과 그 글(에러 진단)을 역할로 나눠 기억해두면 문제 해결이 빠릅니다.


자주 묻는 질문 (FAQ)

Q. proxy_pass에 슬래시를 붙여야 하나요, 빼야 하나요? A. 백엔드 라우트 정의에 달렸습니다. 백엔드가 /users로 라우트를 받으면 location /api/에서 슬래시를 붙여(...:3000/) /api를 떼고 넘기고, 백엔드가 /api/users로 정의돼 있으면 슬래시를 빼서 경로를 그대로 전달하세요.

Q. map 블록을 server 안에 넣었더니 에러가 납니다. A. maphttp 컨텍스트 전용 지시어입니다. server { } 안이 아니라 /etc/nginx/nginx.confhttp { } 블록에 한 번만 선언하세요. 여러 server에서 공유됩니다.

Q. Let's Encrypt 인증서는 수동으로 갱신해야 하나요? A. 아니요. certbot은 설치 시 systemd 타이머나 cron으로 자동 갱신을 등록합니다. 정상 동작 여부만 sudo certbot renew --dry-run으로 가끔 점검하면 됩니다. 갱신 후 Nginx reload도 플러그인이 처리합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 16일

댓글

불러오는 중...