n8n 의 Telegram 트리거를 webhook 모드로 쓰려면 외부에서 닿을 수 있는 도메인 + TLS 가 필수다. 자체 서명 인증서로는 Telegram 서버가 webhook 등록을 거절한다.
문제는 이 VM 에 이미 다른 사이트(RAG 채팅 UI) 가 돌고 있다는 것. 같은 80/443 포트를 두 사이트가 공유해야 하고,
기존 RAG 사이트는 IP 직접 접근으로 들어오는 트래픽을 그대로 받고 있었다. 이 글은 그 위에
n8n.speech.pe.kr 을 충돌 없이 얹은 과정이다.
출발 상태 — default_server 가 모든 트래픽을 잡고 있었다
기존 nginx 설정은 이런 구조였다.
# /etc/nginx/conf.d/rag.conf
server {
listen 80 default_server;
server_name _;
# RAG UI
}
default_server + server_name _ 조합 때문에 IP 든 도메인이든 모든 HTTP 요청을 이 블록이 잡는다. 여기에 그냥 server_name n8n.speech.pe.kr 블록을 하나 더 추가하면, 이름으로 매칭되는
요청은 새 블록으로, 그 외(IP 직접 접근 포함)는 기존 default 블록으로 자연스럽게
갈라진다.
핵심은 새 conf 에서 default_server 키워드를 쓰지 않는 것이다. 같은 포트의
default_server 가 두 개면 nginx 가 부팅을 거부한다.
n8n 리버스 프록시 conf
n8n 컨테이너는 127.0.0.1:5678 에 떠 있다. 그 앞에 nginx 를 세우는 conf.
# /etc/nginx/conf.d/n8n.conf
server {
listen 80;
server_name n8n.speech.pe.kr;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name n8n.speech.pe.kr;
ssl_certificate /etc/letsencrypt/live/n8n.speech.pe.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.speech.pe.kr/privkey.pem;
client_max_body_size 64m; # n8n 워크플로 JSON import 용
proxy_read_timeout 300s; # 긴 실행 대비
proxy_http_version 1.1;
location / {
proxy_pass http://127.0.0.1:5678;
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;
# WebSocket — n8n 에디터 실시간 업데이트에 필요
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
세 가지 디테일이 있다.
client_max_body_size 64m— n8n 의 워크플로 JSON 을 UI 로 import 할 때 수 MB 가
쉽게 넘는다. 기본값 1m 에 막힌다.proxy_read_timeout 300s— 외부 API 호출이 긴 워크플로는 60초로 끊긴다.Upgrade/Connection: upgrade— n8n 에디터가 WebSocket 으로 상태를
받기 때문에 빠뜨리면 UI 가 “연결 끊김” 으로 깜빡인다.
certbot 한 줄로 발급 — 다만 conf 는 손으로 정리
certbot 의 --nginx 플러그인을 쓰면 인증서 발급과 nginx conf 의 SSL 라인 추가를 한 번에 한다.
sudo certbot --nginx -d n8n.speech.pe.kr
DNS 가 이 VM 의 공인 IP 를 가리키고 있다면 1분 안에 끝난다. 단, certbot 이 conf 에 자동으로 끼워넣는 #
managed by Certbot 주석과 if ($host = ...) 블록은 가독성을 해친다. 발급 끝난 직후 conf 를
손으로 깔끔하게 정리해두면 다음 사람(또는 미래의 나)이 읽기 쉽다.
갱신은 snap 의 systemd timer 가 알아서
certbot 을 snap 으로 설치하면 snap.certbot.renew.timer 가 자동 등록된다. 직접 cron 을 적을 필요가
없다. 실제 동작을 확인하려면 dry-run.
sudo certbot renew --dry-run --no-random-sleep-on-renew
--no-random-sleep-on-renew 를 빠뜨리면 비대화 모드에서도 최대 8분 sleep 이 걸린다. 검증할 때만 빼면 되고, 실제 timer 가 도는 백그라운드 갱신은 random sleep 이 있어야 Let’s Encrypt 서버에 부하가 몰리지 않는다.
충돌 없이 사이트 두 개 — 최종 모습
| 접근 경로 | 매칭되는 server 블록 | 응답 |
|---|---|---|
https://n8n.speech.pe.kr |
n8n.conf 의 443 블록 |
n8n 리버스 프록시 |
http://n8n.speech.pe.kr |
n8n.conf 의 80 블록 |
443 으로 301 |
http://<공인IP> |
rag.conf 의 default 블록 |
RAG UI |
| 도메인 매칭 실패 트래픽 | 동일 | 동일 |
같은 80/443 포트 위에서 두 사이트가 깔끔하게 공존한다.
교훈
server_name분리 하나만 잘 잡아도 한 호스트에 도메인 사이트 여러 개를 충돌 없이
올릴 수 있다.default_server는 한 포트에 단 하나. 새 도메인 conf 를 추가할 때는 이 키워드를
쓰지 말 것.- certbot snap 설치 가 가장 손이 덜 간다. cron 작성도, 갱신 스크립트도 없이 자동.
- n8n 같은 도구는 WebSocket / 큰 바디 / 긴 타임아웃 세 가지를 잊지 말 것. 안 하면 미묘하게
깨진다.
이 인프라 위에 Telegram 봇으로 YouTube URL 요약하는 워크플로 를
올렸고, 그 과정에서 activate API 의 함정 과 봇 polling 충돌 도 만났다. 다음 글들에서 차례로 풀어본다.
참고 자료