← Lab
n8n 운영 중인 1인 EC2 서버에 Next.js 포트폴리오 안전하게 올리기
배포2026.09.14
- Next.js
- n8n
- Nginx
- PM2
- AWS
- EC2
- Cloudflare
1. 단일 도메인 공유 구조 설계 (서브패스 vs 서브도메인)
문제
- 기존에 보유 중인 단일 도메인(
daegom.dev)이 셀프호스팅 n8n 서비스에 독점 연결되어 있어, 새로 제작한 Next.js 포트폴리오를 올릴 공간이 부족했습니다.
server {
server_name daegom.dev;
location / {
proxy_pass http://localhost:5678; # 전부 n8n으로
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/daegom.dev/fullchain.pem;
...
}
검토 및 판단
- 서브패스 (
daegom.dev/n8n): → 단일 도메인 관리는 편하지만, n8n 내부 API 경로 변경(N8N_PATH)에 따른 베이스 URL이 깨질 가능성이 존재할 것이라고 판단했습니다. - 서브도메인 (
n8n.daegom.dev) → n8n이 루트 경로를 전제로 설계되어 있어 라우터 구조 변경 없이 안전하게 분리 가능하며, Nginxserver블록으로 독립성이 보장된다고 판단했습니다.
결정
- 기존 n8n을
n8n.daegom.dev서브도메인으로 이동시키고, 루트(daegom.dev)에 포트폴리오를 새로 배포하는 구조를 채택했습니다.
2. 저사양 EC2 배포 환경 구축 (로컬 빌드 vs 서버 빌드)
문제
- Next.js 애플리케이션에 적용된 Route 특성상 서버 사이드 렌더링이 필수적이어서 정적 파일 호스팅이 불가능하고 항상 실행 중인 Node.js 프로세스가 필요했습니다.
- 그러나 운영 중인 EC2의 가용 메모리는 약 500MB 수준에 불과했습니다.
$ free -h
total used free shared buff/cache available
Mem: 1.9Gi 1.3Gi 115Mi 23Mi 640Mi 528Mi
Swap: 2.0Gi 361Mi 1.6Gi
원인 및 위험성
- Next.js 빌드는 순간적으로 1GB 이상의 RAM을 소비하므로, 서버 직접 빌드 시 메모리 부족으로 인한 기존 n8n 컨테이너 강제 종료 위험이 존재했습니다.
- 디스크 기반 Swap 공간 확장은 빌드 타임아웃 및 I/O 병목을 유발할 수 있었습니다.
결정 및 트레이드오프
- 로컬 맥북 빌드 후 Artifact 전송(
rsync) 방식을 채택했습니다. - 환경 차이로 인한 네이티브 모듈 충돌 방지를 위해
node_modules와.git은 전송 대상에서 제외하고, EC2에서npm ci --omit=dev로 서버 측 실행 환경만 구동했습니다. - PM2 프로세스 매니저를 도입하여 백그라운드 구동 및 서버 재부팅 시 자동 재시작을 보장했습니다.
# 로컬에서 빌드 및 전송
npm ci
npm run build
rsync -avz --exclude node_modules --exclude .git \
-e "ssh -i {키 경로}" \
./ ubuntu@daegom.dev:~/daegom-portfolio/
# EC2에서 실행 환경 구축
cd ~/daegom-portfolio
npm ci --omit=dev
# Next.js 16 요구 사양(Node 20.9+)에 맞춰 nvm으로 Node 20 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 20
# PM2로 백그라운드 상시 구동 및 자동 재시작 설정
npm install -g pm2
pm2 start ecosystem.config.js
pm2 save
pm2 startup
3. Nginx 역프록시 분리 및 SSL 인증서 관리
문제
- 단일 Nginx 설정을 두 개의 서브도메인 기반 라우팅으로 분리하고, 기존 인증서와의 충돌 없이 HTTPS를 재설정해야 했습니다.
해결 및 트레이드오프
- Cloudflare DNS:
n8nA 레코드를 추가하되, Let's Encrypt 소유권 검증 시 IP가 가려져 발급이 실패하는 현상을 방지하고자 Proxy를 끄고DNS Only로 설정했습니다.
# .env 수정 (n8n)
N8N_HOST=n8n.daegom.dev
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.daegom.dev/
docker compose down && docker compose up -d
- Nginx 분리: 5678 포트(n8n)와 3000 포트(Next.js)를 각각의
server_name에 매핑하는 독립적인sites-available블록으로 재구성했습니다.
# /etc/nginx/sites-available/daegom-portfolio
server {
listen 80;
server_name daegom.dev;
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
sudo ln -s /etc/nginx/sites-available/daegom-portfolio /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
- 인증서 전략: 신규 서브도메인(
n8n.daegom.dev)은 새 인증서를 발급받고, 기존 도메인(daegom.dev)은 불필요한 Rate Limit 소진을 피하기 위해 기존 인증서 재설치(Reinstall) 옵션을 선택했습니다.
sudo certbot --nginx -d n8n.daegom.dev
sudo certbot --nginx -d daegom.dev
# 1: Attempt to reinstall this existing certificate ← 선택
# 2: Renew & replace the certificate (may be subject to CA rate limits)
🚀 결과
- 추가 도메인 구매 비용 없이 단일 EC2 리소스 범위 내에서 2개 서비스를 안정적으로 분리 및 배포했습니다.
- 로컬 빌드 분리 전략을 통해 저사양 서버의 운영 장애 위험을 사전에 차단할 수 있었습니다.