[시놀로지 나스] n8n 셀프 호스팅 가이드 - PostgreSQL + Cloudflare Tunnel
n8n은 노드 기반의 오픈소스 워크플로우 자동화 도구다. 각종 앱과 서비스를 연결해 반복 작업을 자동화한다는 점에서 Zapier, Make(구 Integromat)와 비슷하지만, 가장 큰 차별점은 셀프 호스팅 공식 지원에 있다. n8n을 직접 서버에 설치해서 운영하면 실행 횟수 제한 없이 사용할 수 있고, Code 노드를 이용해 표준 노드만으로 해결하기 어려운 로직도 구현할 수 있다.
n8n은 클라우드 플랜(n8n Cloud)도 제공하지만(최소 월 20유로), 워크플로우가 늘어나고 실행 횟수가 많아질수록 비용 부담이 커질 수밖에 없다. 반면 NAS, 홈서버에 셀프 호스팅하면 이미 보유한 장비를 활용해 '나만의 자동화 서버'를 직접 운영할 수 있다. 물론 실행 환경과 데이터 저장 방식 역시 원하는 대로 제어할 수 있다. 꼭 홈서버가 아니더라도 Hetzner Cloud, Vultr 같은 VPS(Virtual Private Server; 가상 사설 서버)에 배포해서 n8n 클라우드보다 훨씬 저렴한 가격으로 운영할 수 있다.
셀프 호스팅은 데이터 프라이버시 측면에서도 유리하다. API 키, 웹훅 토큰 같은 민감한 자격 증명(Credentials)이 외부 서버가 아닌 소유한 NAS에 저장된다. Telegram Trigger, Webhook 노드처럼 외부 요청을 받아야 하는 경우 Cloudflare Tunnel을 활용하면 공유기 포트 개방(포트포워딩) 없이 HTTPS 요청을 안전하게 전달할 수 있다.
아래 가이드에서 Synology NAS에 n8n을 셀프 호스팅하는 과정을 단계별로 살펴보자. 단순히 도커 컨테이너 하나를 띄우는 데서 끝나지 않고 장기 운영을 고려한 안정적인 구축을 목표로 했다.
컨테이너 구성
n8n은 단일 컨테이너로도 실행할 수 있지만, 시스템 안정성과 유지보수성을 고려하면 역할별로 컨테이너를 분리하는 것이 좋다. n8n 기본 내장 DB인 SQLite는 단일 파일 기반으로 설정은 간단하지만, 워크플로우와 실행 이력이 지속적으로 쌓이는 환경에선 성능/동시성/백업 관리 측면에서 한계가 있다.
n8n이 공식 지원하는 PostgreSQL을 별도의 컨테이너로 운영하면 데이터를 더 안정적으로 저장할 수 있다. 또한 Code 노드 실행을 메인 n8n 프로세스와 분리할 경우 스크립트 오류나 과부하가 발생하더라도 n8n 서비스에 미치는 영향을 최소화할 수 있다.
결과적으로 아래 4개 컨테이너로 구성된다.
- n8n-postgres: 워크플로우, 실행 기록, 자격 증명 등 주요 데이터를 저장하는 PostgreSQL DB
- n8n: 웹 UI, 워크플로우 관리, 스케줄 실행, 웹훅 처리 등을 담당하는 메인 컨테이너
- n8n-runners: Code 노드 실행을 독립적으로 처리하는 샌드박스 컨테이너
- cloudflared: Cloudflare Tunnel을 통해 외부 HTTPS 요청을 n8n으로 전달하는 컨테이너
폴더/파일 생성
먼저 DSM 패키지 센터에서 Container Manager를 설치한다. 설치를 완료하면 컨테이너 데이터와 설정 파일이 저장될 공유 폴더(docker)가 생성된다.

나스 SSH 접속 후 아래 명령어를 입력하여 필요한 폴더와 빈 파일들을 생성한다. SSH 사용이 처음이라면 이전 포스팅 내용을 참고하자.
# n8n 최상위/하위 폴더 한 번에 생성 (-p 옵션)
mkdir -p /volume1/docker/n8n/n8n_data
mkdir -p /volume1/docker/n8n/postgres_data
# n8n 폴더로 이동
cd /volume1/docker/n8n
# 설정 파일 생성
touch docker-compose.yml .env init-data.sh
# 폴더/파일 잘 만들어졌는지 확인
ls -l
위 명령어를 모두 실행하면 아래와 같은 폴더 구조가 완성된다.
/volume1/docker/n8n/
├─ docker-compose.yml
├─ .env
├─ init-data.sh
├─ n8n_data/
└─ postgres_data/
보안키 생성
시스템 보안과 인증에 필요한 무작위 키를 생성해야 한다. 아래 명령어를 실행한 후 콘솔에 출력되는 결과값은 이후 .env 파일을 구성할 때 사용하므로 각각 기록해 둔다.
# N8N_ENCRYPTION_KEY 생성
openssl rand -hex 32
# N8N_RUNNERS_AUTH_TOKEN 생성
openssl rand -hex 32

