n8n 고급 활용
커스텀 노드 개발, 서브워크플로우 모듈화, Docker 운영 배포, 보안 강화, 성능 최적화, 모니터링까지 — n8n을 프로덕션 수준으로 운영하는 완전 가이드
서브워크플로우 & 모듈화
대규모 자동화 시스템을 구축하면 워크플로우가 복잡해집니다. 서브워크플로우(Sub-Workflow)는 공통 로직을 별도 워크플로우로 분리하고 재사용하는 모듈화 패턴입니다.
서브워크플로우 구조
서브워크플로우 모듈화: 메인 워크플로우에서 Execute Workflow 노드로 서브워크플로우 호출
서브워크플로우 만들기
# 서브워크플로우 구성 (별도 워크플로우)
# 1. 트리거: Execute Workflow Trigger
노드: Execute Workflow Trigger
# 이 노드가 있어야 서브워크플로우로 호출 가능
# 입력 파라미터를 $json으로 받음
↓
# 2. 처리 로직
Basic LLM Chain
User Message: {{ $json.prompt }}
↓
Code 노드 (결과 가공)
↓
# 3. 결과 반환
# 마지막 노드의 출력이 자동으로 호출자에게 반환됨
# 메인 워크플로우에서 서브워크플로우 호출
Execute Workflow 노드
Source: Database (워크플로우 이름/ID로 선택)
Workflow: "AI 처리 서브워크플로우"
# 데이터 전달 방법
Wait For Sub-Workflow: true # 완료까지 대기 (동기)
Input Data:
prompt: {{ $json.userPrompt }}
language: {{ $json.language }}
maxTokens: 2048
서브워크플로우 패턴
# 패턴 1: 공통 알림 서브워크플로우
# 모든 워크플로우에서 재사용
Input: { channel, message, severity, metadata }
Switch (severity)
"error" → PagerDuty + Slack #alerts
"warning" → Slack #monitoring
"info" → Slack #general + DB 로그
---
# 패턴 2: 데이터 검증 서브워크플로우
Input: { data, schema }
Code 노드 (JSON Schema 검증)
const Ajv = require('ajv');
const ajv = new Ajv();
const valid = ajv.validate($json.schema, $json.data);
if (!valid) {
throw new Error(`검증 실패: ${ajv.errorsText()}`);
}
return [{ json: { valid: true, data: $json.data } }];
---
# 패턴 3: Rate Limiter 서브워크플로우
Input: { key, maxRequests, windowSeconds }
Redis 노드 (GET 현재 카운트)
↓
IF 노드 (카운트 < maxRequests)
True → Redis INCR + EXPIRE → 통과
False → 에러 반환 (429 Too Many Requests)
에러 처리 & 안정성
에러 처리 전략
| 전략 | 적용 방법 | 사용 시나리오 |
|---|---|---|
| Continue on Fail | 노드 설정 → "Continue on Fail" ON | 에러 무시하고 계속 진행 (비중요 작업) |
| Error Workflow | Settings → Error Workflow 지정 | 워크플로우 실패 시 알림/복구 |
| Try/Catch 패턴 | Code 노드 + IF 노드 조합 | 노드별 세밀한 에러 처리 |
| 재시도 설정 | 노드 → Retry on Fail | 일시적 API 오류, 네트워크 불안정 |
에러 워크플로우 패턴
# 에러 워크플로우 구성
Error Trigger
↓
Code 노드 (에러 정보 파싱)
const { execution } = $json;
const errorInfo = {
workflowName: execution.workflowData.name,
workflowId: execution.workflowData.id,
executionId: execution.id,
errorNode: execution.lastNodeExecuted,
errorMessage: execution.data?.resultData?.error?.message || "Unknown",
errorStack: execution.data?.resultData?.error?.stack || "",
startedAt: execution.startedAt,
failedAt: new Date().toISOString(),
inputData: JSON.stringify(
execution.data?.resultData?.runData?.[execution.lastNodeExecuted]?.[0]?.data?.main?.[0]?.[0]?.json
).slice(0, 500), // 처음 500자만
};
return [{ json: errorInfo }];
↓
IF 노드 (심각도 분류)
Condition: {{ $json.errorMessage }} Contains "ECONNREFUSED"
→ "인프라 에러" (긴급)
Default → "일반 에러"
↓ (긴급)
PagerDuty 노드 (즉시 호출)
+ Slack #incidents 알림
↓ (일반)
Slack #errors 알림
+ PostgreSQL 에러 로그 저장
재시도 + 지수 백오프
// Code 노드 — 재시도 로직 구현
async function withRetry(fn, maxRetries = 3, baseDelay = 1000) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
if (attempt === maxRetries) break;
// 지수 백오프 + 지터
const delay = baseDelay * Math.pow(2, attempt) +
Math.random() * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// 사용 예시
const result = await withRetry(async () => {
const response = await $helpers.httpRequest({
method: 'POST',
url: 'https://api.anthropic.com/v1/messages',
body: { /* ... */ },
});
return response;
}, 3, 2000);
return [{ json: result }];
커스텀 노드 개발
기본 제공 노드로 해결되지 않는 경우, TypeScript로 커스텀 노드를 개발하여 n8n에 등록할 수 있습니다.
개발 환경 설정
# n8n 커스텀 노드 프로젝트 초기화
npx n8n-node-dev init
# 또는 수동 설정
mkdir n8n-nodes-mycustom
cd n8n-nodes-mycustom
npm init -y
npm install -D typescript \
@types/node \
n8n-workflow \
@n8n/n8n-node-dev-utils
# tsconfig.json
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"strict": true,
"outDir": "./dist",
"declaration": true,
"sourceMap": true
},
"include": ["nodes/**/*.ts", "credentials/**/*.ts"]
}
커스텀 노드 코드
// nodes/MyApiNode/MyApiNode.node.ts
import { IExecuteFunctions, INodeExecutionData, INodeType,
INodeTypeDescription, NodeOperationError } from 'n8n-workflow';
export class MyApiNode implements INodeType {
description: INodeTypeDescription = {
// 기본 정보
displayName: 'My Custom API',
name: 'myApiNode',
icon: 'file:my-icon.svg',
group: ['transform'],
version: 1,
description: 'My Custom API Node',
// 기본값
defaults: {
name: 'My API',
},
// 입출력 설정
inputs: ['main'],
outputs: ['main'],
// 자격증명
credentials: [
{
name: 'myApiCredentials',
required: true,
},
],
// 노드 파라미터 (UI 설정 필드)
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{ name: 'Get User', value: 'getUser' },
{ name: 'Create User', value: 'createUser' },
],
default: 'getUser',
},
{
displayName: 'User ID',
name: 'userId',
type: 'string',
required: true,
displayOptions: {
show: { operation: ['getUser'] },
},
default: '',
placeholder: 'user-123',
},
],
};
// 실행 로직
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
// 자격증명 가져오기
const credentials = await this.getCredentials('myApiCredentials');
const apiKey = credentials.apiKey as string;
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i) as string;
if (operation === 'getUser') {
const userId = this.getNodeParameter('userId', i) as string;
const response = await this.helpers.httpRequest({
method: 'GET',
url: `https://api.myservice.com/users/${userId}`,
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
returnData.push({ json: response });
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: { error: (error as Error).message },
pairedItem: { item: i },
});
continue;
}
throw new NodeOperationError(this.getNode(), error as Error, {
itemIndex: i,
});
}
}
return [returnData];
}
}
커스텀 노드 설치
# 빌드
npm run build
# n8n에 등록 (패키지로 설치)
cd ~/.n8n
mkdir custom
cp -r /path/to/n8n-nodes-mycustom/dist ./custom/node_modules/n8n-nodes-mycustom
# package.json 수정
{
"n8n": {
"nodes": ["dist/nodes/MyApiNode/MyApiNode.node.js"],
"credentials": ["dist/credentials/MyApiCredentials.credentials.js"]
}
}
# 환경 변수로 커스텀 노드 경로 지정
N8N_CUSTOM_EXTENSIONS=/home/node/.n8n/custom
# n8n 재시작
docker compose restart n8n
프로덕션 배포
운영용 Docker Compose
# docker-compose.prod.yml
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "127.0.0.1:5678:5678" # 로컬 바인딩 (Nginx 뒤에 위치)
environment:
# 보안
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_USER_MANAGEMENT_JWT_SECRET=${JWT_SECRET}
# 인증 (n8n v1 이후: 이메일/패스워드 기반)
- N8N_USER_MANAGEMENT_DISABLED=false
- N8N_DIAGNOSTICS_ENABLED=false
- N8N_VERSION_NOTIFICATIONS_ENABLED=false
# 외부 URL
- N8N_HOST=n8n.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourdomain.com
# 데이터베이스
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
# 실행 이력 관리
- EXECUTIONS_DATA_MAX_AGE=720 # 30일
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_PRUNE_MAX_COUNT=50000
# 성능
- N8N_CONCURRENCY_PRODUCTION_LIMIT=20
- QUEUE_BULL_REDIS_HOST=redis
- EXECUTIONS_MODE=queue # 대용량 처리 시
# 타임존
- GENERIC_TIMEZONE=Asia/Seoul
- TZ=Asia/Seoul
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- n8n-network
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:5678/healthz"]
interval: 30s
timeout: 10s
retries: 3
n8n-worker: # Queue 모드 사용 시 워커
image: n8nio/n8n:latest
command: worker
restart: always
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
- QUEUE_BULL_REDIS_HOST=redis
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
networks:
- n8n-network
scale: 3 # 3개 워커 인스턴스
postgres:
image: postgres:15-alpine
restart: always
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres-init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- n8n-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
retries: 5
redis:
image: redis:7-alpine
restart: always
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
networks:
- n8n-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
retries: 5
volumes:
n8n_data:
postgres_data:
redis_data:
networks:
n8n-network:
driver: bridge
Nginx 리버스 프록시
# /etc/nginx/sites-available/n8n
server {
listen 80;
server_name n8n.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name n8n.yourdomain.com;
# SSL 인증서 (Certbot)
ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# 보안 헤더
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
# n8n 프록시
location / {
proxy_pass http://127.0.0.1:5678;
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;
# WebSocket 타임아웃
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
# 최대 요청 크기 (파일 업로드)
client_max_body_size 100M;
}
보안 설정
인증 & 접근 제어
# n8n v1 이후 이메일/패스워드 인증 (권장)
환경 변수:
N8N_USER_MANAGEMENT_DISABLED=false
# 첫 실행 시 관리자 계정 생성 프롬프트 표시
# 이후 Settings → Users에서 팀원 추가
# IP 기반 접근 제한 (Nginx)
location / {
allow 10.0.0.0/8; # 내부 네트워크
allow 192.168.0.0/16; # 로컬 네트워크
allow 203.0.113.0/24; # 회사 IP 대역
deny all;
proxy_pass http://127.0.0.1:5678;
}
# Webhook 경로는 외부 허용 (선택적)
location /webhook/ {
proxy_pass http://127.0.0.1:5678;
}
자격증명 보안
# 암호화 키 생성 (최초 설정, 절대 분실 금지)
openssl rand -hex 32
# → 저장: .env 파일에 N8N_ENCRYPTION_KEY=<생성된 값>
# .env 파일 예시
N8N_ENCRYPTION_KEY=a1b2c3d4e5f6... # 64자 hex
JWT_SECRET=your-jwt-secret
DB_PASSWORD=strong-random-password
# .env 파일 권한 설정
chmod 600 .env
chown root:root .env
# 자격증명 백업 (암호화된 상태)
# Settings → Setup → Download backup 또는
docker exec n8n n8n export:credentials --all --output=/backup/credentials.json
Webhook 보안
// Webhook 서명 검증 (Code 노드)
const crypto = require('crypto');
const payload = JSON.stringify($json.body);
const signature = $json.headers['x-signature-256'];
const secret = $env.WEBHOOK_SECRET;
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
throw new Error('Invalid webhook signature');
}
return [{ json: $json.body }];
네트워크 격리
# Docker 네트워크 격리
networks:
n8n-internal:
internal: true # 외부 인터넷 차단
n8n-external:
driver: bridge # 인터넷 접근 허용
# n8n은 내부+외부 둘 다, DB/Redis는 내부만
services:
n8n:
networks: [n8n-internal, n8n-external]
postgres:
networks: [n8n-internal] # 내부 전용
redis:
networks: [n8n-internal] # 내부 전용
성능 최적화
Queue 모드 (대용량 처리)
기본 실행 모드는 단일 프로세스로 처리합니다. 대량 워크플로우를 동시에 처리하려면 Queue 모드를 사용하세요.
Queue 모드: 메인 서버는 작업을 Redis Queue에 넣고, 여러 워커가 병렬로 처리
# Queue 모드 설정
environment:
- EXECUTIONS_MODE=queue # queue 또는 regular
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_BULL_REDIS_PORT=6379
- QUEUE_BULL_REDIS_DB=0
# 워커 실행
docker run -d n8nio/n8n worker \
-e EXECUTIONS_MODE=queue \
-e QUEUE_BULL_REDIS_HOST=redis
성능 튜닝 포인트
# 1. 병렬 실행 제한 (과부하 방지)
N8N_CONCURRENCY_PRODUCTION_LIMIT=10 # 동시 실행 워크플로우 수
# 2. 실행 이력 자동 정리
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=720 # 720시간 = 30일
EXECUTIONS_DATA_PRUNE_MAX_COUNT=100000
# 3. PostgreSQL 인덱스 최적화
-- 실행 이력 조회 성능 향상
CREATE INDEX idx_execution_finished ON execution_entity (finished, stopped_at);
CREATE INDEX idx_execution_workflow ON execution_entity (workflow_id, started_at);
# 4. Redis 메모리 설정
maxmemory 1gb
maxmemory-policy allkeys-lru
# 5. Node.js 힙 메모리 증가 (대용량 데이터 처리 시)
NODE_OPTIONS=--max-old-space-size=4096
대량 데이터 처리 최적화
// Code 노드 — 청크 분할 처리
function chunkArray<T>(array: T[], size: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
const allItems = $input.all().map(item => item.json);
const chunks = chunkArray(allItems, 50); // 50개씩 묶음
return chunks.map(chunk => ({ json: { chunk, count: chunk.length } }));
# 청크 단위 병렬 처리 패턴
Code 노드 (배열 → 청크 분할)
↓
Loop Over Items (각 청크마다)
↓
SplitInBatches (청크 내 아이템 병렬 처리)
↓
[병렬] HTTP Request × N
↓
Merge
↓
Wait 노드 (Rate Limit 방지, 1초 대기)
↓
다음 청크...
모니터링 & 운영
헬스체크 & 모니터링
# n8n 헬스체크 엔드포인트
GET https://n8n.yourdomain.com/healthz
# 응답: {"status":"ok"}
# Prometheus 메트릭 활성화
N8N_METRICS=true
N8N_METRICS_PREFIX=n8n_
# 메트릭 엔드포인트
GET http://localhost:5678/metrics
# 주요 메트릭
n8n_workflow_success_total # 성공 실행 수
n8n_workflow_failed_total # 실패 실행 수
n8n_execution_duration_seconds # 실행 시간 분포
n8n_active_executions_count # 현재 실행 중인 수
Grafana 대시보드 설정
# docker-compose에 Prometheus + Grafana 추가
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'n8n'
static_configs:
- targets: ['n8n:5678']
metrics_path: '/metrics'
알림 설정
# 실패율 급증 알림 워크플로우 (n8n으로 모니터링 구축)
Schedule Trigger (5분마다)
↓
PostgreSQL 노드
Query: |
SELECT
COUNT(*) FILTER (WHERE finished AND NOT stopped) AS success,
COUNT(*) FILTER (WHERE finished AND stopped) AS failed,
COUNT(*) FILTER (WHERE NOT finished) AS running
FROM execution_entity
WHERE started_at > NOW() - INTERVAL '5 minutes'
↓
Code 노드 (실패율 계산)
const { success, failed, running } = $json;
const total = success + failed;
const failureRate = total > 0 ? failed / total : 0;
const alert = failureRate > 0.3 && total > 10; // 30% 이상, 10건 이상
return [{ json: { success, failed, running, failureRate, alert } }];
↓
IF 노드 (alert === true)
↓ (True)
Slack 노드
Channel: #n8n-alerts
Message: |
⚠️ n8n 실패율 급증
최근 5분간 실패율: {{ Math.round($json.failureRate * 100) }}%
성공: {{ $json.success }}, 실패: {{ $json.failed }}
백업 & 복구
# 워크플로우 내보내기 (정기 백업)
docker exec n8n n8n export:workflow \
--all \
--output=/backup/workflows-$(date +%Y%m%d).json
# 자격증명 내보내기
docker exec n8n n8n export:credentials \
--all \
--output=/backup/credentials-$(date +%Y%m%d).json
# PostgreSQL 백업
docker exec postgres pg_dump -U n8n n8n | gzip \
> /backup/n8n-db-$(date +%Y%m%d).sql.gz
# 복구
docker exec -i postgres psql -U n8n n8n < /backup/n8n-db-20260101.sql
docker exec n8n n8n import:workflow \
--input=/backup/workflows-20260101.json
docker exec n8n n8n import:credentials \
--input=/backup/credentials-20260101.json
# 자동 백업 스크립트 (cron)
#!/bin/bash
BACKUP_DIR=/opt/backups/n8n
DATE=$(date +%Y%m%d-%H%M)
mkdir -p $BACKUP_DIR
# 워크플로우 백업
docker exec n8n n8n export:workflow --all --output=/tmp/wf.json
docker cp n8n:/tmp/wf.json $BACKUP_DIR/workflows-$DATE.json
# DB 백업
docker exec postgres pg_dump -U n8n n8n | gzip \
> $BACKUP_DIR/db-$DATE.sql.gz
# 30일 이전 백업 삭제
find $BACKUP_DIR -mtime +30 -delete
echo "백업 완료: $DATE"
# crontab 등록 (매일 새벽 3시)
# 0 3 * * * /opt/scripts/n8n-backup.sh >> /var/log/n8n-backup.log 2>&1
워크플로우 관리 & 거버넌스
버전 관리 (Git 연동)
# n8n Enterprise: Source Control (Git 통합)
Settings → Source Control → Connect to Git
# 오픈소스 대안: 정기적으로 워크플로우 내보내기 후 Git 커밋
#!/bin/bash — 워크플로우 Git 저장 스크립트
REPO_DIR=/opt/n8n-workflows
# 워크플로우 내보내기
docker exec n8n n8n export:workflow --all \
--output=$REPO_DIR/workflows.json --pretty
# Git 커밋
cd $REPO_DIR
git add workflows.json
git diff --staged --quiet || git commit -m "workflow: auto-export $(date +%Y-%m-%d)"
git push origin main
네이밍 컨벤션
# 워크플로우 명명 규칙
[트리거 타입] [목적] [버전]
예시:
[Webhook] 고객 문의 AI 분류 v2
[Schedule] 일일 매출 리포트 발송
[Manual] 데이터 마이그레이션
[Sub] 공통 알림 발송 모듈
[Sub] AI 응답 생성 모듈
# 노드 명명 규칙
동사 + 목적어
예시:
Load Customer Data
Validate Input Format
Call Claude API
Parse AI Response
Send Slack Alert
Save to Database
워크플로우 테스트
# 테스트용 더미 데이터 생성 (Code 노드)
// 실제 데이터 없이 워크플로우 테스트
const testData = [
{
id: "test-001",
email: "test@example.com",
message: "결제가 안 됩니다. 도와주세요!",
createdAt: new Date().toISOString(),
}
];
return testData.map(item => ({ json: item }));
# 스테이징 환경 분리
# 환경 변수로 환경 구분
N8N_ENVIRONMENT=production # 또는 staging, development
# 워크플로우에서 환경 체크
IF 노드
Condition: {{ $env.N8N_ENVIRONMENT }} Equal "production"
True → 실제 Slack 알림 발송
False → 콘솔 로그만 (테스트 모드)
트러블슈팅
자주 발생하는 문제
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| Webhook이 수신되지 않음 | WEBHOOK_URL 미설정 또는 방화벽 | WEBHOOK_URL=https://n8n.yourdomain.com 설정, 포트 443/80 개방 |
| 자격증명 복호화 실패 | N8N_ENCRYPTION_KEY 변경 | 최초 설정한 키 복원 (절대 변경 금지) |
| 메모리 부족 (OOM) | 대용량 데이터 처리, 실행 이력 누적 | 실행 이력 정리, NODE_OPTIONS 메모리 증가 |
| 실행이 멈춤 (Stuck) | 무한 루프, 응답 없는 API | 노드별 타임아웃 설정, 실행 취소 후 워크플로우 수정 |
| AI Agent 무한 루프 | 도구 설명 불명확, 모델 혼란 | 최대 반복 횟수 설정, 도구 설명 개선 |
| PostgreSQL 연결 실패 | 네트워크 분리, 자격증명 오류 | Docker 네트워크 확인, DB 자격증명 재확인 |
| Webhook 중복 실행 | 타임아웃 재시도, 외부 서비스 재전송 | Idempotency Key 구현, 중복 체크 로직 추가 |
디버깅 팁
# 실행 로그 확인
docker logs n8n --tail 100 -f
# 특정 워크플로우 실행 이력 DB 조회
docker exec postgres psql -U n8n -c "
SELECT id, finished, stopped, started_at, stopped_at,
(data::json->'resultData'->'error'->>'message') as error
FROM execution_entity
WHERE workflow_id = '워크플로우ID'
ORDER BY started_at DESC
LIMIT 20;
"
# n8n 디버그 모드 실행
N8N_LOG_LEVEL=debug n8n start
# Code 노드에서 디버깅
console.log('현재 데이터:', JSON.stringify($json, null, 2));
console.log('이전 노드 출력:', JSON.stringify($input.all(), null, 2));
관련 가이드
📚 n8n 학습 경로
- n8n이란? — 기초 개념, 설치, 첫 워크플로우
- AI 워크플로우 구축 — Claude/OpenAI/RAG 연동
- 고급 활용 (현재 페이지) — 운영, 보안, 성능
🔗 연관 주제
- Docker — 컨테이너 기반 n8n 배포
- Claude API — Anthropic API 연동 심화
- MCP — n8n과 MCP 결합 가능성
- 보안 모범 사례 — 운영 환경 보안