infra

Nginx 리버스 프록시로 Node.js 앱을 도메인에 연결하는 방법

Node.js 앱을 직접 80 포트로 띄우는 대신 Nginx를 리버스 프록시로 앞에 세우면 도메인 연결과 HTTPS 적용이 깔끔해진다. 설정 파일 작성부터 Certbot SSL 적용까지 정리했다.

Nginx리버스프록시Node.jsHTTPSinfra
Nginx 리버스 프록시 설정 파일 — server 블록에서 proxy_pass로 Node.js localhost:3000을 연결하는 구조
  • ·Nginx는 1024 미만 포트(80, 443)에서 실행되고 Node.js 앱은 3000번 이상 포트에서 실행
  • ·proxy_pass: Nginx가 요청을 전달할 upstream 주소 지정
  • ·Let's Encrypt 인증서 유효 기간: 90일 (Certbot이 자동 갱신 처리)
  • ·Nginx 설정 문법 검사: nginx -t
Node.js 앱을 직접 80 포트로 띄우려다 root 권한 문제에 막혔고, 그 이후 Nginx를 앞에 세워서 리버스 프록시로 연결하는 방식으로 바꿨다. 같은 서버에서 여러 도메인을 운영할 때도 Nginx 하나에서 server 블록으로 분리하면 되니까 이후에는 앱을 추가할 때마다 고민 없이 설정 파일만 추가하고 있다. Let's Encrypt Certbot으로 SSL 인증서를 받아서 붙이는 것도 리버스 프록시가 있으니 훨씬 깔끔하게 처리됐다.

Nginx 리버스 프록시란 무엇인가

Nginx 리버스 프록시가 Node.js 도메인 연결에 필요한 이유

Node.js 앱은 기본적으로 3000번이나 8080번 같은 1024 이상의 포트에서 실행된다. 사용자가 브라우저에서 도메인을 입력하면 80번(HTTP)이나 443번(HTTPS) 포트로 연결되기 때문에, Node.js 앱을 직접 웹에 노출하려면 80 포트로 바꿔야 한다. 문제는 1024 미만 포트는 root 권한이 있어야 바인딩할 수 있다는 점이다. Node.js를 root로 실행하는 건 보안상 권장되지 않는다. Nginx를 리버스 프록시로 앞에 세우면 이 문제가 해결된다. Nginx는 80, 443 포트에서 요청을 받아 내부 3000번 포트로 전달한다. 사용자는 도메인에 접속하고, Nginx가 그 요청을 Node.js 앱에 대신 전달하는 구조다. Node.js는 계속 3000번 포트에서 실행되고 외부에는 80, 443만 노출된다. 한 서버에서 여러 도메인을 운영할 때도 리버스 프록시가 필수다. 하나의 IP에서 도메인 A는 앱 A로, 도메인 B는 앱 B로 요청을 나눠서 전달하는 것도 Nginx server 블록으로 간단하게 처리할 수 있다. SSL 인증서 처리도 Nginx에서 일괄 관리하면 각 앱에서 별도로 설정할 필요가 없다.

Nginx 리버스 프록시와 포워드 프록시의 차이

프록시는 클라이언트와 서버 사이에서 요청을 중계하는 서버다. 포워드 프록시(Forward Proxy)는 클라이언트 쪽에 위치해서 클라이언트를 대신해 서버에 요청한다. 회사 내부망에서 직원들의 인터넷 요청을 검열하거나 캐싱하는 프록시 서버가 전형적인 포워드 프록시다. 요청을 받는 서버 입장에서는 실제 클라이언트가 누구인지 모르고 프록시 IP만 보인다. 리버스 프록시(Reverse Proxy)는 서버 쪽에 위치해서 클라이언트 요청을 내부 서버에 대신 전달한다. 클라이언트 입장에서는 리버스 프록시가 서버인 것처럼 보이고, 뒤에 어떤 서버가 있는지 알 수 없다. Nginx를 앞에 세우고 Node.js 앱이 뒤에서 실행되는 구성이 바로 리버스 프록시다. 웹 서비스에서 리버스 프록시를 쓰는 이유는 다양하다. 로드 밸런싱, SSL 종단, 정적 파일 캐싱, 요청 로깅, 내부 서버 IP 은닉 등이 모두 리버스 프록시 레이어에서 처리된다. Apache도 리버스 프록시로 쓸 수 있지만 Nginx는 이벤트 기반 아키텍처로 동시 연결 처리 성능이 뛰어나서 리버스 프록시 역할로 가장 널리 쓰인다.