- 첫 번째 값: n8n 자격 증명 데이터를 암호화하는 데 사용 (
N8N_ENCRYPTION_KEY) - 두 번째 값: n8n <> Task Runner 통신 인증 토큰으로 사용 (
N8N_RUNNERS_AUTH_TOKEN)
.env 파일 구성
유지보수를 위해 버전·비밀번호·암호화 키 같은 설정값은 .env 파일에서 별도로 관리하는 게 좋다. 나스 DSM > 텍스트 편집기에서 docker > n8n 폴더에 있는 .env 파일을 열고 아래 내용을 붙여넣는다. .env는 숨김 파일이므로 [파일 유형]을 [모든 파일]로 선택해야 보인다.

POSTGRES_PASSWORD, N8N_DB_PASSWORD에는 원하는 데이터베이스 비밀번호를 직접 입력하고, N8N_ENCRYPTION_KEY, N8N_RUNNERS_AUTH_TOKEN에는 위 단계에서 생성한 보안 키를 각각 입력한다.
# n8n 버전(stable 권장)
N8N_VERSION=stable
# PostgreSQL 버전
POSTGRES_VERSION=18
# n8n 실행 환경을 운영 모드로 설정
NODE_ENV=production
# 컨테이너 내부 시스템 시간대
TZ=Asia/Seoul
# n8n 워크플로우/스케줄 기준 시간대
GENERIC_TIMEZONE=Asia/Seoul
# Postgres 초기 접속용 기본 DB
POSTGRES_DB=postgres
# Postgres 관리자 계정 이름
POSTGRES_USER=postgres
# Postgres 관리자 계정 비밀번호
POSTGRES_PASSWORD=슈퍼유저_비밀번호
# n8n 전용 DB 이름 (실제 n8n 데이터가 저장되는 DB)
N8N_DB_NAME=n8n
# n8n이 DB에 접속할 때 사용할 계정 이름
N8N_DB_USER=n8n
# n8n 전용 DB 계정 비밀번호
N8N_DB_PASSWORD=n8n유저_비밀번호
# n8n 자격증명 데이터를 암호화하는 고정 키 (생성 후 변경 금지)
N8N_ENCRYPTION_KEY=openssl_rand_hex_32_결과값
# n8n Task Runner 인증 토큰
N8N_RUNNERS_AUTH_TOKEN=openssl_rand_hex_32_결과값
# runner 동시 실행 수
N8N_RUNNERS_MAX_CONCURRENCY=2
🔍 PostgreSQL은 슈퍼유저 계정 1개와 DB 1개로도 n8n을 실행할 수 있다. 이 경우 최고 관리자 권한으로 DB에 접근하게 되므로, 외부 공격이나 컨테이너 제어권이 탈취될 경우 피해 범위가 PostgreSQL 전체로 확대될 수 있다. 이 가이드에선 보안을 위해 PostgreSQL 관리자 계정은 초기 DB 생성·관리용으로만 사용하고, 실제 n8n 실행에는 전용 DB·계정을 사용하는 방식으로 구성한다.
데이터 초기화 스크립트
PostgreSQL 공식 Docker 이미지는 컨테이너가 처음 초기화될 때 /docker-entrypoint-initdb.d 경로에 있는 .sh, .sql 파일을 자동으로 실행한다.
초기화 스크립트를 NAS에 미리 만들어 두고 이 경로에 볼륨으로 마운트해 두면, 최초 실행 시(PostgreSQL 데이터 디렉터리가 비어 있는 상태) n8n 전용 데이터베이스와 계정이 자동으로 생성된다. 데이터 디렉터리가 초기화된 이후에는 초기화 스크립트가 다시 실행되지 않는다.
DSM File Station > docker > n8n 폴더에 있는 init-data.sh 파일을 텍스트 편집기로 열고, 아래 내용을 붙여넣는다.
#!/bin/bash
set -e
# n8n 전용 DB와 DB 계정을 생성하는 스크립트
# postgres_data 디렉터리가 비어 있을때만 실행됨 (최초 초기화 시점)
# N8N_DB_PASSWORD에는 작은따옴표(')를 넣지 않는 것을 권장
psql -v ON_ERROR_STOP=1 \
--username "$POSTGRES_USER" \
--dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER ${N8N_DB_USER} WITH PASSWORD '${N8N_DB_PASSWORD}';
CREATE DATABASE ${N8N_DB_NAME} OWNER ${N8N_DB_USER};
EOSQL
psql: PostgreSQL에 접속해서 SQL을 실행하는 CLI 도구-v ON_ERROR_STOP=1: SQL 실행 중 에러 발생하면 즉시 중단하도록 설정--username "$POSTGRES_USER": PostgreSQL에 접속할 관리자 계정 지정--dbname "$POSTGRES_DB": 초기 접속에 사용할 데이터베이스 지정<<-EOSQL: 아래 SQL 문들을psql입력으로 전달 (heredoc 문법)CREATE USER ...:N8N_DB_USER와N8N_DB_PASSWORD로 n8n 전용 DB 계정 생성CREATE DATABASE ...:N8N_DB_NAME으로 n8n 전용 DB 생성,N8N_DB_USER를 소유자로 지정
도커 구성 파일 생성
도커를 서비스로 띄우는 방법은 크게 일반 docker run 방식과 Docker Compose 방식으로 나뉜다. docker run은 컨테이너를 하나씩 직접 실행하는 방식인 반면, Docker Compose는 여러 컨테이너의 실행 설정을 .yml 파일로 통합 관리하고 한 번에 실행하는 방식이다.
이번 가이드에서 구축할 환경은 n8n, n8n runner, PostgreSQL 등 여러 컨테이너를 함께 사용해야 하므로 Docker Compose 방식이 더 적합하다. Compose를 사용하면 각 컨테이너의 이미지, 환경 변수, 포트, 볼륨, 의존 관계까지 .yml 파일 하나로 통합 관리할 수 있어서 편리하다.
docker > n8n 폴더에 생성한 docker-compose.yml 파일을 텍스트 편집기로 열고 아래 내용을 붙여넣는다.
services:
postgres:
image: postgres:${POSTGRES_VERSION}
container_name: n8n-postgres
# 직접 정지한 경우를 제외하고 재부팅/오류 상황에서 자동 재시작
restart: unless-stopped
environment:
TZ: ${TZ}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
N8N_DB_NAME: ${N8N_DB_NAME}
N8N_DB_USER: ${N8N_DB_USER}
N8N_DB_PASSWORD: ${N8N_DB_PASSWORD}
# 볼륨 연결 설정 (NAS 경로:컨테이너 내부 경로)
# 컨테이너가 오른쪽 경로에 데이터를 쓰거나 읽으면, 실제 파일은 왼쪽 NAS 경로에 저장되거나 읽힘
volumes:
# PostgreSQL 데이터 디렉터리를 NAS 폴더에 연결
# PostgreSQL 18부터 볼륨 마운트 경로가 /var/lib/postgresql 로 변경됨
- /volume1/docker/n8n/postgres_data:/var/lib/postgresql
# NAS 초기화 스크립트를 PostgreSQL 자동 실행 경로에 연결
# :ro를 붙이면 컨테이너 안에서 읽기 전용으로 마운트됨
- /volume1/docker/n8n/init-data.sh:/docker-entrypoint-initdb.d/init-data.sh:ro
# PostgreSQL이 실제로 접속 가능한 상태인지 확인
healthcheck:
# healthcheck 명령어 정의
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
# healthcheck 실행 간격
interval: 10s
# healthcheck 명령이 이 시간 안에 응답하지 않으면 실패로 간주
timeout: 5s
# 연속으로 실패할 수 있는 최대 횟수 (이 횟수만큼 실패하면 unhealthy)
retries: 10
# 컨테이너 시작 후 이 시간 동안은 실패해도 실패 횟수로 계산하지 않음
start_period: 30s
n8n:
image: n8nio/n8n:${N8N_VERSION}
container_name: n8n
restart: unless-stopped
# PostgreSQL이 접속 가능 상태가 된 뒤 n8n 시작
depends_on:
postgres:
condition: service_healthy
# 포트 설정 (호스트 포트:컨테이너 포트)
# NAS의 5678 포트를 컨테이너의 5678 포트와 연결
# http://나스IP:5678 접속 → 컨테이너 내부 5678 포트로 전달 → n8n 페이지 표시
ports:
- "5678:5678"
environment:
# Runtime
NODE_ENV: ${NODE_ENV}
# 컨테이너 시스템 시간대
TZ: ${TZ}
# n8n 애플리케이션 시간대
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
# Database
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${N8N_DB_NAME}
DB_POSTGRESDB_USER: ${N8N_DB_USER}
DB_POSTGRESDB_PASSWORD: ${N8N_DB_PASSWORD}
# Security / persistence
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
# n8n 설정 파일을 소유자만 읽고 쓸 수 있도록 권한 검사
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: true
# HTTPS에서만 쿠키 전송 허용 여부(Cloudflare Tunnel 적용 후 true로 변경)
N8N_SECURE_COOKIE: false
# Task Runner
N8N_RUNNERS_MODE: external
# 외부 Runner 컨테이너가 n8n Task Broker에 접속할 수 있도록 허용
# Task Broker는 n8n 본체와 러너 사이에서 작업을 전달/관리하는 중계 서버
N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
# Diagnostics
N8N_DIAGNOSTICS_ENABLED: false
# n8n 설정 데이터를 NAS 폴더에 보존 (NAS 경로:컨테이너 내부 경로)
volumes:
- /volume1/docker/n8n/n8n_data:/home/node/.n8n
n8n-runners:
image: n8nio/runners:${N8N_VERSION}
container_name: n8n-runners
restart: unless-stopped
depends_on:
- n8n
environment:
# Runtime
TZ: ${TZ}
# Task Runner
# 러너가 작업을 받아오기 위해 n8n의 Task Broker에 접속하는 내부 주소
# 주소에서 'n8n'은 Docker Compose 서비스명, '5679'는 Task Broker 기본 포트
N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_MAX_CONCURRENCY: ${N8N_RUNNERS_MAX_CONCURRENCY}
# 로그 수준 설정
# info는 일반 실행 정보와 주요 상태 기록
N8N_RUNNERS_LAUNCHER_LOG_LEVEL: info
내용을 모두 입력한 뒤 텍스트 편집기 왼쪽 상단 파일 > 인코딩 > Unicode (UTF-8)에 체크한 뒤 저장한다.

