YunDaeHyeon
← 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이 루트 경로를 전제로 설계되어 있어 라우터 구조 변경 없이 안전하게 분리 가능하며, Nginx server 블록으로 독립성이 보장된다고 판단했습니다.

결정

  • 기존 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: n8n A 레코드를 추가하되, 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개 서비스를 안정적으로 분리 및 배포했습니다.
  • 로컬 빌드 분리 전략을 통해 저사양 서버의 운영 장애 위험을 사전에 차단할 수 있었습니다.