Nginx 리버스 프록시 설정하는 방법

Nginx 리버스 프록시로 Node.js 앱을 도메인에 연결하는 방법

Ubuntu 기준으로 /etc/nginx/sites-available/ 경로에 도메인 이름으로 파일을 만든다. example.com이라면 /etc/nginx/sites-available/example.com을 만든다. server 블록을 작성하고 listen 80으로 HTTP 요청을 받을 포트를 지정한다. server_name example.com www.example.com으로 연결할 도메인을 설정한다. location / { proxy_pass http://localhost:3000; } 블록이 핵심이다. 루트 경로로 들어오는 모든 요청을 로컬의 3000번 포트로 전달한다. 여기에 몇 가지 proxy_set_header 지시어를 추가해야 Node.js 앱에서 클라이언트의 실제 IP를 확인할 수 있다. 파일 작성 후 ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/로 심볼릭 링크를 만들고, nginx -t로 설정 문법을 검증한 뒤 systemctl reload nginx로 적용한다. nginx -t에서 오류가 없어야만 reload해야 한다. 오류가 있는 상태로 reload하면 Nginx 서비스 자체가 중단될 수 있다.

server {
  listen 80;
  server_name example.com www.example.com;

  location / {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    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_cache_bypass $http_upgrade;
  }
}

Nginx 리버스 프록시에 HTTPS와 SSL 인증서를 적용하는 방법

HTTP로만 서빙하면 브라우저에서 '안전하지 않음' 경고가 표시되고 검색 엔진 순위에도 불리하다. HTTPS를 적용하려면 SSL 인증서가 필요하다. Let's Encrypt의 Certbot을 사용하면 무료로 인증서를 발급받고 Nginx 설정에 자동으로 적용할 수 있다. Ubuntu에서는 apt install certbot python3-certbot-nginx로 Certbot을 설치하고, certbot --nginx -d example.com -d www.example.com 명령을 실행한다. 도메인이 서버 IP를 가리키고 있어야 인증서 발급이 가능하다. Certbot이 도메인 소유권을 확인하고 인증서를 발급한 뒤 Nginx 설정을 자동으로 수정해서 443 포트에서 HTTPS를 서빙하도록 만들어준다. 인증서 유효 기간은 90일이다. Certbot은 systemd timer로 자동 갱신을 처리한다. 갱신은 certbot renew --dry-run으로 테스트할 수 있다. 만료 30일 전부터 자동 갱신을 시도하기 때문에 별도 관리 없이 인증서가 만료되는 일은 거의 없다. Certbot 적용 후 Nginx 설정 파일을 보면 443 블록과 HTTP를 HTTPS로 리다이렉트하는 블록이 자동으로 추가되어 있다.

Nginx 리버스 프록시 운영과 트러블슈팅

Nginx 리버스 프록시로 여러 도메인을 한 서버에서 운영하는 방법

한 서버에서 여러 도메인을 운영하려면 도메인마다 server 블록을 분리하면 된다. /etc/nginx/sites-available/ 경로에 도메인별로 파일을 만들고, 각각 /etc/nginx/sites-enabled/에 심볼릭 링크를 걸어두면 된다. 첫 번째 도메인은 3000번 포트의 앱으로, 두 번째 도메인은 4000번 포트의 앱으로 라우팅하는 방식이다. Nginx는 요청이 들어오면 server_name을 보고 어떤 server 블록이 처리할지 결정한다. 도메인이 다르면 완전히 독립된 설정이 적용되기 때문에 각 앱이 서로 간섭 없이 운영된다. 하나의 도메인에서 경로별로 다른 앱으로 라우팅하는 것도 가능하다. location /api는 백엔드 앱으로, location /는 프론트엔드 앱으로 분기하는 방식이다. Next.js 프론트엔드와 Express 백엔드를 하나의 도메인에서 경로로 나눠 운영할 때 이 패턴을 직접 써봤는데, 도메인 하나로 프론트와 백을 동시에 서빙할 수 있어서 CORS 설정이 필요 없어졌다. 새 도메인을 추가할 때마다 sites-available에 파일 하나 만들고 Certbot으로 인증서 받고 reload하는 루틴이 익숙해지면 서버 추가가 부담스럽지 않다.

Nginx 리버스 프록시 설정 오류를 디버깅하는 방법

Nginx 설정을 수정하고 나서 예상대로 동작하지 않을 때 확인해야 할 것들이 있다. 첫 번째로 확인할 것은 nginx -t 명령이다. 이 명령은 설정 파일 문법을 검사하고 오류 위치를 알려준다. 설정을 바꿀 때마다 reload 전에 이 명령을 먼저 실행하는 습관을 들이면 잘못된 설정으로 Nginx가 구동되지 않는 상황을 방지할 수 있다. 두 번째는 오류 로그다. /var/log/nginx/error.log에 오류 내용이 기록된다. tail -f /var/log/nginx/error.log로 실시간으로 확인하면서 요청을 보내보면 어디서 실패하는지 바로 보인다. 가장 흔한 오류 유형은 upstream 연결 실패다. connect() failed (111: Connection refused)는 proxy_pass로 지정한 포트에 앱이 실행되지 않고 있다는 의미다. Node.js 앱이 실제로 그 포트에서 돌고 있는지 ss -tlnp | grep 3000으로 확인한다. 503 오류는 upstream이 응답하지 않을 때, 502는 upstream이 잘못된 응답을 보낼 때 나타난다. 설정 변경 후 systemctl reload nginx가 아니라 systemctl restart nginx로 완전 재시작하면 해결되는 경우도 있다.

자주 묻는 질문

Nginx 설정 후 브라우저에서 502 Bad Gateway가 뜨면 어떻게 하나요?+

proxy_pass로 지정한 포트에 앱이 실행 중인지 먼저 확인하세요. ss -tlnp | grep 3000으로 포트 3000이 listening 상태인지 확인합니다. 앱이 실행 중인데 502가 계속 난다면 /var/log/nginx/error.log에서 구체적인 오류 메시지를 확인하세요.

proxy_pass에서 localhost 대신 127.0.0.1을 써야 하나요?+

127.0.0.1이 조금 더 안정적입니다. localhost는 /etc/hosts 설정에 따라 IPv6 ::1로 해석될 수 있는데, Node.js 앱이 IPv4로만 바인딩하고 있으면 연결이 안 되는 경우가 생깁니다. 확실히 하고 싶다면 127.0.0.1을 쓰는 게 좋습니다.

Nginx를 거치면 Node.js 앱에서 클라이언트 IP가 127.0.0.1로 보이는 문제가 있습니다.+

proxy_set_header X-Real-IP $remote_addr 과 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for를 Nginx 설정에 추가하세요. Node.js 앱에서는 req.headers['x-real-ip'] 또는 req.headers['x-forwarded-for']로 실제 클라이언트 IP를 읽으면 됩니다.

정적 파일(이미지, CSS)은 Nginx에서 직접 서빙하는 게 낫나요?+

네, 정적 파일은 Nginx가 직접 서빙하는 게 빠릅니다. location /static { root /var/www/app; } 형태로 정적 파일 경로를 설정하면 Node.js 프로세스를 거치지 않아 불필요한 부하를 줄일 수 있습니다.

관련 글