폴더/파일 권한 조정
n8n과 PostgreSQL은 각각 컨테이너 내부의 특정 사용자 권한으로 실행된다. 따라서 컨테이너가 직접 데이터를 읽고 쓰는 n8n_data, postgres_data 폴더는 해당 컨테이너 내부 사용자의 UID(사용자)/GID(그룹)에 맞춰 소유권을 지정해줘야 한다.
.env 파일에는 DB 비밀번호, n8n 암호화 키 같은 민감한 정보가 포함되어 있으므로 다른 사용자가 열어볼 수 없도록 권한을 엄격히 제한하는 것이 좋다. 나스 SSH에서 아래 명령어를 입력한다.
# n8n 작업 폴더로 이동
cd /volume1/docker/n8n
# 컨테이너가 직접 쓰는 데이터 폴더는 컨테이너 내부 사용자 UID:GID에 맞춤
# n8n 컨테이너의 node 사용자 UID:GID는 보통 1000:1000
sudo chown -R 1000:1000 n8n_data
# PostgreSQL 컨테이너의 postgres 사용자 UID:GID는 보통 999:999
sudo chown -R 999:999 postgres_data
# 데이터 폴더는 컨테이너 사용자만 접근 가능하도록 제한
sudo chmod 700 n8n_data postgres_data
# Compose 설정 파일은 소유자만 수정, 나머지는 읽기만 허용
sudo chmod 644 docker-compose.yml
# 초기화 스크립트는 실행 가능하도록 설정
sudo chmod 755 init-data.sh
# 비밀번호/키가 들어 있는 .env는 소유자만 읽기/쓰기 허용
sudo chmod 600 .env
컨테이너 생성/실행
터미널에서 직접 명령어를 입력하거나, Container Manager 앱의 프로젝트 기능을 이용해 Compose 컨테이너를 생성하고 실행할 수 있다. 터미널 사용이 익숙하지 않다면 Container Manager 한 곳에서 관리하는 게 더 편하다.
Container Manager 사용 시
Container Manager 앱 실행 > 왼쪽 [프로젝트] 메뉴 > [생성] 버튼을 클릭하고 경로를 /docker/n8n으로 지정한다. 폴더를 지정하면 아래에 Compose 구성 파일 내용이 표시되어야 한다. 프로젝트 이름은 구분하기 쉽도록 n8n 등으로 입력한다.

