🤔 "n8n이 살아있는 건지, 워크플로우가 제대로 도는 건지 어떻게 알아?"
여러 n8n 워크플로우를 만들어서 퍼블리쉬하고, HWP MD 컨버터와 AI Taro도 만들어서 운영하다 보니 정작 잘 돌아가고 있는지 확인하기가 어려워졌다.
n8n 서버가 뜨면 다 괜찮은 줄 알고 있었는데.
어느 날 확인해 봤더니 중요한 워크플로우가 이틀째 조용히 에러를 뿜고 있었던 거다.
서버는 멀쩡했으니까 당연히 몰랐던 거고. 그때 결심했다.
직접 모니터링 대시보드를 만들어야겠다고.

✅ 이 글의 핵심 요약
- n8n + 웹앱 + DB 상태를 한 화면에서 실시간 확인 가능한 대시보드를 직접 만들다
- HTTP 헬스체크 + TCP 체크 + n8n 워크플로우 레벨 API 모니터링까지 구현
- 이상 감지 시 Telegram으로 즉시 알림, 어떤 워크플로우가 문제인지 이름 알려줌
- 스택: Node.js + Express + 바닐라 JS + Docker Compose 외부 의존성 최소화
📋 목차
- 왜 직접 만들었나 — 문제 상황
- 전체 스택 한눈에 보기
- 웹앱 HTTP 헬스체크 구현
- n8n 워크플로우 레벨 모니터링 (핵심!)
- 대시보드 UI 설계 포인트
- Docker Compose로 배포하기
- 운영 후기 — 실제로 잡아낸 것들
- FAQ
1️⃣ 왜 직접 만들었나 — 문제 상황
집에서 맥미니랑 집 PC에 n8n, PostgreSQL을 운영하고 있고, 외부에는 Netlify랑 Render에 웹앱도 몇 개 올려뒀다. 서비스가 늘어나다 보니 이 상황이 반복됐다:
- 🔴 워크플로우가 이틀째 에러인데 서버는 멀쩡해서 몰랐음
- 🔴 Render 무료 플랜 콜드 스타트로 응답 없음 → 실서비스에서 오류
- 🔴 PostgreSQL이 안 죽었는지 수동 확인하는 게 귀찮아짐
- 🔴 Uptime Robot 같은 외부 서비스는 내부망 서비스 체크 불가
결국 "n8n 워크플로우 레벨까지 체크하는 모니터링"은 범용 도구로는 못 하더라.
그래서 직접 만들기로 했다. 🛠️
2️⃣ 전체 스택 한눈에 보기
의존성을 최대한 줄였다. 모니터링 도구 자체가 의존성 때문에 죽으면 안 되니까 😅
| 역할 | 기술 | 선택 이유 |
|---|---|---|
| 백엔드 | Node.js + Express | 외부 라이브러리 Zero, 순수 http/https/net 모듈만 |
| 프론트엔드 | 바닐라 HTML/CSS/JS | 프레임워크 없이 빠르게, 가볍게 |
| 배포 | Docker + Docker Compose | 한 줄로 올리고 내리기, 재시작 정책 자동화 |
| 알림 | Telegram Bot API | 무료 + 모바일 즉시 수신, 워크플로우 이름까지 전송 |
현재 모니터링 중인 서비스는 총 8개다. 로컬 인프라(n8n 2대, PostgreSQL), Netlify 정적 앱 2개, Render 백엔드 1개가 섞여 있다.
3️⃣ 웹앱 HTTP 헬스체크 구현
웹앱은 HTTP 요청을 날려서 상태 코드가 400 미만이면 정상 처리한다. 포인트는 done 플래그로 중복 resolve를 막는 것이다. error 이벤트랑 timeout 이벤트가 동시에 뜨는 경우가 있다.
// HTTP 헬스체크 핵심 코드 function checkHttp(url, timeout = 5000) { return new Promise(resolve => { let done = false; const finish = (r) => { if (!done) { done = true; resolve(r); } }; const start = Date.now(); const mod = url.startsWith('https') ? https : http; const req = mod.get(url, { timeout }, res => { res.resume(); finish({ ok: res.statusCode < 400, ping: Date.now() - start }); }); req.on('error', () => finish({ ok: false, ping: null })); req.on('timeout', () => { req.destroy(); finish({ ok: false, ping: null }); }); }); }
⚠️ Render 콜드 스타트 주의!
Render 무료 플랜은 트래픽이 없으면 컨테이너를 종료한다. 첫 요청에서 30~50초가 걸리는 경우가 생겨서, Render 서비스만 타임아웃을 15초로 따로 설정했다. 5초로 맞추면 항상 DOWN으로 찍혀서 쓸모없는 알림이 폭탄처럼 날아올 수 있다 😅 (그래서 지금은 14달러 내고 유료 서비스를 쓴다)
PostgreSQL처럼 HTTP가 아닌 서비스는 TCP 소켓 체크로 처리한다. 소켓 연결이 성공하면 바로 끊고 정상 판단. 구조는 HTTP 체크랑 거의 동일하다.
4️⃣ n8n 워크플로우 레벨 모니터링 (이게 핵심!)
n8n 서버가 살아있어도 워크플로우는 조용히 실패하고 있을 수 있다.
이걸 잡으려고 n8n API를 두 단계로 호출한다.
🔍 1차: 실패 건수 조회
/api/v1/executions?status=error
24시간 이내 실패가 있으면 Telegram 1차 경보 발송
📋 2차: 워크플로우별 상세
워크플로우 목록 + 실행 100건을 병렬 호출해서 어떤 워크플로우가 문제인지 이름까지 알림에 포함
// 워크플로우별 최신 실행 상태 매핑 function checkN8nWorkflows(baseUrl, apiKey) { return Promise.all([ apiGet('/api/v1/workflows'), // 전체 워크플로우 목록 apiGet('/api/v1/executions?limit=100'), // 최근 실행 100건 ]).then(([workflowsRes, executionsRes]) => { const latestByWf = {}; for (const exec of executionsRes.data) { if (!latestByWf[exec.workflowId]) latestByWf[exec.workflowId] = exec; } return workflowsRes.data .filter(wf => wf.active) // 활성화된 것만 .map(wf => ({ name: wf.name, lastStatus: latestByWf[wf.id]?.status || 'unknown', lastRunAt: latestByWf[wf.id]?.startedAt || null, })); }); }
실패한 워크플로우가 감지되면 Telegram으로 이름을 포함해서 알림을 쏜다. workflowAlerted 플래그로 중복 알림은 막고, 문제가 해소되면 리셋해서 다음 실패 때 다시 알림이 가도록 처리했다.
5️⃣ 대시보드 UI 설계 포인트
UI에서 신경 쓴 건 딱 하나다. "문제가 있을 때만 눈에 띄게".
멀쩡할 때 정보가 넘치면 정작 중요한 게 묻혀버리기 때문에.
서비스 카드 — 상태 도트 + 응답 속도
초록/빨강 도트, 서비스 이름, URL, 응답 시간(ms), 상태 태그로 구성. 한눈에 파악 가능.
n8n 워크플로우 목록 — 자동 펼침 로직
기본은 접힌 상태. 실패 중인 워크플로우가 있을 때만 자동으로 펼쳐져. 이게 핵심 UX. 정상일 땐 깔끔하게, 문제 있을 때만 튀어나와.
상대시간 표시 — "37분 전", "어제"
ISO 타임스탬프를 한국어 상대시간으로 변환. "2025-04-16T03:22:11Z" 같은 걸 보여주는 것보다 훨씬 직관적이다.
이벤트 로그 — UP↔DOWN 전환 기록
클라이언트 단에서 상태 변경을 감지해서 최근 20건을 로그로 남겨. 잠깐 자리 비운 사이에 뻑났다가 복구된 흔적을 확인할 수 있다.
6️⃣ Docker Compose로 배포하기
배포는 Docker Compose 한 줄로 끝난다. 한 가지 중요한 설정이 있다:
services:
monitor:
build: ./backend
ports:
- "3900:3900"
env_file: .env
extra_hosts:
- "host.docker.internal:host-gateway" # ← 이게 핵심!
restart: unless-stopped
volumes:
- ./frontend:/app/public
💡 extra_hosts 설정이 왜 필요해?
Docker 컨테이너 안에서 맥미니의 로컬 서비스(n8n, PostgreSQL)에 접근하려면 host.docker.internal로 호스트 IP를 가리켜야 한다.
이 설정 없으면 로컬 서비스에 아예 접근이 안 된다. 많이 해본 사람들은 기본적으로 안다.
환경변수는 .env 파일로 분리해서 관리한다.
n8n API 키는 n8n 설정 → API → 키 생성에서 만들 수 있다.
# .env 파일 예시 TELEGRAM_BOT_TOKEN=your_bot_token TELEGRAM_CHAT_ID=your_chat_id MACMINI_N8N_API_KEY=your_api_key HOMEPC_N8N_API_KEY=your_api_key
7️⃣ 운영 후기 — 실제로 잡아낸 것들
| 사례 | 감지 방법 | 결과 |
|---|---|---|
| 특정 워크플로우 48시간 에러 | n8n API 2차 체크 | 즉시 Telegram 알림으로 확인 |
| Render 콜드 스타트 30초 | 타임아웃 15초 별도 설정 | 불필요한 알림 폭탄 방지 |
| PostgreSQL 포트 응답 없음 | TCP 소켓 체크 | DB 프로세스 죽은 거 30초 내 발견 |
직접 만든 걸 쓰면서 가장 좋은 점은 내 인프라에 딱 맞게 커스텀할 수 있다는 거다.
범용 도구로는 "n8n 서버 살아있음"까지만 알 수 있는데, 이 대시보드는 "어떤 워크플로우가 몇 분 전에 실패했는지"까지 보여준다.
📌 한 가지 한계점
모니터링 도구가 올라간 맥미니 자체가 죽으면 아무것도 모른다는 단점이 있다. 이건 외부에서 헬스핑을 받는 방식으로 보완할 수 있는데, 아직은 그 정도까지 필요하지 않아서 다음으로 미뤄뒀다 😅
❓ FAQ
Q. Uptime Robot이나 Better Uptime 같은 외부 서비스 쓰면 안 되나요?
A. 외부 URL이 있는 서비스는 쓸 수 있어. 근데 로컬 내부망 서비스(n8n, PostgreSQL)는 체크 자체가 불가능하다. 그리고 n8n 워크플로우 레벨 상태 확인은 직접 구현 말고는 방법이 없다.
Q. 폴링 주기를 60초로 한 이유가 있나요?
A. n8n API를 너무 자주 호출하면 n8n 서버에 부하가 올 수 있다. 모니터링이 오히려 운영에 영향을 줄 수 있으니 60초로 여유 있게 잡았다. 빠른 응답이 필요하면 30초로 줄여도 괜찮다.
Q. 상태 히스토리는 저장 안 하나요?
A. 현재는 메모리에만 저장한다. 컨테이너가 재시작되면 초기화되고. 히스토리 추적이 필요하다면 SQLite나 InfluxDB 같은 경량 DB를 붙이면 되는데, 지금 규모에서는 오버엔지니어링이라 안 했다.
Q. n8n API 키는 어떻게 발급하나요?
A. n8n 대시보드 → 우측 상단 메뉴 → Settings → n8n API → Create an API key 순서로 발급할 수 있다. 발급된 키를 .env 파일에 넣으면 된다.
🚀 직접 만드는 게 결국 더 강해
범용 도구로는 절대 못 잡는 문제들이 있어.
n8n 워크플로우 레벨까지 들여다보는 모니터링, 한번 만들어두면 진짜 든든해 💪






댓글 쓰기