업무 자동화를 남의 SaaS에 맡기지 않고 내 서버에 직접 올리면, 월 구독료 대신 삽질을 지불하게 된다. 나는 지난 몇 달간 그 값을 치렀다. 이 글은 그 과정에서 따로따로 써 둔 기록들을 실제로 밟아야 하는 순서대로 다시 꿴 지도다.
각 단계마다 그때 무엇에 걸렸는지 한 문단으로 요약하고, 상세한 진단 과정은 해당 글로 연결했다. 처음 구축하는 분은 위에서부터 읽고, 특정 단계에서 막힌 분은 그 항목만 펼쳐 보면 된다.

전체 그림
[Telegram / 스케줄러] <- 입구
|
[ n8n 워크플로 ] <- 자동화 엔진
|
+------+------+
| |
[외부 API] [ LLM ] <- 처리
|
클라우드 API 또는 로컬 GPU 서버
-------------------------------------
Oracle Cloud Free Tier + Docker + Nginx/TLS <- 바닥
바닥부터 올라간다. 순서를 건너뛰면 대개 위층에서 원인을 못 찾는 문제로 돌아온다.
1단계. 서버 — 왜 무료 티어로 충분한가
시작할 때 첫 갈림길은 n8n Cloud를 쓸 것인가, 직접 호스팅할 것인가다. 나는 직접 호스팅을 골랐고, 그 비교와 선택 이유는 첫 구축기에 정리해 뒀다. 결론만 말하면 개인이 쓰는 규모에서는 Oracle Cloud Always Free 인스턴스로 충분하다.
→ 자세한 과정: n8n 워크플로 자동화로 주간 업무 리포트 만들기 — Cloud vs 셀프호스팅 비교와 인프라 선택
다만 무료 티어는 디스크가 넉넉하지 않다. 운영 몇 달이 지나면 조용히 차오르는데, 범인은 대개 애플리케이션이 아니라 Docker 빌드 캐시다. 실제로 76%까지 찼던 디스크에서 21GB를 회수했고, 가장 안전하고 효과가 큰 한 줄은 이것이었다.
docker builder prune
→ 자세한 과정: Oracle Cloud Free Tier 디스크 21GB 회수기 — Docker 빌드 캐시가 범인이었다
2단계. 도메인과 HTTPS — 선택이 아니라 전제
n8n의 Telegram 트리거를 webhook 모드로 쓰려면 외부에서 닿는 도메인과 유효한 TLS 인증서가 필수다. 자체 서명 인증서로는 Telegram이 webhook을 받아주지 않는다. 즉 HTTPS는 나중에 붙이는 장식이 아니라, 2단계에서 반드시 끝내야 하는 전제 조건이다.
여기서 흔히 걸리는 곳은 인증서 발급이 아니라 Nginx 설정이다. 기존 default_server가 모든 트래픽을 먼저 잡고 있으면 새 도메인이 엉뚱한 사이트로 간다. 한 IP에 사이트 두 개를 충돌 없이 공존시키는 구성과, certbot 발급 후 손으로 정리해야 하는 부분을 정리해 뒀다.
→ 자세한 과정: Nginx + Let’s Encrypt로 n8n에 도메인·TLS 붙이기 — 한 IP에 두 사이트 공존시키기
3단계. 워크플로를 만들고 — 켜기
워크플로 정의를 JSON 파일로 관리하면 버전 관리가 편하다. 문제는 켜는 단계다. public API의 /activate 엔드포인트가 Bad request만 뱉고 아무 설명도 주지 않는 상황을 만났다.
원인은 import된 워크플로에 activeVersionId가 비어 있는 것이었고, CLI의 publish:workflow와 내부 REST를 차례로 시도한 끝에 답은 의외로 단순했다. UI 토글 한 번이 publish·activate·setWebhook을 한꺼번에 처리한다. 자동화 스크립트를 짜다가 여기서 시간을 버리지 않길 바란다.
→ 자세한 과정: n8n public API의 /activate가 ‘Bad request’만 뱉을 때 — UI 토글이 답이었다
4단계. 입구 만들기 — Telegram 봇
스케줄 실행만으로는 반쪽이다. 내가 원할 때 말을 걸 수 있는 입구가 있어야 실제로 쓰게 된다. Telegram 봇에 URL을 던지면 요약해서 답장하는 워크플로를 만들었고, 트리거는 polling이 아니라 webhook 모드를 썼다(그래서 2단계의 TLS가 필요했다).
→ 자세한 과정: n8n + Telegram + LLM으로 만든 ‘URL 던지면 요약 답장’ 봇
그리고 Telegram 봇을 여러 곳에서 쓰기 시작하면 거의 반드시 만나는 에러가 있다. 409 Conflict — 같은 토큰으로 getUpdates를 두 곳에서 돌릴 때 난다. 내 경우 진범은 잊고 있던 systemd 유닛의 Environment=TELEGRAM_BOT_TOKEN=… 한 줄이었다. 애플리케이션 설정을 아무리 꺼도 막히지 않고, 환경변수 자체를 끊어야 해결된다.
→ 자세한 과정: Telegram 봇 polling 409 충돌 디버깅 — 누가 몰래 getUpdates를 돌리고 있었다
5단계. 외부 API의 벽 — 서버 IP는 노트북 IP와 다르다
자동화가 무너지는 가장 허무한 방식은 같은 코드가 노트북에서는 되고 서버에서는 안 되는 상황이다. YouTube 자막을 받아 요약하는 흐름에서 실제로 겪었고, 원인은 코드가 아니라 서버 공인 IP가 봇으로 분류돼 차단된 것이었다.
클라우드 데이터센터 IP는 이미 수많은 크롤러가 써 온 주소다. 개인이 새로 받은 IP라도 평판은 물려받는다. 차단 여부를 확인하는 방법, 그때도 살아 있는 우회 경로(oEmbed·watch 페이지), 그리고 근본 대책 세 가지(주거용 프록시 / Whisper STT 사이드카 / 외부 자막 API)를 비교해 뒀다.
→ 자세한 과정: VM의 공인 IP가 YouTube 봇 차단을 당했다 — 진단과 우회 옵션
교훈: 외부 서비스를 자동으로 긁는 워크플로에는 반드시 폴백 경로를 설계해 둔다. 위 봇도 자막을 못 받으면 영상 설명(description)으로 대체하도록 만들어 뒀다.
6단계. LLM 붙이기 — 클라우드 API인가, 내 GPU인가
자동화의 마지막 조각은 판단과 요약을 맡을 모델이다. 선택지는 두 갈래다.
가. 클라우드 API — 빠르게 시작하기
앞의 요약 봇은 gpt-4o-mini 한 번 호출로 끝낸다. 토큰 단가가 낮은 모델이면 개인 용도에서 비용은 사실상 신경 쓰이지 않는 수준이고, 서버 자원도 쓰지 않는다. 다만 어떤 모델을 고를지는 계속 바뀐다 — 오픈소스 모델이 매주 쏟아지고, 그중 무료로 쓸 수 있는 것도 적지 않다.
→ 자세한 과정: 오픈소스 LLM 신작 4종, 일주일 만에 정체가 드러났다 — 스펙과 가격 정리
나. 로컬 GPU — 데이터를 밖으로 내보내지 않기
사내 데이터를 다루거나 호출량이 많아지면 직접 모델을 띄우게 된다. 이때 가장 먼저 부딪히는 건 체감 속도다. RTX A6000에 Qwen2.5-7B를 올렸더니 첫 호출이 12.6초 걸렸고, GPU 경합을 의심했지만 실측 사용률은 0%였다.
답은 나눗셈 하나에 있었다. 초당 최대 토큰 수 = 메모리 대역폭 ÷ 모델 크기. 이 계산으로 내 장비의 상한을 먼저 구하면, 튜닝할 문제인지 하드웨어 한계인지가 바로 갈린다.
→ 자세한 과정: 사내 GPU에 Qwen2.5-7B를 올리고 배운 것 — 느린 게 아니라 대역폭의 한계였다
7단계. 운영 — 만들고 끝이 아니다
자동화 시스템은 세워 둔 뒤 조용히 썩는다. 실제로 관리 부담이 되는 것은 두 가지였다.
- 디스크 — Docker 빌드 캐시와 오래된 이미지가 계속 쌓인다. 정기 정비를 재부팅 일정에 묶어 두는 편이 낫다.
- 실행 기록 — n8n은 모든 실행 이력을 DB에 남긴다. 보존 기간을 정해 두지 않으면 DB가 혼자 커진다.
둘 다 발견이 늦을수록 회수 작업이 커진다. 월 1회라도 docker system df와 DB 용량을 확인하는 습관을 권한다.
이 순서대로 밟으면 된다
- ① 서버 확보 — Oracle Cloud Always Free, 셀프호스팅 결정 (구축기)
- ② 도메인 + TLS — webhook을 쓰려면 먼저 (Nginx·certbot)
- ③ 워크플로 작성과 활성화 — UI 토글 (activate 함정)
- ④ 입구 연결 — Telegram 봇 (요약 봇 / 409 충돌)
- ⑤ 외부 API 폴백 설계 (IP 차단 대응)
- ⑥ LLM 연결 — 클라우드(모델 고르기) 또는 로컬 GPU(속도 계산)
- ⑦ 정기 정비 — 디스크와 실행 기록 (디스크 회수)
이 스택으로 지금 매일 돌고 있는 것들이 있다. 아침마다 새 모델을 감시해 알려주고, 평일 장 시간에 시세를 확인하고, 던져 둔 URL을 요약해 답장한다. 하나하나는 별것 아니지만, 구독료 없이 내 서버 안에서 돈다는 점이 계속 쓰게 만드는 이유다.
각 단계에서 막히면 해당 글에 그날의 삽질이 그대로 적혀 있다. 같은 곳에서 시간을 버리지 않으셨으면 한다.