n8n은 5678 포트를 통해 웹 UI를 제공하므로 웹 포털은 따로 설정하지 않아도 된다. 체크박스가 해제된 상태에서 [다음] 버튼을 누른다.

[프로젝트가 생성되면 시작]에 체크하고 완료 버튼을 누른다.

모든 컨테이너를 설치할 때까지 잠시 기다린다.

터미널 사용 시
나스 SSH에서 아래 명령어를 입력하여 컨테이너를 생성/실행한다.
cd /volume1/docker/n8n
# docker-compose.yml에 정의된 이미지 다운로드
sudo docker compose pull
# 컨테이너를 백그라운드에서 생성 후 실행
sudo docker compose up -d
컨테이너 생성이 완료되면 정상 실행 여부를 확인하는 것이 좋다. 아래 명령어를 입력했을 때 n8n-postgres, n8n, n8n-runners 세 컨테이너가 모두 Up(실행 중) 상태로 표시되면 성공이다.
# 컨테이너 실행 상태 확인
sudo docker compose ps

오류 발생 여부 등 더 자세한 점검이 필요하다면 아래 명령어로 각 컨테이너의 최근 로그를 확인할 수 있다.
# n8n 컨테이너 로그 확인
sudo docker compose logs n8n --tail=100
# PostgreSQL 컨테이너 로그 확인
sudo docker compose logs postgres --tail=100
# n8n 러너 컨테이너 로그 확인
sudo docker compose logs n8n-runners --tail=100

