사용자 브라우저
↓
Nginx
↓
NestJS / Next.js / API Server
Nginx 없이:
사용자 → Node.js 앱 직접 접근
Nginx 사용:
사용자 → Nginx → Node.js 앱
장점:
보안, HTTPS, 라우팅, 운영 설정 관리가 쉬워짐
| 역할 | 의미 | 예시 |
|---|---|---|
| Web Server | 파일을 직접 응답 | HTML, CSS, JS, 이미지 |
| Reverse Proxy | 요청을 다른 서버로 전달 | /api 요청을 NestJS로 전달 |
사용자:
https://example.com
Nginx:
dist/index.html, JS, CSS 파일 제공
사용자:
https://api.example.com/admin/consults
Nginx:
localhost:3000에서 실행 중인 NestJS로 요청 전달
Client
↓
https://api.example.com
↓
Nginx :443
↓
NestJS localhost:3000
Node.js 앱 직접 노출:
포트 관리 어려움
HTTPS 적용 번거로움
정적 파일/압축/캐시 처리 약함
운영 설정 분산
여러 앱 라우팅 어려움
외부 공개:
80, 443
내부 앱:
3000, 3001, 4000 등
Nginx:
외부 요청을 내부 앱으로 전달
server 블록과 location 블록으로 구성됩니다.server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
}
}
server:
특정 도메인과 포트에 대한 설정
예:
example.com의 80 포트 요청 처리
location:
요청 경로별 처리 방식
예:
/api 요청은 백엔드로 전달
/admin 요청은 관리자 앱으로 전달
/static 요청은 정적 파일 제공
proxy_pass:
요청을 넘길 내부 서버 주소
예:
proxy_pass http://localhost:3000;
server_name은 도메인 기준입니다.location은 URL 경로 기준입니다.proxy_pass는 실제 요청을 넘길 대상입니다.localhost:3000에서 실행 중이라면 Nginx는 외부 요청을 해당 포트로 넘길 수 있습니다.server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
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;
}
}
| Header | 의미 |
|---|---|
Host | 원래 요청 도메인 |
X-Real-IP | 실제 사용자 IP |
X-Forwarded-For | 프록시를 거친 IP 목록 |
X-Forwarded-Proto | http/https 여부 |
req.ip, protocol, secure cookie 설정에 영향을 줄 수 있습니다.사용자 IP:
운영 로그, 중복 IP 확인, rate limit
X-Forwarded-Proto:
HTTPS 판단, secure cookie, redirect
HTTP:
암호화 없음
HTTPS:
TLS 인증서 기반 암호화
HTTP로 상담 신청:
이름, 전화번호가 암호화 없이 전송될 수 있음
HTTP로 관리자 로그인:
관리자 세션 탈취 위험 증가
도메인 준비
↓
Nginx 80 포트 연결
↓
Certbot 인증서 발급
↓
Nginx HTTPS 설정 자동 반영
↓
인증서 자동 갱신 설정
sudo apt update
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api.example.com
sudo certbot renew --dry-run
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
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;
}
}
http://api.example.com
↓
301 redirect
↓
https://api.example.com
↓
Nginx 443
↓
NestJS localhost:3000
http://로 접속해도 자동으로 https://로 이동하게 해야 합니다.sudo nginx -t
정상 예시:
syntax is ok
test is successful
sudo systemctl reload nginx
sudo systemctl status nginx
reload는 설정을 부드럽게 다시 읽습니다.nginx -t → reload 순서가 안전합니다.sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
| 상태 코드 | 의미 |
|---|---|
| 200 | 성공 |
| 301/302 | 리다이렉트 |
| 400 | 잘못된 요청 |
| 401 | 인증 필요 |
| 403 | 접근 금지 |
| 404 | 경로 없음 |
| 413 | 요청 크기 초과 |
| 499 | 클라이언트가 연결 끊음 |
| 502 | upstream 서버 오류 |
| 504 | upstream timeout |
사용자
↓
Nginx 정상
↓
백엔드 연결 실패
↓
502 Bad Gateway
백엔드 앱이 꺼져 있음
백엔드 포트가 다름
proxy_pass 주소가 틀림
백엔드가 crash 반복
방화벽/네트워크 문제
Docker 컨테이너 이름/포트 오류
sudo systemctl status nginx
sudo tail -f /var/log/nginx/error.log
curl http://localhost:3000/health
pm2 status
docker compose ps
docker compose logs -f backend
curl localhost:3000 실패:
백엔드 앱 문제
curl localhost:3000 성공:
Nginx proxy 설정 문제 가능
Nginx error.log에 connection refused:
백엔드 포트/실행 상태 문제
server {
listen 443 ssl;
server_name api.example.com;
client_max_body_size 20M;
location / {
proxy_pass http://localhost:3000;
}
}
Nginx 제한
백엔드 body parser 제한
파일 업로드 라이브러리 제한
S3 presigned upload 구조 여부
location / {
proxy_pass http://localhost:3000;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
timeout을 무작정 늘리는 것은 해결책이 아님
느린 API 원인 분석 필요
대용량 작업은 비동기 Job으로 분리
엑셀 Export는 ExportJob 구조 권장
location /socket.io/ {
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_set_header X-Forwarded-Proto $scheme;
}
실시간 상담 알림
관리자 주문 상태 실시간 반영
작업 진행률 표시
ExportJob 진행 상태 실시간 표시
Nginx Upgrade header
백엔드 WebSocket path
프론트 WebSocket URL
HTTPS 환경에서는 wss 사용
CORS 설정
로드밸런서 idle timeout
wss://를 사용해야 합니다.ws://를 쓰면 브라우저에서 mixed content 문제가 날 수 있습니다.프론트:
https://www.example.com
API:
https://api.example.com
브라우저:
다른 origin으로 판단
권장:
백엔드 애플리케이션에서 명확히 처리
가능:
Nginx에서 header 추가
주의:
둘 다 중복 처리하면 꼬일 수 있음
app.enableCors({
origin: ['https://www.example.com', 'https://admin.example.com'],
credentials: true,
});
credentials: true 사용 시 origin: "*" 불가
쿠키 기반 인증이면 SameSite/Secure 설정 확인
OPTIONS preflight 처리 필요
로컬 개발 origin도 별도 허용
Secure 쿠키는 HTTPS에서만 전송됩니다.httpOnly:
JavaScript에서 접근 불가
secure:
HTTPS에서만 전송
sameSite:
cross-site 요청에서 쿠키 전송 기준
domain:
쿠키 적용 도메인
path:
쿠키 적용 경로
운영:
httpOnly true
secure true
sameSite 정책 명확히
HTTPS 필수
로컬:
secure false가 필요할 수 있음
localhost 기준 별도 설정
Nginx가 HTTPS 종료
↓
백엔드로는 HTTP 요청처럼 보일 수 있음
↓
X-Forwarded-Proto로 원래 HTTPS 요청임을 전달
고객:
https://www.example.com
관리자:
https://admin.example.com
API:
https://api.example.com
장점:
역할이 명확함
배포 분리 쉬움
CORS 정책 명확
관리자와 고객 분리 쉬움
단점:
CORS 설정 필요
쿠키 domain/sameSite 고려 필요
인증 정책 복잡 가능
프론트:
https://example.com
API:
https://example.com/api
장점:
CORS 부담 적음
쿠키 처리 단순
사용자 입장에서 도메인 단순
단점:
Nginx location 설정 중요
프론트 라우팅과 API 경로 충돌 주의
배포 분리 복잡 가능
/api 경로 Reverse Proxy 예시/api 경로만 백엔드로 넘길 수 있습니다.server {
listen 443 ssl;
server_name example.com;
root /var/www/frontend/dist;
index index.html;
location /api/ {
proxy_pass http://localhost:3000/api/;
proxy_http_version 1.1;
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;
}
location / {
try_files $uri $uri/ /index.html;
}
}
proxy_pass 뒤 slash 여부 주의
프론트 라우팅과 /api 경로 충돌 방지
백엔드 global prefix와 맞추기
SPA fallback이 API 요청을 먹지 않게 하기
/api 요청은 백엔드로 가야 하고, 프론트 라우팅 fallback으로 빠지면 안 됩니다.server {
listen 80;
server_name example.com;
root /var/www/frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
사용자 /products/1 직접 접속
↓
실제 파일 없음
↓
Nginx가 index.html 반환
↓
React Router가 /products/1 처리
index.html 캐시 길게 잡지 않기
assets 캐시 전략 분리
배포 시 이전 파일 정리 기준 필요
CloudFront보다 글로벌 캐시 약함
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1024;
HTML
CSS
JavaScript
JSON
SVG
XML
JPG
PNG
WebP
AVIF
ZIP
PDF
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
HTTPS가 완전히 안정된 뒤 적용
includeSubDomains는 모든 서브도메인 HTTPS 준비 필요
잘못 적용하면 HTTP 접속 복구가 어려울 수 있음
외부 공개:
80
443
외부 비공개:
3000
5432
6379
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 22
sudo ufw enable
sudo ufw status
PostgreSQL 5432 외부 공개 금지
Redis 6379 외부 공개 금지
Node 앱 3000 외부 직접 공개 지양
관리자용 내부 도구 포트 공개 금지
Client
↓
Nginx :443
↓
PM2로 실행 중인 NestJS :3000
pm2 status
pm2 logs
pm2 restart togethermall-api
Nginx:
host에 설치
Backend:
Docker container, host port 3000으로 노출
proxy_pass:
http://localhost:3000
Nginx:
Docker container
Backend:
Docker container
proxy_pass:
http://backend:3000
Nginx가 어디에서 실행되는지에 따라 proxy_pass host가 달라짐
호스트 Nginx는 localhost:3000
컨테이너 Nginx는 service name:3000
포트 publish 여부 확인
Docker network 확인
localhost가 의미하는 대상이 달라집니다.server_name이 실제 도메인과 맞는가?proxy_pass 대상 포트가 실제 백엔드와 맞는가?X-Forwarded-* header가 설정되어 있는가?client_max_body_size가 적절한가?try_files 설정이 있는가?certbot renew --dry-run이 성공하는가?https://로 호출되는가?wss://로 연결되는가?curl localhost:3000/health가 성공하는가?Ubuntu 서버에서 Nginx를 Reverse Proxy로 사용해 NestJS API를 운영하고 있어.
상황:
1. 도메인은 api.example.com
2. Nginx는 host에 설치되어 있음
3. NestJS는 PM2로 localhost:3000에서 실행 중
4. HTTPS는 Let's Encrypt + Certbot 사용
5. 브라우저에서 https://api.example.com 접속 시 502 Bad Gateway가 발생함
6. curl http://localhost:3000/health 결과는 아래와 같음
7. sudo nginx -t 결과는 아래와 같음
8. /var/log/nginx/error.log 최근 로그는 아래와 같음
아래 정보를 기준으로 원인을 좁히고 확인 순서를 알려줘.
답변은:
- 가장 가능성 높은 원인
- 확인 명령어
- Nginx 설정 수정 후보
- 백엔드/PM2 확인 항목
- 재발 방지 체크리스트
순서로 정리해줘.
curl localhost:3000으로 backend 상태를 먼저 확인하는가?nginx -t 후 reload하라고 하는가?sudo nginx -t로 문법을 확인하고 sudo systemctl reload nginx로 반영하는 순서가 안전합니다.proxy_pass, PM2/Docker 로그를 먼저 확인해야 합니다.client_max_body_size뿐 아니라 백엔드 body parser 제한도 함께 확인해야 합니다.Upgrade, Connection header를 설정해야 하며, HTTPS 환경에서는 wss:// 연결을 사용해야 합니다./api 경로로 나눌지는 CORS, 쿠키, 배포 구조를 고려해 선택해야 합니다.nginx -t, backend health check, error log를 함께 제공해야 정확한 원인 분석이 가능합니다.