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

- ·Nginx는 1024 미만 포트(80, 443)에서 실행되고 Node.js 앱은 3000번 이상 포트에서 실행
- ·proxy_pass: Nginx가 요청을 전달할 upstream 주소 지정
- ·Let's Encrypt 인증서 유효 기간: 90일 (Certbot이 자동 갱신 처리)
- ·Nginx 설정 문법 검사: nginx -t
Node.js 앱을 직접 80 포트로 띄우려다가 리눅스의 root 권한 문제에 콱 막혔던 적이 있습니다. 보안상 root로 실행하는 건 도저히 내키지 않아서 Nginx를 앞에 세우는 리버스 프록시 방식을 도입했는데, 결과적으로 신의 한 수였습니다. 같은 서버에서 여러 웹 서비스를 띄울 때도 Nginx 설정 파일 하나만 복사해서 포트만 바꿔 추가하면 되니 배포 관리가 엄청나게 쾌적해졌습니다. Let's Encrypt Certbot 덕분에 무료 SSL 적용과 자동 갱신까지 통틀어 10분도 안 걸려 해결한 순간의 쾌감은 아직도 생생합니다.
1. 리버스 프록시 개념과 필요성
왜 Node.js 앱 앞단에 Nginx 리버스 프록시를 세울까
개발 환경에서는 npm start를 치고 localhost:3000으로 접속해서 테스트하면 그만이지만, 실제 프로덕션 서버에 배포할 때는 이야기가 달라집니다. 브라우저에서 기본 HTTP 포트인 80이나 HTTPS 포트인 443으로 접속했을 때 앱이 바로 떠야 하는데, Linux 환경에서는 1024번 이하의 웰노운(Well-Known) 포트를 바인딩하려면 root 권한이 필수입니다. 보안 관점에서 Node.js 프로세스를 root 권한으로 실행하는 건 매우 위험한 삽질입니다. 해킹이라도 당하는 날에는 서버 전체 제어권이 넘어가기 때문이죠. 그래서 80/443 포트 수신은 안정적인 Nginx에 맡기고, Nginx가 들어온 요청을 안전하게 백엔드에서 실행 중인 3000번 등의 Node.js 포트로 토스해 주는 방식을 씁니다. 이 역할을 하는 중계 장치가 바로 리버스 프록시입니다. 이렇게 Nginx를 앞단에 세워두면 포트 포워딩 역할뿐만 아니라 버퍼링, gzip 압축, HTTP 요청 필터링 등 자잘한 보안 설정까지 Nginx 수준에서 처리할 수 있어 백엔드 앱의 부하를 덜고 더 안전하게 서버를 보호할 수 있습니다.
포워드 프록시와 리버스 프록시의 핵심 동작 차이
웹 서핑을 하다 보면 프록시(Proxy)라는 말을 자주 접하게 되는데, 방향에 따라 동작 방식이 완전히 다릅니다. 먼저 포워드 프록시(Forward Proxy)는 클라이언트(사용자) 측에 가깝게 붙어서 중계 역할을 수행합니다. 예를 들어 회사 보안망에서 직원들이 특정 사이트에 접속하는 것을 차단하거나, 자주 들어가는 사이트의 정적 소스를 미리 캐싱해서 빠르게 띄워주는 용도로 씁니다. 이때 외부 웹 서버 입장에서는 실제 접속자가 누구인지 모르고 오직 포워드 프록시 서버의 IP만 보게 됩니다. 반면 우리가 적용하려는 리버스 프록시(Reverse Proxy)는 반대로 서버 쪽에 붙어서 동작합니다. 클라이언트는 자신이 실제 백엔드 애플리케이션 서버에 요청을 보냈다고 믿지만, 실제로는 앞단에 대리인으로 서 있는 리버스 프록시(Nginx)와 통신하는 구조입니다. 리버스 프록시는 뒷단에 어떤 백엔드 구조가 숨겨져 있는지 외부로부터 감추는 일종의 방어막 역할을 합니다. 이를 통해 실제 Node.js가 돌아가는 서버의 내부 IP와 포트를 숨길 수 있고, 여러 대의 서버로 요청을 나누어 보내는 로드 밸런싱도 자연스럽게 처리할 수 있습니다.
2. Nginx 설정과 도메인 연결
Nginx 블록 설정을 통해 도메인과 Node.js 앱 연결하기
Nginx의 설정은 Ubuntu 기준으로 /etc/nginx/sites-available/에 설정 파일을 만들고, 이를 /etc/nginx/sites-enabled/ 경로로 심볼릭 링크를 걸어서 활성화하는 것이 표준 프로토콜입니다. 설정 파일 내 핵심은 server 블록인데, listen 80;으로 HTTP 기본 요청을 대기하고 server_name에 도메인을 선언해 줍니다. 그리고 내부 location / 블록 안에 proxy_pass http://localhost:3000; 지시어를 작성하여 해당 포트로 흘러가게 만듭니다. 여기서 진짜 중요한 트러블슈팅 팁이 있는데, Nginx 설정만 단순하게 넘기면 Node.js 소스코드 내부에서 클라이언트의 실제 IP 주소를 가져오려고 할 때 무조건 로컬 호스트인 127.0.0.1로만 찍히는 황당한 현상을 겪게 됩니다. 요청이 Nginx를 거쳐 들어왔기 때문이죠. 이를 방지하려면 proxy_set_header Host $host;, proxy_set_header X-Real-IP $remote_addr;, proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 같은 헤더 추가 설정들을 꼼꼼히 채워 넣어 요청의 원래 발신지 정보를 보존해야 합니다. 설정을 변경한 후에는 반드시 nginx -t를 쳐서 구문 오류가 없는지 테스트해야 하며, 성공 메시지를 본 다음 systemctl reload nginx를 해 줘야 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;
}
}Certbot을 이용한 Let's Encrypt HTTPS 인증서 무료 적용하기
요즘 웹 표준에서는 HTTPS 보안 프로토콜을 올리지 않으면 브라우저에서 '주의 요함' 경고가 뜨고, 포털 검색 노출에서도 엄청난 페널티를 받습니다. 다행히 Let's Encrypt라는 고마운 비영리 단체 덕분에 무료로 90일짜리 SSL/TLS 인증서를 손쉽게 발급받을 수 있습니다. 게다가 발급 툴인 Certbot이 Nginx 설정을 자동으로 파싱해서 수작업 없이 인증서 주입부터 HTTP를 HTTPS로 강제 리다이렉트시키는 설정까지 한 번에 완료해 줍니다. 설정 방법은 매우 간단해서, 패키지 매니저로 certbot python3-certbot-nginx를 설치한 뒤 sudo certbot --nginx -d 내도메인 명령어만 실행하고 이메일 입력 등의 몇 가지 질문에 답하기만 하면 됩니다. 한 가지 알아둘 꿀팁은 90일 만료 기간에 대한 갱신 관리입니다. Certbot은 설치 시점에 OS의 systemd 서비스나 cron 데몬에 하루에 두 번 만료 여부를 검사하고 자동 갱신을 시도하는 스케줄을 알아서 등록해 둡니다. 따라서 발급이 잘 완료됐다면 개발자가 수동으로 매번 인증서를 교체할 필요가 전혀 없습니다. 갱신 로직이 잘 도는지 테스트해 보고 싶다면 sudo certbot renew --dry-run을 실행해 가상 갱신 시뮬레이션 결과만 체크해 보면 안심하고 서비스를 돌릴 수 있습니다.
3. 실무 운영 및 문제 해결
단일 Nginx 서버에서 여러 독립 도메인 멀티 호스팅하기
비용 절감을 위해 클라우드 가상 서버(VPS) 하나만 개설하고 그 안에서 여러 개의 사이트를 동시에 띄워 운영하고 싶을 때가 많습니다. 예를 들어 하나의 IP로 들어오는 접속 요청 중 blog.me는 3000번 포트의 Node.js 블로그 앱으로 보내고, shop.me는 4000번 포트의 쇼핑몰 앱으로 나눠 보내고 싶을 때 Nginx 리버스 프록시가 진가를 발휘합니다. 이를 가상 호스트(Virtual Host) 또는 Nginx의 멀티 서버 블록 구성이라고 부릅니다. 설정법은 심플하게 /etc/nginx/sites-available/ 폴더 내에 각 도메인 명칭으로 설정 파일을 각각 따로 만들고, 내부의 server_name 지시어에 매핑하고자 하는 도메인을 지정한 뒤 서로 다른 proxy_pass 포트를 향하게 적어주면 끝입니다. 예전에 나도 동일 서버 안에 포트가 다른 백엔드를 3개나 올려두고 도메인 주소의 서브도메인 분기로 라우팅을 구성해 썼는데, 각 웹앱 간의 간섭도 전혀 없고 SSL 인증서 발급도 Certbot으로 각각 따로 지정해 주기만 하면 완벽하게 분기 처리가 되어 놀라울 정도로 쾌적하게 멀티 호스팅 서버 환경을 운영할 수 있었습니다.
Nginx 리버스 프록시 배포 후 자주 겪는 502/503 설정 오류 디버깅하기
Nginx 리버스 프록시 설정을 다 마치고 설레는 마음으로 도메인에 접속했을 때 화면에 거대한 '502 Bad Gateway'나 '503 Service Temporarily Unavailable' 문구만 덩그러니 뜨는 경우가 정말 허다합니다. 502 에러는 앞단의 Nginx는 정상 작동하고 있지만 뒷단의 Node.js 애플리케이션 서버가 꺼져 있거나 포트 바인딩이 엇갈려 요청을 넘겨주지 못할 때 발생합니다. 이때는 가장 먼저 pm2 status나 netstat -tlnp를 돌려 Node.js 서버가 명시한 포트(예: 3000)에서 확실히 살아 있는지부터 체크해야 합니다. 503 에러의 경우 일시적인 과부하일 수도 있지만, Nginx 설정 내 버퍼 사이즈 크기가 너무 작아서 대용량 API 응답이나 헤더 값을 처리하지 못하고 중단되는 경우도 잦습니다. 도저히 감이 안 잡힐 때는 무조건 /var/log/nginx/error.log 로그 파일을 까보는 것이 직관적인 팁입니다. 여기에 들어가면 upstream connection refused 같은 날것의 에러 텍스트가 정확한 파일 라인과 함께 찍혀 있어, 구글링 한 번이면 삽질 시간을 획기적으로 단축하고 원인을 규명해 해결할 수 있습니다.
자주 묻는 질문
proxy_pass 설정 시 주소에 localhost 대신 127.0.0.1을 사용하는 것이 더 좋나요?+
네, 실무에서는 127.0.0.1로 하드코딩하는 것을 더 추천합니다. OS에 따라 localhost라는 별칭이 IPv6 주소인 `::1`로 우선 매핑되는 경우가 있는데, 만약 Node.js 백엔드가 IPv4 대역(127.0.0.1)으로만 포트를 열어두었다면 Nginx가 대상을 찾지 못해 502 Bad Gateway 에러가 날 수 있기 때문입니다.
정적 파일(이미지, CSS 등)도 Nginx 리버스 프록시를 통해 Node.js로 매번 넘겨받아야 하나요?+
기능적으로는 동작하지만 성능상 비효율적입니다. Nginx는 C언어로 빌드되어 정적 파일 서빙 능력이 압도적으로 빠르기 때문에, `/static` 또는 `/public` 같은 경로에 대해서는 Nginx `location` 블록에 `root` 지시어를 써서 Nginx가 디스크에서 직접 파일을 브라우저로 내려주도록 구성하는 것이 서버 자원을 크게 아끼는 방법입니다.
관련 글
Docker로 Jenkins 설치하고 초기 설정까지 완료하는 방법
운영체제 환경의 꼬임 없이 깔끔하게 Docker 컨테이너 위에 Jenkins를 실행하는 방법과 데이터 휘발을 막기 위한 볼륨 마운트 전략, 초기 어드민 인증을 푸는 DevOps 가이드를 제공합니다.
Next.js 정적 사이트를 Jenkins로 자동 배포하는 방법 — Jenkinsfile로 빌드부터 배포까지
Next.js 프로젝트를 output: 'export' 설정으로 정적 빌드한 뒤, Jenkins 파이프라인을 구축해 원격 운영 서버로 자동 배포하는 정석 프로세스를 알아봅니다. Credentials를 통한 SSH 안전 연동 팁도 다룹니다.