접속 확인
컨테이너 정상 실행을 확인했다면 브라우저에서 n8n 웹 UI에 접속해 본다. 나스 내부 IP가 192.168.1.2 라면 http://192.168.1.2:5678 주소로 접속할 수 있다.
# NAS 내부 IP로 접속
http://NAS_내부IP:5678
# Tailscale 사용 중이면 NAS의 Tailscale IP로도 접속 가능
http://NAS_TAILSCALE_IP:5678
아래 화면이 나오면 성공이다. Owner Account 계정을 생성하고 로그인한다.

참고로 Compose 구성 파일에 재시작 정책을 추가해뒀기 때문에, 컨테이너를 수동으로 중지하지 않는 이상 NAS를 재부팅하더라도 자동으로 다시 실행된다.
restart: unless-stopped
업데이트 방법
업데이트 전에는 n8n 릴리즈 노트에서 호환성에 영향을 줄 수 있는 변경사항(breaking changes)을 확인하고, 가능하면 백업 후 진행하는 것이 좋다. 먼저 .env 파일에서 N8N_VERSION 값을 원하는 버전으로 변경한다.
N8N_VERSION=2.21.7
그다음 나스 SSH에서 아래 명령어를 순서대로 실행한다. Container Manager를 사용 중이라면 프로젝트를 중지한 뒤, 빌드 버튼을 누르면 된다.
cd /volume1/docker/n8n
# docker-compose.yml에 정의된 이미지 다운로드 또는 갱신
sudo docker compose pull
# 컨테이너를 백그라운드에서 생성/재생성 후 백그라운드 실행
sudo docker compose up -d
# 컨테이너 실행 상태 확인
sudo docker compose ps
컨테이너 중지/삭제는 아래 명령어를 사용한다. docker compose up -d는 기존 컨테이너가 있더라도 이미지나 설정 변경이 있으면 필요한 컨테이너만 재생성한다. down 명령어는 버전 업데이트에선 사용할 일이 거의 없고, 서비스를 완전히 내리거나 설정 변경 후 깨끗한 상태에서 다시 시작하고 싶을 때 주로 사용한다.
cd /volume1/docker/n8n
# 실행 중인 n8n 관련 컨테이너와 Compose 네트워크를 안전하게 중지/삭제
sudo docker compose down
터미널 명령어와 Container Manager UI 비교

- 이미지 버전을 변경했을 때 (업데이트)
- 터미널 명령어:
docker compose pull→docker compose up -d - Container Manager: 중지 → 빌드(이미지 있으면 재사용, 없거나 버전 다르면 자동으로 pull)
- 터미널 명령어:
- 구성 파일을 수정했을 때
- 터미널 명령어:
docker compose up -d - Container Manager: 중지 → 빌드
- 터미널 명령어:
- 컨테이너 중지/제거하고 Compose로 생성된 네트워크까지 정리할 때
- 터미널 명령어:
docker compose down - Container Manager: 정리
- 터미널 명령어:
Cloudflare Tunnel
배경 지식
외부 네트워크에서 n8n 컨테이너에 접속하려면 일반적으로 공유기 포트포워딩이 필요하다. 하지만 포트포워딩은 외부에서 내부 네트워크로 들어오는 인바운드 연결을 허용하는 방식이어서 신중하게 사용해야 한다. 인바운드 연결을 열어 두면 NAS의 특정 포트로 직접 접근할 수 있게 되고, 서비스 설정 오류나 취약점이 있을 경우 무차별 대입 공격이나 포트 스캔 등의 대상이 될 수 있다.
포트포워딩을 설정하지 않으면 NAS에서 외부로 나가는 아웃바운드 연결은 가능하지만, 외부에서 NAS로 직접 들어오는 인바운드 연결은 허용되지 않는다. Cloudflare Tunnel은 이 구조를 활용해 로컬 기기에서 Cloudflare 서버로 아웃바운드 연결을 만들어 두고 이 연결을 계속 유지한다. 이렇게 내부 네트워크에서 외부 서버로 연결을 만들어 두는 방식을 터널(Tunnel)이라고 부른다.
외부 사용자가 n8n 도메인으로 요청을 보내면 Cloudflare가 먼저 요청을 받은 뒤, 이미 열려 있는 터널을 통해 내부 네트워크의 n8n 컨테이너로 전달한다. 이를 통해 공유기 포트를 개방하지 않고도 외부에서 접속할 수 있고, Cloudflare가 SSL 인증서를 자동으로 관리해 주기 때문에 안전한 HTTPS 통신까지 손쉽게 구현할 수 있다.

Telegram Trigger나 Webhook 노드처럼 외부 서비스가 n8n으로 데이터를 보내야 하는 경우, 외부에서 접근 가능한 HTTPS 주소가 필요하다. Cloudflare Tunnel을 사용하면 https://webhook.yourdomain.net 같은 공개 HTTPS 주소를 n8n에 연결해서 포트포워딩 없이도 웹훅 요청을 받을 수 있다.
💡 개인 용도나 홈 서버는 Cloudflare Tunnel을 무료로 사용할 수 있다.
터널 생성
Cloudflare Tunnel을 이용하려면 외부 접속에 사용할 도메인이 있어야 하고, 이 도메인의 네임서버가 Cloudflare로 설정되어 있어야 한다.
Cloudflare 콘솔 페이지 > 좌측 [네트워크] 메뉴 > [터널] > [+ 터널 생성] 버튼을 클릭한다.

터널 이름은 n8n-nas 처럼 구분하기 쉬운 이름으로 입력한다.

운영체제는 Docker를 선택한 뒤, Run tunnel with Docker 필드 우측에 있는 명령어 복사 버튼을 클릭한다.

복사된 명령어를 보면 아래와 같은 구조로 되어 있다. --token 뒤에 있는 토큰 값을 따로 저장해 둔다.
docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token <TOKEN>
DSM으로 돌아와서 .env 파일을 텍스트 편집기로 열고 아래 내용을 추가한다. CLOUDFLARED_TOKEN에는 방금 복사한 토큰 값을 붙여넣는다.
# Cloudflare Zero Trust에서 터널 생성 후 발급받은 token 값
CLOUDFLARED_TOKEN=cloudflare_tunnel_token_값
# n8n이 인식할 외부 접속 호스트명
N8N_HOST=n8n.yourdomain.net
# n8n 외부 접속 프로토콜
N8N_PROTOCOL=https
# n8n 컨테이너 내부 리스닝 포트
N8N_PORT=5678
# n8n 에디터 UI 접속 주소 (주소 마지막에 / 포함)
N8N_EDITOR_BASE_URL=https://n8n.yourdomain.net/
# n8n이 생성하는 웹훅 URL의 기준 주소 (주소 마지막에 / 포함)
WEBHOOK_URL=https://webhook.yourdomain.net/
# n8n이 리버스 프록시(중간 서버) 1단계 뒤에서 동작한다고 인식하도록 설정
N8N_PROXY_HOPS=1
docker-compose.yml 파일을 열고 아래 내용을 붙여넣는다.
services:
postgres:
image: postgres:${POSTGRES_VERSION}
container_name: n8n-postgres
# 직접 정지한 경우를 제외하고 재부팅/오류 상황에서 자동 재시작
restart: unless-stopped
environment:
TZ: ${TZ}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
N8N_DB_NAME: ${N8N_DB_NAME}
N8N_DB_USER: ${N8N_DB_USER}
N8N_DB_PASSWORD: ${N8N_DB_PASSWORD}
# 볼륨 연결 설정 (NAS 경로:컨테이너 내부 경로)
# 컨테이너가 오른쪽 경로에 데이터를 쓰거나 읽으면, 실제 파일은 왼쪽 NAS 경로에 저장되거나 읽힘
volumes:
# PostgreSQL 데이터 디렉터리를 NAS 폴더에 연결
# PostgreSQL 18부터 볼륨 마운트 경로가 /var/lib/postgresql 로 변경됨
- /volume1/docker/n8n/postgres_data:/var/lib/postgresql
# NAS의 초기화 스크립트를 PostgreSQL 자동 실행 경로에 연결
# :ro를 붙이면 컨테이너 안에서 읽기 전용으로 마운트됨
- /volume1/docker/n8n/init-data.sh:/docker-entrypoint-initdb.d/init-data.sh:ro
# PostgreSQL이 실제로 접속 가능한 상태인지 확인
healthcheck:
# healthcheck 명령어 정의
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
# healthcheck 실행 간격
interval: 10s
# healthcheck 명령이 이 시간 안에 응답하지 않으면 실패로 간주
timeout: 5s
# 연속으로 실패할 수 있는 최대 횟수 (이 횟수만큼 실패하면 unhealthy)
retries: 10
# 컨테이너 시작 후 이 시간 동안은 실패해도 실패 횟수로 계산하지 않음
start_period: 30s
n8n:
image: n8nio/n8n:${N8N_VERSION}
container_name: n8n
restart: unless-stopped
# PostgreSQL이 접속 가능 상태가 된 뒤 n8n 시작
depends_on:
postgres:
condition: service_healthy
# 포트 설정 (호스트 포트:컨테이너 포트)
# n8n 포트를 NAS 자신(127.0.0.1, localhost)한테만 개방
# LAN 직접 접속은 차단되지만, 같은 Compose 내부의 서비스들은 접근 가능
ports:
- "127.0.0.1:5678:5678"
environment:
# Runtime
NODE_ENV: ${NODE_ENV}
# 컨테이너 시스템 시간대
TZ: ${TZ}
# n8n 애플리케이션 시간대
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
# Database
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${N8N_DB_NAME}
DB_POSTGRESDB_USER: ${N8N_DB_USER}
DB_POSTGRESDB_PASSWORD: ${N8N_DB_PASSWORD}
# Security / persistence
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
# n8n 설정 파일을 소유자만 읽고 쓸 수 있도록 권한 검사
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: true
# HTTPS에서만 쿠키 전송 허용 여부
N8N_SECURE_COOKIE: true
# Task Runner
N8N_RUNNERS_MODE: external
# 외부 Runner 컨테이너가 n8n Task Broker에 접속할 수 있도록 허용
# Task Broker는 n8n 본체와 러너 사이에서 작업을 전달/관리하는 중계 서버
N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
# Public URL
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
N8N_PORT: ${N8N_PORT}
N8N_EDITOR_BASE_URL: ${N8N_EDITOR_BASE_URL}
WEBHOOK_URL: ${WEBHOOK_URL}
N8N_PROXY_HOPS: ${N8N_PROXY_HOPS}
# Diagnostics
N8N_DIAGNOSTICS_ENABLED: false
# n8n 설정 데이터를 NAS 폴더에 보존 (NAS 경로:컨테이너 내부 경로)
volumes:
- /volume1/docker/n8n/n8n_data:/home/node/.n8n
n8n-runners:
image: n8nio/runners:${N8N_VERSION}
container_name: n8n-runners
restart: unless-stopped
depends_on:
- n8n
environment:
# Runtime
TZ: ${TZ}
# Task Runner
# 러너가 작업을 받아오기 위해 n8n의 Task Broker에 접속하는 내부 주소
# 주소에서 n8n은 Docker Compose 서비스명, 5679는 Task Broker 기본 포트
N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_MAX_CONCURRENCY: ${N8N_RUNNERS_MAX_CONCURRENCY}
# 로그 수준 설정
# info는 일반 실행 정보와 주요 상태 기록
N8N_RUNNERS_LAUNCHER_LOG_LEVEL: info
cloudflared:
image: cloudflare/cloudflared:latest
container_name: n8n-cloudflared
restart: unless-stopped
depends_on:
- n8n
command: tunnel --no-autoupdate run --token ${CLOUDFLARED_TOKEN}
위 YAML 파일에선 n8n 서비스의 ports 설정을 기존 5678:5678에서 127.0.0.1:5678:5678로 변경했다. 그럼 NAS 내부에서만 5678 포트가 열리고 외부 네트워크 인터페이스에는 노출되지 않는다(로컬호스트에만 바인딩). 즉, 같은 LAN에 있더라도 다른 기기에서는 나스IP:5678 주소로 접속할 수 없다.
하지만 Docker Compose로 함께 실행된 컨테이너들은 내부 네트워크를 공유하기 때문에 서비스 이름을 도메인처럼 사용하여 서로 통신할 수 있다. 결과적으로 cloudflared 컨테이너는 호스트의 포트 노출 여부와 관계없이 Docker 내부 주소인 http://n8n:5678(서비스명:포트)을 통해 n8n에 접근할 수 있게 된다.

터널 경로 설정
이제 기본적인 설정은 모두 완료했다. 나스 SSH에서 아래 명령어를 입력하거나 Container Manager 앱에서 프로젝트 중지 → 빌드 버튼을 눌러서 컨테이너를 생성하고 실행한다.
cd /volume1/docker/n8n
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
컨테이너가 정상적으로 실행되면 Cloudflare 설정 화면에 "터널이 성공적으로 연결되었다"는 메시지가 표시된다.

위에서 생성한 터널로 이동한 뒤 상단 [경로] 탭 > 우측 [경로 추가] > [게시된 애플리케이션] 버튼을 클릭하고, n8n 웹 UI 접속 시 사용할 서브도메인을 추가한다.

- 서브도메인:
n8n - 도메인: 등록한 도메인 선택
- 서비스 URL:
http://n8n:5678
웹훅은 외부 서비스의 요청을 받아야 하는 공개 엔드포인트인 반면, n8n 웹 UI는 관리자용 콘솔이므로 Zero Trust, IP 제한 등의 추가적인 보호 레이어를 적용하는 것이 좋다.
보안을 위해 웹 UI와 웹훅 전용 엔드포인트를 분리하고, 웹훅 전용 주소는 /webhook , /webhook-test 같은 경로만 n8n으로 전달되도록 제한하면 불필요한 경로 노출을 줄일 수 있다.
다시 [경로 추가] > [게시된 애플리케이션] 버튼을 눌러 웹훅 전용 서브도메인을 추가한다.

- 서브도메인:
webhook - 도메인: 등록한 도메인 선택
- 경로:
^/(?:webhook|webhook-test)/[a-zA-Z0-9_-]+(?:/[a-zA-Z0-9_-]+)*/?$ - 서비스 URL:
http://n8n:5678
여기까지 완료하면 Synology NAS에서 PostgreSQL, 외부 Task Runner, Cloudflare Tunnel을 연동한 n8n 셀프 호스팅 환경이 완성된다. 단일 n8n 컨테이너 방식보다 초기 설정은 다소 복잡하지만 데이터 저장, Code 노드 실행, 외부 HTTPS 통신을 명확히 분리함으로써 훨씬 안정적으로 운영할 수 있다.
운영 중에는 docker compose ps와 로그 확인 명령어로 컨테이너 상태를 주기적으로 점검하고, n8n 업데이트 전에는 릴리즈 노트와 백업 상태를 먼저 확인하는 것이 좋다. 특히 n8n_data, postgres_data 폴더와 .env 파일은 주기적으로 안전한 곳에 백업해 두는 것을 권장한다.
'⌚️ Productivity' 카테고리의 다른 글
| [macOS] 예쁘고 직관적인 라디얼 스타일 앱 전환기 - Orbit (0) | 2026.04.26 |
|---|---|
| 티스토리 블로그에 자동 목차(TOC) 추가하기 (0) | 2026.04.07 |
| 티스토리 블로그 본문에 앵커(Anchor) 링크 추가하기 (1) | 2026.03.28 |
| [크롬 확장] 워터마크 제거·모델 고정 등 Gemini 더 편하게 쓰기 - Voyager (0) | 2026.03.07 |
| 중국 직구 아수스 공유기 TUF BE6500 한글 적용 방법 (0) | 2026.02.27 |
댓글
이 글 공유하기
다른 글
-
[macOS] 예쁘고 직관적인 라디얼 스타일 앱 전환기 - Orbit
[macOS] 예쁘고 직관적인 라디얼 스타일 앱 전환기 - Orbit
2026.04.26 -
티스토리 블로그에 자동 목차(TOC) 추가하기
티스토리 블로그에 자동 목차(TOC) 추가하기
2026.04.07 -
티스토리 블로그 본문에 앵커(Anchor) 링크 추가하기
티스토리 블로그 본문에 앵커(Anchor) 링크 추가하기
2026.03.28 -
[크롬 확장] 워터마크 제거·모델 고정 등 Gemini 더 편하게 쓰기 - Voyager
[크롬 확장] 워터마크 제거·모델 고정 등 Gemini 더 편하게 쓰기 - Voyager
2026.03.07