본문 바로가기
  • AI 시대에 적응하는 현대인을 위한 지식 공간
  • AI를 위한 데이터를 과학으로 역어본다
AI 활용

MacOS에서 n8n SSL/HTTPS 구축하기

by 피크나인 2025. 12. 16.

MacOS에서 n8n SSL/HTTPS 구축 가이드

MacOS환경에서 서버환경을 설정하여 운영하려고 할 때, 커스텀 도메인을 이용하여 실제 서버처럼 운영하기 위해서는 HTTPS를 지원하도록 설정하는 것이 좋습니다. HTTPS서비스는 HTTP서비스와는 다르게 SSL 인증서를 이용할 경우에만 정상적인 웹브라우저 접근을 허용할 수 잇습니다. 이번 글에서는 SSL인증서 기반의  Docker 환경에서 n8n에 적용하는 방법을 단계별로 안내해 드릴게요.

 

가장 권장되는 방식은 Nginx를 리버스 프록시로 사용하여 SSL 종료(SSL Termination)를 처리하는 것입니다. 이 방식이 관리도 쉽고 보안적으로도 우수합니다.

Mac에서 다양한 웹서비스를 위해서 SSL/HTTPS서비스를 구축합니다.
 

MacOS Tahoe 26.x에서 n8n SSL/HTTPS 구축하기
MacOS Tahoe 26.x에서 n8n SSL/HTTPS 구축하기


1. 전체 운영 환경

아키텍처

[인터넷] → [라우터 443] → [Nginx (SSL 종료)] → [n8n 컨테이너 5678]

MacOS에서의 서버 운영 환경  |  예시

아래의 개발환경에 맞춰서 'MacOS에서 n8n을 활용한 업무자동화 시스템 구축' 과 관련된 프로젝트를 진행중이라는 가정하에, SSL인증서 기반의 HTTPS로의 접근을  시도해 보도록 하겠습니다. 

  • 서버 : Apple Mini Max, MacOS Tahoe 26.1
  • 커스텀 도메인 : play.peaknine.io (IP: 61.85.213.***)
  • SSL인증서 : Sectigo PositiveSSL Wildcard (*.peaknine.io)
  • Docker : Docker Desktop 4.5
  • Project name : n8n-play
  • Container : n8n-1 (full container name in docker shell : n8n-play-n8n-1), port : 5678
  • Docker image : n8nio/n8n (use docker-compose.yml)
  • Network Router : Iptime T16000M
  • Router Port Forwarding : 443(외부) > 443(내부)

하고 싶은 작업


2. 프로젝트 디렉토리 구조 설정

먼저 프로젝트 'n8n-play'의  디렉토리 구조를 만듭니다.

# 프로젝트 디렉토리 생성
cd ~/Projects  # 또는 원하는 위치
mkdir -p n8n-play/{ssl,nginx,n8n-data}
cd n8n-play

 

최종 디렉토리 구조:

n8n-play/
├── docker-compose.yml
├── ssl/
│   ├── play_peaknine_io.crt      # 통합 인증서
│   └── play_peaknine_io.key      # 개인키
├── nginx/
│   └── nginx.conf
└── n8n-data/                      # n8n 데이터 영속화

3. SSL 인증서 파일 준비

Sectigo PositiveSSL Wildcard 인증서를 구매하면 보통 다음 파일들을 받습니다. 실제 다운로드 받은 '인증서.zip'파일과는 약간의 차이가 있지만, 구성형식은 유사합니다. Sectigo의 실제 인증서에 대한 자세한 내용은 'CASE 2: Setigo PositiveSSL Wildcard형 인증서를 구매한 경우' 를 참조하시기 바랍니다. 

 

CASE 1 : 일반적인 인증서를 활용하는 경우

Sectigo PositiveSSL인증서 및 다른 회사의 SSL인증서는 모두 아래와 같은 도메인 인증서, 개인키, 중간 인증서 체인과 같은 기본적인 파일 구조를 가지고 있습니다.

  • *.peaknine.io.crt (도메인 인증서)
  • *.peaknine.io.key (개인키)
  • *.peaknine.io.ca-bundle (중간 인증서 체인)

인증서 통합 (Chain Certificate 생성)

Nginx에서 사용하려면 도메인 인증서(*.peaknine.io.crt)와 중간 인증서(*.peaknine.io.ca-buldle)를 하나로 합쳐야 합니다.

# ssl 디렉토리로 이동
cd ~/Projects/n8n-play/ssl

# 인증서 파일들을 이 디렉토리에 복사한 후
# 도메인 인증서 + CA Bundle을 순서대로 합침
cat star_peaknine_io.crt star_peaknine_io.ca-bundle > play_peaknine_io.crt

# 개인키 파일 복사 (이름 변경)
cp star_peaknine_io.key play_peaknine_io.key

# 개인키 권한 설정 (보안상 중요)
chmod 600 play_peaknine_io.key

인증서 유효성 검증

# 인증서 정보 확인
openssl x509 -in play_peaknine_io.crt -text -noout | head -20

# 개인키와 인증서 매칭 확인 (해시값이 동일해야 함)
openssl x509 -noout -modulus -in play_peaknine_io.crt | openssl md5
openssl rsa -noout -modulus -in play_peaknine_io.key | openssl md5

 

두 명령의 MD5 해시값이 동일하면 키와 인증서가 올바르게 매칭된 것입니다.

CASE 2: Sectigo PositiveSSL Wildcard형 인증서를 구매한 경우

아래의 이미지는 Sectigo PositiveSSL Wildcard형으로 구매한 인증서 '_wildcard_.peaknine.io_202512118335B.zip'파일의 압축을 풀어논 후의 디렉토리 구조입니다. 일반적으로 Sectigo에서 제공하는 표준적인 인증서 패키지 구조를 가지고 있습니다. 이 두번쩨 케이스에서는 Sectigo에서 발행한 통합 SSL인증서를  활용하는 방법을 알아보고자 합니다.

Setigo에서 발행된 Wildcard형 SSL인증서 파일의 구조

사용할 SSL 인증서 파일

상단의 이미지 스크린샷을 기준으로 2개 파일만 사용하면 됩니다. 일반적인 인증서 구조에서는 도메인 인증서와 중간 인증서를 통합해야만 Nginx에서 사용할 수 있었지만, Sectigo의 표준 인증서 구조에서는 이미 통합된 형태의 인증서 파일 '_wildcard_.peaknine.io_202512118335B.all.crt.pem'을 그대로 활용할 수 있습니다.

용도 파일명 설명
통합 인증서 _wildcard_.peaknine.io_202512118335B.all.crt.pem 도메인 인증서 + 체인이 이미 합쳐진 파일 (9KB)
개인키 _wildcard_.peaknine.io_202512118335B.key.pem 비밀키 파일 (2KB)

.all.crt.pem 파일이 이미 인증서 체인을 포함하고 있어서, 별도로 cat 명령으로 합칠 필요가 없습니다!

수정된 SSL 인증서 파일 준비

*.pem타입의 인증서는 Base64형태의 텍스트 형태로 인코딩된 형태로 *.crt 또는 *.key로의 단순한 파일명 변경만으로 동일하게 사용할 수 있습니다. 

# 프로젝트 디렉토리로 이동
cd ~/Projects/n8n-play

# ssl 디렉토리 생성 (없다면)
mkdir -p ssl

# 인증서 파일 복사 (파일명을 간단하게 변경)
cp "/path/to/SSL_peaknine.io/_wildcard_.peaknine.io_202512118335B/ \
_wildcard_.peaknine.io_202512118335B.all.crt.pem" ./ssl/wildcard_peaknine_io.crt

cp "/path/to/SSL_peaknine.io/_wildcard_.peaknine.io_202512118335B/ \
_wildcard_.peaknine.io_202512118335B.key.pem" ./ssl/wildcard_peaknine_io.key

# 개인키 보안 권한 설정
chmod 600 ./ssl/wildcard_peaknine_io.key

# 파일 확인
ls -la ./ssl/

인증서 유효성 검증

Nginx에서 사용하는 두 개의 인증서 (./ssl/wildcard_peaknine_io.crt, ./ssl/wildcard_peaknine_io.key) 는 MD5 해시값이 동일해야 합니다. 아래의 개인키-인증서 매칭 확인은 반드시 확인하셔야 합니다. 

# 인증서 정보 확인 (발급 대상, 유효기간 등)
openssl x509 -in ./ssl/wildcard_peaknine_io.crt -text -noout | head -30

# 와일드카드 도메인 확인
openssl x509 -in ./ssl/wildcard_peaknine_io.crt -noout -subject
# 예상 결과: subject=CN = *.peaknine.io

# 인증서 만료일 확인
openssl x509 -in ./ssl/wildcard_peaknine_io.crt -noout -dates

# 개인키-인증서 매칭 확인 (두 해시값이 동일해야 함)
openssl x509 -noout -modulus -in ./ssl/wildcard_peaknine_io.crt | openssl md5
openssl rsa -noout -modulus -in ./ssl/wildcard_peaknine_io.key | openssl md5

# 실제 매칭 확인시 화면 예시 (두개의 해시값이 동일하게 나타납니다.)
> openssl x509 -noout -modulus -in ./ssl/wildcard_peaknine_io.crt | openssl md5                                     ✔ │ 00:56:14
MD5(stdin)= d2bea39568cfbceb8f52f9e53dab****

> openssl rsa -noout -modulus -in ./ssl/wildcard_peaknine_io.key | openssl md5                                      ✔ │ 00:56:16
MD5(stdin)= d2bea39568cfbceb8f52f9e53dab****

4. MacOS 키체인에 인증서 등록  |  선택사항

로컬에서 브라우저 경고 없이 접근하려면 MacOS 키체인에 등록합니다.

# CA Bundle을 시스템 키체인에 등록
sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain \
  ~/Projects/n8n-play/ssl/star_peaknine_io.ca-bundle

 

또는 키체인 접근(Keychain Access) 앱을 통해 수동으로 등록:

 

macOS에서 SSL 인증서를 설치하려면 다운로드한 인증서 파일을 키체인 접근(Keychain Access) 앱에서 열어 시스템 키체인에 추가하고, 해당 인증서를 '항상 신뢰(Always Trust)'로 설정한 뒤, 필요하다면 브라우저를 재시작하면 됩니다. 이 과정은 웹사이트의 자체 서명 인증서나 개발용 인증서를 신뢰하여 보안 경고 없이 접근하기 위해 주로 사용됩니다. 

단계별 설치 방법

인증서 파일 다운로드: 먼저 설치하려는 .cer, .crt, .pem 또는 .pfx 형식의 SSL 인증서 파일을 Mac으로 다운로드합니다.

 

키체인 접근 실행:

  • 응용 프로그램 > 유틸리티 > 키체인 접근 (Keychain Access) 앱을 엽니다.
  • 또는 Spotlight 검색(Cmd + Space)에서 '키체인 접근'을 검색하여 실행합니다.

인증서 가져오기:

  • 키체인 접근 앱에서 좌측 상단의 키체인 선택 메뉴에서 '시스템 (System)'을 선택합니다.
  • 상단 메뉴바에서 '파일 (File) > 항목 가져오기 (Import Items...)'를 클릭합니다.
  • 다운로드한 인증서 파일을 찾아 선택하고 '열기'를 클릭합니다.
  • 또는 인증서 파일을 드래그 앤 드롭 (MacOS Keychain Access에서의 인증서 등록은 *.pfx 바이너리 인증서를 드래그 앤 드롭합니다.)

신뢰 설정:

  • 키체인 접근 목록에서 가져온 인증서를 찾습니다 (보통 이름이 동일).
  • 인증서를 더블클릭하여 상세 창을 엽니다.
  • '신뢰 (Trust)' 섹션을 확장(펼치기)합니다.
  • 'SSL' 또는 '항목 사용 시 (When using this certificate)' 옵션을 '항상 신뢰 (Always Trust)'로 변경합니다.
  • Mac의 관리자 암호를 입력하여 변경 사항을 승인합니다.

Keychain Access앱을 통해서 '*.peaknine.io'인증서가 맨 아래 부분에 추가되었습니다.
Keychain Access앱을 통해서 '*.peaknine.io'인증서가 맨 아래 부분에 추가되었습니다.

브라우저 재시작:

  • 설치가 완료되면 Safari, Chrome 등 모든 브라우저를 완전히 종료했다가 다시 실행합니다. 
  • 이제 해당 인증서를 사용하는 사이트에 접속할 때 보안 경고 없이 정상적으로 접근할 수 있습니다. 개발 환경에서 자체 인증서를 사용할 경우 이 과정을 통해 신뢰할 수 있게 됩니다. 

5.  Nginx 설정 파일 생성

프로젝트 폴더의 아래에 './nginx'폴더를 생성하고, 'nginx.conf' 설정파일을 생성합니다.

cd ~/Projects/n8n-play/nginx

nginx.conf (웹서비스 설정 파일 생성)

events {
    worker_connections 1024;
}

http {
    upstream n8n {
        server n8n:5678;
    }

    # HTTP → HTTPS 리다이렉트
    server {
        listen 80;
        server_name play.peaknine.io;
        return 301 https://$server_name$request_uri;
    }

    # HTTPS 서버
    server {
        listen 443 ssl;
        http2 on;
        server_name play.peaknine.io;

        # SSL 인증서 설정 (파일명 수정됨)
        ssl_certificate /etc/nginx/ssl/wildcard_peaknine_io.crt;
        ssl_certificate_key /etc/nginx/ssl/wildcard_peaknine_io.key;

        # SSL 보안 설정
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        # 보안 헤더
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;

        client_max_body_size 100M;

        # n8n 에디터 및 API
        location / {
            proxy_pass http://n8n;
            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;
            proxy_set_header X-Forwarded-Host $host;
            
            proxy_connect_timeout 300;
            proxy_send_timeout 300;
            proxy_read_timeout 300;
            
            proxy_buffering off;
            proxy_cache off;
        }

        # Webhook 경로
        location /webhook/ {
            proxy_pass http://n8n;
            proxy_http_version 1.1;
            
            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;
            
            proxy_connect_timeout 600;
            proxy_send_timeout 600;
            proxy_read_timeout 600;
        }

        location /webhook-test/ {
            proxy_pass http://n8n;
            proxy_http_version 1.1;
            
            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;
        }
    }
}

nginx.conf 상세 주석 버전

#=============================================================================
# NGINX 설정 파일 (nginx.conf)
# 용도: n8n 워크플로우 자동화 플랫폼을 위한 SSL 리버스 프록시
# 도메인: play.peaknine.io
#=============================================================================

#-----------------------------------------------------------------------------
# events 블록
# - Nginx의 연결 처리 방식을 설정하는 영역입니다.
# - 주로 동시 접속자 수와 관련된 설정을 합니다.
#-----------------------------------------------------------------------------
events {
    # worker_connections: 하나의 워커 프로세스가 동시에 처리할 수 있는 최대 연결 수
    # - 1024는 일반적인 소규모~중규모 서비스에 적합한 값입니다.
    # - 총 동시 접속 가능 수 = worker_processes × worker_connections
    # - 예: 워커 4개 × 1024 = 최대 4,096개 동시 연결
    worker_connections 1024;
}

#-----------------------------------------------------------------------------
# http 블록
# - HTTP/HTTPS 서버 설정의 최상위 컨테이너입니다.
# - 이 안에 upstream, server, location 등이 포함됩니다.
#-----------------------------------------------------------------------------
http {

    #-------------------------------------------------------------------------
    # upstream 블록
    # - 백엔드 서버(프록시 대상)를 정의합니다.
    # - 여러 서버를 등록하면 로드밸런싱도 가능합니다.
    #-------------------------------------------------------------------------
    upstream n8n {
        # server: 프록시할 백엔드 서버의 주소와 포트
        # - "n8n"은 docker-compose.yml에서 정의한 서비스 이름입니다.
        # - Docker 네트워크 내에서 서비스 이름으로 통신할 수 있습니다.
        # - 5678은 n8n의 기본 포트입니다.
        server n8n:5678;
        
        # 로드밸런싱 예시 (n8n 다중 인스턴스 운영 시):
        # server n8n-1:5678 weight=3;  # 가중치 3 (더 많은 요청 처리)
        # server n8n-2:5678 weight=1;  # 가중치 1
    }

    #-------------------------------------------------------------------------
    # HTTP 서버 블록 (포트 80)
    # - HTTP 요청을 받아서 HTTPS로 리다이렉트하는 역할입니다.
    # - 보안을 위해 모든 HTTP 트래픽을 HTTPS로 강제 전환합니다.
    #-------------------------------------------------------------------------
    server {
        # listen: 이 서버 블록이 수신할 포트 번호
        # - 80은 HTTP의 기본 포트입니다.
        listen 80;

        # server_name: 이 서버 블록이 처리할 도메인 이름
        # - 요청의 Host 헤더와 매칭되는 서버 블록이 처리합니다.
        # - 여러 도메인을 공백으로 구분하여 나열할 수 있습니다.
        # - 예: server_name play.peaknine.io api.peaknine.io;
        server_name play.peaknine.io;

        # return: 클라이언트에게 응답 코드와 함께 리다이렉트
        # - 301: 영구 리다이렉트 (브라우저가 캐시함, SEO에 유리)
        # - 302: 임시 리다이렉트 (브라우저가 캐시하지 않음)
        # - $server_name: 현재 server_name 값 (play.peaknine.io)
        # - $request_uri: 요청된 URI 경로 (/webhook/xxx 등)
        # 
        # 예시: http://play.peaknine.io/webhook/test 
        #       → https://play.peaknine.io/webhook/test
        return 301 https://$server_name$request_uri;
    }

    #-------------------------------------------------------------------------
    # HTTPS 서버 블록 (포트 443)
    # - SSL/TLS 암호화된 연결을 처리하는 메인 서버입니다.
    # - 실제 n8n 서비스로 프록시하는 핵심 설정이 여기 있습니다.
    #-------------------------------------------------------------------------
    server {
        # listen 443 ssl http2: HTTPS 연결 수신 설정
        # - 443: HTTPS의 기본 포트
        # - ssl: SSL/TLS 암호화 활성화
        # - http2: HTTP/2 프로토콜 활성화 (더 빠른 성능, 멀티플렉싱 지원)
        listen 443 ssl http2;

        # server_name: HTTPS로 서비스할 도메인
        server_name play.peaknine.io;

        #---------------------------------------------------------------------
        # SSL 인증서 설정
        # - Nginx가 HTTPS 통신에 사용할 인증서와 개인키를 지정합니다.
        #---------------------------------------------------------------------
        
        # ssl_certificate: SSL 인증서 파일 경로
        # - 도메인 인증서 + 중간 인증서(체인)가 합쳐진 파일이어야 합니다.
        # - 순서: [도메인 인증서] → [중간 인증서] → [루트 인증서(선택)]
        # - .all.crt.pem 파일은 이미 체인이 포함되어 있습니다.
        ssl_certificate /etc/nginx/ssl/wildcard_peaknine_io.crt;

        # ssl_certificate_key: SSL 개인키 파일 경로
        # - 인증서와 쌍을 이루는 비밀키입니다.
        # - 절대 외부에 노출되면 안 됩니다!
        # - 권한은 600 (소유자만 읽기/쓰기)으로 설정하세요.
        ssl_certificate_key /etc/nginx/ssl/wildcard_peaknine_io.key;

        #---------------------------------------------------------------------
        # SSL 보안 설정
        # - 암호화 프로토콜과 알고리즘을 설정합니다.
        # - 오래된/취약한 프로토콜을 비활성화하여 보안을 강화합니다.
        #---------------------------------------------------------------------

        # ssl_protocols: 허용할 TLS 버전
        # - TLSv1.0, TLSv1.1: 보안 취약점이 있어 비활성화 권장
        # - TLSv1.2: 현재 가장 널리 사용되는 안전한 버전
        # - TLSv1.3: 최신 버전, 더 빠르고 안전함
        ssl_protocols TLSv1.2 TLSv1.3;

        # ssl_ciphers: 사용할 암호화 알고리즘 목록
        # - ECDHE: 타원곡선 디피-헬만 키 교환 (전방 비밀성 제공)
        # - AES-GCM: 고급 암호화 표준 (빠르고 안전함)
        # - SHA256/SHA384: 해시 알고리즘
        # - 콜론(:)으로 구분하여 우선순위 순으로 나열
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

        # ssl_prefer_server_ciphers: 서버 측 암호화 알고리즘 우선 사용
        # - on: 서버가 지정한 순서대로 알고리즘 선택 (권장)
        # - off: 클라이언트가 선호하는 알고리즘 사용
        ssl_prefer_server_ciphers on;

        # ssl_session_cache: SSL 세션 캐시 설정
        # - shared:SSL:10m: 모든 워커가 공유하는 10MB 캐시
        # - 1MB당 약 4,000개 세션 저장 가능
        # - 재접속 시 핸드셰이크를 건너뛰어 성능 향상
        ssl_session_cache shared:SSL:10m;

        # ssl_session_timeout: SSL 세션 유효 시간
        # - 10m: 10분간 세션 재사용 가능
        # - 적절한 값으로 설정하면 성능과 보안의 균형 유지
        ssl_session_timeout 10m;

        #---------------------------------------------------------------------
        # 보안 HTTP 헤더
        # - 브라우저에게 보안 관련 지시를 내리는 헤더들입니다.
        # - 다양한 웹 공격을 방어하는 데 도움이 됩니다.
        #---------------------------------------------------------------------

        # Strict-Transport-Security (HSTS): HTTPS 강제 사용 헤더
        # - max-age=31536000: 1년간 이 사이트는 HTTPS로만 접속
        # - includeSubDomains: 모든 서브도메인에도 적용
        # - 브라우저가 이 헤더를 받으면 HTTP 접속 시도 자체를 차단
        # - always: 에러 응답에도 이 헤더 포함
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

        # X-Frame-Options: 클릭재킹 공격 방지
        # - DENY: 어떤 사이트에서도 이 페이지를 iframe으로 삽입 불가
        # - SAMEORIGIN: 같은 도메인에서만 iframe 허용
        # - 악성 사이트가 n8n 페이지를 숨겨서 클릭 유도하는 것을 방지
        add_header X-Frame-Options DENY;

        # X-Content-Type-Options: MIME 타입 스니핑 방지
        # - nosniff: 브라우저가 Content-Type을 무시하고 추측하지 않음
        # - 악성 스크립트가 다른 파일 타입으로 위장하는 것을 방지
        add_header X-Content-Type-Options nosniff;

        #---------------------------------------------------------------------
        # 요청 크기 제한
        #---------------------------------------------------------------------

        # client_max_body_size: 클라이언트가 업로드할 수 있는 최대 크기
        # - 기본값은 1MB로 매우 작음
        # - n8n에서 파일 업로드 워크플로우 사용 시 늘려야 함
        # - 100M: 최대 100MB 파일 업로드 허용
        client_max_body_size 100M;

        #---------------------------------------------------------------------
        # location 블록: / (루트 - 모든 요청)
        # - n8n 에디터 UI 및 일반 API 요청을 처리합니다.
        # - WebSocket 연결도 이 블록에서 처리합니다.
        #---------------------------------------------------------------------
        location / {
            # proxy_pass: 요청을 전달할 백엔드 서버
            # - http://n8n: 위에서 정의한 upstream 이름
            # - 모든 요청이 n8n 컨테이너의 5678 포트로 전달됨
            proxy_pass http://n8n;

            # proxy_http_version: 백엔드와 통신할 HTTP 버전
            # - 1.1: HTTP/1.1 사용 (keep-alive, WebSocket 지원에 필요)
            # - 기본값 1.0은 WebSocket을 지원하지 않음
            proxy_http_version 1.1;
            
            #-----------------------------------------------------------------
            # WebSocket 지원 설정 (n8n 에디터 필수!)
            # - n8n 에디터는 실시간 업데이트를 위해 WebSocket을 사용합니다.
            # - 이 설정이 없으면 에디터가 제대로 작동하지 않습니다.
            #-----------------------------------------------------------------

            # Upgrade 헤더: WebSocket 프로토콜 업그레이드 요청 전달
            # - $http_upgrade: 클라이언트가 보낸 Upgrade 헤더 값
            # - WebSocket 연결 시 "websocket" 값이 들어옴
            proxy_set_header Upgrade $http_upgrade;

            # Connection 헤더: 연결 유지 방식 설정
            # - "upgrade": Upgrade 헤더와 함께 사용하여 WebSocket 전환
            # - 일반 HTTP 요청 시에도 문제없이 작동함
            proxy_set_header Connection "upgrade";
            
            #-----------------------------------------------------------------
            # 프록시 헤더 설정
            # - 백엔드(n8n)에게 원본 요청 정보를 전달합니다.
            # - 이 정보가 없으면 n8n은 모든 요청이 Nginx에서 온 것으로 인식
            #-----------------------------------------------------------------

            # Host: 원본 요청의 도메인 이름 전달
            # - $host: 클라이언트가 요청한 호스트 (play.peaknine.io)
            # - n8n이 올바른 URL을 생성하는 데 필요
            proxy_set_header Host $host;

            # X-Real-IP: 실제 클라이언트 IP 주소
            # - $remote_addr: 요청을 보낸 클라이언트의 실제 IP
            # - 로그 분석, 접근 제어 등에 사용
            proxy_set_header X-Real-IP $remote_addr;

            # X-Forwarded-For: 프록시 체인을 거친 IP 목록
            # - $proxy_add_x_forwarded_for: 기존 값에 현재 클라이언트 IP 추가
            # - 여러 프록시를 거치면: "클라이언트IP, 프록시1IP, 프록시2IP"
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            # X-Forwarded-Proto: 원본 요청의 프로토콜
            # - $scheme: http 또는 https
            # - n8n이 HTTPS로 서비스되고 있음을 인식하게 함
            # - Webhook URL 생성 시 https://로 시작하도록 함
            proxy_set_header X-Forwarded-Proto $scheme;

            # X-Forwarded-Host: 원본 요청의 호스트
            # - $host: 클라이언트가 요청한 호스트명
            # - Host 헤더와 유사하지만 프록시 환경임을 명시
            proxy_set_header X-Forwarded-Host $host;
            
            #-----------------------------------------------------------------
            # 타임아웃 설정
            # - 오래 걸리는 워크플로우 실행을 위해 충분히 길게 설정
            #-----------------------------------------------------------------

            # proxy_connect_timeout: 백엔드 연결 시도 제한 시간
            # - 300초(5분) 내에 연결되지 않으면 504 에러 반환
            proxy_connect_timeout 300;

            # proxy_send_timeout: 백엔드로 데이터 전송 제한 시간
            # - 300초 동안 데이터 전송이 없으면 연결 종료
            proxy_send_timeout 300;

            # proxy_read_timeout: 백엔드 응답 대기 제한 시간
            # - 300초 동안 응답이 없으면 504 Gateway Timeout 반환
            # - 오래 걸리는 워크플로우는 이 값을 더 늘려야 할 수 있음
            proxy_read_timeout 300;
            
            #-----------------------------------------------------------------
            # 버퍼링 설정
            # - 실시간 스트리밍과 WebSocket에 최적화된 설정
            #-----------------------------------------------------------------

            # proxy_buffering: 응답 버퍼링 비활성화
            # - off: 백엔드 응답을 즉시 클라이언트에게 전달
            # - 실시간 로그 스트리밍, Server-Sent Events에 필요
            # - on이면 응답을 모아서 한번에 전달 (일반 웹사이트에 적합)
            proxy_buffering off;

            # proxy_cache: 응답 캐싱 비활성화
            # - off: 매 요청마다 백엔드에서 새로운 응답을 받음
            # - n8n 에디터는 실시간 데이터가 중요하므로 캐시 불필요
            proxy_cache off;
        }

        #---------------------------------------------------------------------
        # location 블록: /webhook/
        # - n8n Webhook 요청을 처리합니다.
        # - 외부 서비스(Slack, GitHub 등)에서 호출하는 엔드포인트입니다.
        # - 예: https://play.peaknine.io/webhook/abc-123-xyz
        #---------------------------------------------------------------------
        location /webhook/ {
            proxy_pass http://n8n;
            proxy_http_version 1.1;
            
            # 기본 프록시 헤더 (위와 동일)
            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;
            
            # Webhook은 처리 시간이 길 수 있으므로 타임아웃을 더 길게 설정
            # - 600초(10분): 복잡한 워크플로우도 충분히 처리 가능
            # - 예: 대용량 파일 처리, 외부 API 다중 호출 등
            proxy_connect_timeout 600;
            proxy_send_timeout 600;
            proxy_read_timeout 600;
        }

        #---------------------------------------------------------------------
        # location 블록: /webhook-test/
        # - n8n 에디터에서 "Test Webhook" 버튼 클릭 시 사용됩니다.
        # - 워크플로우를 저장하지 않고 테스트할 때 사용하는 임시 엔드포인트입니다.
        # - 프로덕션 Webhook과 URL 경로가 다릅니다.
        #---------------------------------------------------------------------
        location /webhook-test/ {
            proxy_pass http://n8n;
            proxy_http_version 1.1;
            
            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;
            
            # 테스트용이므로 타임아웃은 기본값 사용해도 무방
            # 필요시 위 /webhook/과 동일하게 늘릴 수 있음
        }
    }
}

 

주요 개념 요약표

지시문 역할 중요도
upstream 백엔드 서버 그룹 정의
listen 443 ssl HTTPS 포트 수신
ssl_certificate 인증서 파일 경로
proxy_pass 요청 전달 대상
proxy_set_header Upgrade WebSocket 지원
proxy_set_header X-Forwarded-Proto HTTPS 인식
ssl_protocols TLS 버전 제한
proxy_read_timeout 응답 대기 시간
add_header HSTS 보안 헤더

 

자주 수정하는 설정들

상황 수정할 설정
큰 파일 업로드 필요 client_max_body_size 500M;
워크플로우 실행이 오래 걸림 proxy_read_timeout 1800; (30분)
다른 서브도메인 추가 server_name 및 새 server 블록 추가
IP 접근 제한 allow 192.168.1.0/24; deny all;
로그 상세화 access_log, error_log 설정 추가

로그 상세화

로그 설정

http { } 블록 시작 부분에 다음을 추가합니다.

#-------------------------------------------------------------------------
    # 로그 설정
    # - 접속 로그와 에러 로그를 설정합니다.
    # - Docker 환경에서는 /var/log/nginx/ 또는 stdout/stderr 사용
    #-------------------------------------------------------------------------

    # 로그 포맷 정의: 'main' (기본 포맷)
    # - $remote_addr: 클라이언트 IP
    # - $remote_user: 인증된 사용자명 (없으면 -)
    # - $time_local: 접속 시간 (로컬 타임존)
    # - $request: 요청 라인 (예: "GET /webhook/test HTTP/1.1")
    # - $status: HTTP 응답 코드 (200, 404, 500 등)
    # - $body_bytes_sent: 응답 본문 크기 (바이트)
    # - $http_referer: 이전 페이지 URL (없으면 -)
    # - $http_user_agent: 브라우저/클라이언트 정보
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent"';

    # 로그 포맷 정의: 'detailed' (상세 포맷 - 디버깅용)
    # - $request_time: 요청 처리 시간 (초)
    # - $upstream_response_time: 백엔드(n8n) 응답 시간
    # - $upstream_addr: 백엔드 서버 주소
    # - $ssl_protocol: SSL/TLS 버전 (TLSv1.2, TLSv1.3)
    # - $ssl_cipher: 사용된 암호화 알고리즘
    log_format detailed '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" '
                        'rt=$request_time uct=$upstream_connect_time '
                        'uht=$upstream_header_time urt=$upstream_response_time '
                        'upstream=$upstream_addr ssl=$ssl_protocol/$ssl_cipher';

    # 접속 로그 (Access Log)
    # - /var/log/nginx/access.log: 로그 파일 경로
    # - main: 위에서 정의한 로그 포맷 사용
    # - buffer=32k: 32KB 버퍼에 모았다가 한번에 기록 (성능 향상)
    # - flush=5s: 5초마다 버퍼를 비우고 디스크에 기록
    access_log /var/log/nginx/access.log main buffer=32k flush=5s;

    # 에러 로그 (Error Log)
    # - /var/log/nginx/error.log: 로그 파일 경로
    # - warn: 로그 레벨 (debug < info < notice < warn < error < crit)
    #   - debug: 모든 디버그 정보 (매우 상세, 디스크 많이 사용)
    #   - info: 정보성 메시지 포함
    #   - notice: 주목할 만한 이벤트
    #   - warn: 경고 메시지 (권장)
    #   - error: 에러만 기록
    #   - crit: 심각한 에러만 기록
    error_log /var/log/nginx/error.log warn;

error-log 로그 레벨 종류 

# 레벨이 낮을수록 더 많은 로그 출력 (위 → 아래로 심각도 증가)
error_log /var/log/nginx/error.log debug;   # 모든 디버그 정보 (매우 상세)
error_log /var/log/nginx/error.log info;    # 정보성 메시지 포함
error_log /var/log/nginx/error.log notice;  # 주목할 만한 이벤트
error_log /var/log/nginx/error.log warn;    # 경고 메시지 (권장)
error_log /var/log/nginx/error.log error;   # 에러만 기록
error_log /var/log/nginx/error.log crit;    # 심각한 에러만
error_log /var/log/nginx/error.log alert;   # 즉시 조치 필요
error_log /var/log/nginx/error.log emerg;   # 시스템 사용 불가
```

---

## error_log는 고정 형식입니다

Nginx가 자동으로 다음 형식으로 기록합니다:
```
[날짜/시간] [레벨] [프로세스ID#스레드ID]: *연결번호 메시지
```

**실제 출력 예시:**
```
2025/12/15 14:30:22 [warn] 7#7: *1 upstream server temporarily disabled
2025/12/15 14:30:45 [error] 7#7: *15 connect() failed (111: Connection refused)
2025/12/15 14:31:00 [crit] 7#7: *20 SSL_do_handshake() failed

docker-compose.yml 로그 볼륨 추가

로그 파일을 호스트에서도 볼 수 있도록 볼륨을 추가합니다:

  nginx:
    image: nginx:alpine
    container_name: n8n-play-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
      # 로그 디렉토리 마운트 (추가)
      # - 호스트: ~/docker/n8n-play/logs/nginx/
      # - 컨테이너: /var/log/nginx/
      - ./logs/nginx:/var/log/nginx
    depends_on:
      - n8n
    networks:
      - n8n-network

 

로그 디렉토리 생성

# 로그 디렉토리 생성
mkdir -p ~/docker/n8n-play/logs/nginx

 

로그 확인 명령어

# 실시간 접속 로그 확인
tail -f ~/docker/n8n-play/logs/nginx/access.log

# 실시간 에러 로그 확인
tail -f ~/docker/n8n-play/logs/nginx/error.log

# 또는 Docker 명령어로 직접 확인
docker logs -f n8n-play-nginx

 

로그 출력 예시

access.log (main 포맷):

61.85.213.191 - - [15/Dec/2025:14:30:22 +0900] "GET / HTTP/2.0" 200 4532 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
61.85.213.191 - - [15/Dec/2025:14:30:25 +0900] "POST /webhook/abc123 HTTP/2.0" 200 128 "-" "GitHub-Hookshot/abc1234"

error.log:

2025/12/15 14:30:22 [warn] 7#7: *1 upstream server temporarily disabled while connecting to upstream
2025/12/15 14:30:45 [error] 7#7: *15 connect() failed (111: Connection refused) while connecting to upstream

 

선택사항: 상세 로그로 전환하기

디버깅이 필요할 때 detailed 포맷으로 변경할 수 있습니다.:

# main → detailed로 변경
access_log /var/log/nginx/access.log detailed buffer=32k flush=5s;

# 에러 로그 레벨을 info로 낮춤 (더 많은 정보)
error_log /var/log/nginx/error.log info;

디버깅 완료 후 다시 main과 warn으로 복구하는 것을 권장합니다! 


Nginx 웹서버가 필요한 이유

SSL 종료 (SSL Termination)

[클라이언트] ←──HTTPS(암호화)──→ [Nginx] ←──HTTP(평문)──→ [n8n]
  • n8n 컨테이너는 기본적으로 HTTP만 지원합니다
  • Nginx가 HTTPS 암호화/복호화를 전담하고, n8n에는 일반 HTTP로 전달하게 됩니다.
  • 인증서 관리가 한 곳(Nginx)에서만 이루어져 관리가 단순해지는 장점이 있습니다.

WebSocket 프록시 지원

n8n 에디터는 실시간 WebSocket 연결이 필수입니다. 웹 소켓 연결에서는 다음과 같은 작업이 수행됩니다.

  • 워크플로우 실행 상태 실시간 업데이트
  • 에디터 UI의 실시간 동기화
  • 테스트 실행 결과 즉시 반영

Nginx가 WebSocket 업그레이드를 올바르게 처리해줍니다:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

보안 강화

기능 설명
HTTP → HTTPS 강제 리다이렉트 실수로 HTTP 접속해도 자동 전환
보안 헤더 추가 HSTS, X-Frame-Options 등
n8n 포트 비노출 5678 포트를 외부에 직접 노출하지 않음
TLS 버전/암호화 제어 취약한 프로토콜 차단

유연한 확장성

향후 서비스 확장 시 Nginx 설정만 수정하면 됩니다 : 

  • 로드밸런싱 (n8n 다중 인스턴스)
  • Rate Limiting (API 호출 제한)
  • IP 화이트리스트/블랙리스트
  • 캐싱
  • 다른 서비스 추가 (예: api.peaknine.io → 다른 컨테이너)

Nginx 없이 가능한가?

기술적으로 가능은 하지만 권장하지 않습니다:

방법 1: n8n 직접 SSL 설정 (비권장)

# docker-compose.yml에서 n8n에 직접 인증서 마운트
environment:
  - N8N_SSL_CERT=/ssl/wildcard_peaknine_io.crt
  - N8N_SSL_KEY=/ssl/wildcard_peaknine_io.key
volumes:
  - ./ssl:/ssl:ro
ports:
  - "443:5678"

 

문제점:

  • n8n의 SSL 지원이 제한적
  • 인증서 갱신 시 n8n 재시작 필요
  • WebSocket 관련 이슈 발생 가능
  • 보안 헤더 추가 불가

방법 2: Traefik 사용 (대안)

Nginx 대신 Traefik을 사용할 수도 있지만, 설정이 더 복잡합니다. Nginx의 리버스 프록시를 사용하는 것을 권장합니다.

최종 디렉토리 구조

n8n-play/
├── docker-compose.yml
├── ssl/
│   ├── wildcard_peaknine_io.crt    # .all.crt.pem 복사본
│   └── wildcard_peaknine_io.key    # .key.pem 복사본
├── nginx/
│   └── nginx.conf
└── n8n-data/			# n8n 데이터의 영속적 저장을 위해서 필요
				# 다른 디렉토리 '~/docker/n8n/data로 지정할 수도 있음

6. docker-compose.yml 생성

이제 프로젝트 'n8n-play'를 Docker위에서 온전하게 실행될 수 있게 하는 콘테이너에 관련된 설정파일인 'docker-compose.yml'파일을 생성하는 방법을 살펴 보겠습니다.

cd ~/Docker/n8n-play	# n8n-play 프로젝트의 기본 폴더, 사용자의 폴더 구조에 맞게 변경

 

docker-compose.yml 파일을 생성

version: '3.8'	# 시스템 경고가 있는 경우 주석처리할 수 있음

services:
  # Nginx 리버스 프록시
  nginx:
    image: nginx:alpine
    container_name: n8n-play-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      #---------------------------------------------------------------------
      # 볼륨 마운트 설명
      # 형식: [호스트 경로]:[컨테이너 경로]:[옵션]
      #---------------------------------------------------------------------
      
      # nginx.conf 마운트
      # - 호스트: ~/docker/n8n-play/nginx/nginx.conf
      # - 컨테이너: /etc/nginx/nginx.conf
      # - :ro = 읽기 전용 (컨테이너가 수정 불가)
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      
      # SSL 인증서 마운트
      # - 호스트: ~/docker/n8n-play/ssl/
      # - 컨테이너: /etc/nginx/ssl/
      # - 컨테이너 안의 Nginx는 /etc/nginx/ssl/ 경로로 접근
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - n8n
    networks:
      - n8n-network

  # n8n 서비스
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n-play-n8n-1
    restart: unless-stopped
    environment:
      # 기본 설정
      - N8N_HOST=play.peaknine.io
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://play.peaknine.io/
      
      # 프록시 뒤에서 실행 설정 (중요!)
      # N8N_PROXY_HOPS: 프록시 홉 수
      # - Nginx 리버스 프록시를 거치므로 1로 설정
      # - X-Forwarded-* 헤더를 신뢰하도록 함
      - N8N_PROXY_HOPS=1
      
      # 에디터 설정
      - N8N_EDITOR_BASE_URL=https://play.peaknine.io/
      
      # 타임존 설정
      - GENERIC_TIMEZONE=Asia/Seoul
      - TZ=Asia/Seoul
      
      # 보안 설정 (선택사항 - 기본 인증)
      # - N8N_BASIC_AUTH_ACTIVE=true
      # - N8N_BASIC_AUTH_USER=admin
      # - N8N_BASIC_AUTH_PASSWORD=your_secure_password
      
      # N8N_ENCRYPTION_KEY: 자격 증명 암호화 키
      # - 자격 증명(API 키, OAuth 토큰 등)을 암호화하는 데 사용
      # - 반드시 고정값으로 설정해야 컨테이너 재생성 후에도 복호화 가능
      # - 생성 방법: openssl rand -hex 16
      # - 아래 값은 예시입니다. 직접 생성한 값으로 교체하세요!
      - N8N_ENCRYPTION_KEY=your_random_32_character_string_here
      
      # N8N_SECURE_COOKIE: 쿠키 보안 설정
      # - true: HTTPS에서만 쿠키 전송 (HTTPS 환경에서 권장)
      # - false: HTTP에서도 쿠키 전송 (개발 환경용)
      - N8N_SECURE_COOKIE=true
      
      # 실행 모드
      - N8N_RUNNERS_DISABLED=false
      - EXECUTIONS_MODE=regular
      
      # 로그 레벨
      - N8N_LOG_LEVEL=info
    volumes:
      - ./n8n-data:/home/node/.n8n
    networks:
      - n8n-network
    # 내부 포트만 노출 (외부 직접 접근 차단)
    expose:
      - "5678"

networks:
  n8n-network:
    driver: bridge

 

N8N_ENCRYPTION_KEY (! important)

- N8N_ENCRYPTION_KEY=your_random_32_character_string_here
항목 설명
용도 n8n이 저장하는 자격 증명(Credentials)을 암호화하는 키
암호화 대상 API 키, OAuth 토큰, 비밀번호, 데이터베이스 연결 정보 등
미설정 시 문제 n8n이 자동 생성하지만, 컨테이너 재생성 시 키가 바뀌어 기존 자격 증명 복호화 불가
권장 직접 생성하여 고정값으로 설정

 

암호화 키 생성 방법:

# 방법 1: openssl 사용 (32자 헥사 문자열)
openssl rand -hex 16

# 출력 예시: 256bf84bdc1c32c1031fc4a88933a61f

# 방법 2: Node.js 사용
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"

# 방법 3: Python 사용
python3 -c "import secrets; print(secrets.token_hex(16))"

 

중요 주의사항

주의 설명
키 백업 필수 N8N_ENCRYPTION_KEY를 분실하면 기존 자격 증명 영구 복구 불가
키 노출 금지 Git에 커밋하지 마세요! .env 파일로 분리 권장
키 변경 금지 한번 설정 후 변경하면 기존 자격 증명 복호화 불가

 

선택사항: .env 파일로 분리 (보안 강화)

보안성이 강하고 민감한 Key관련 정보는 별도로 분리하여 보관하는 것이 안전합니다. 또한 git에 공유되는 것을 방지하기 위해 .gitignore에도 등록해서 공유되는 일이 없도록 하는 것이 좋습니다.

를 별도 파일로 관리:

 

.env 파일 생성:

# ~/docker/n8n-play/.env
N8N_ENCRYPTION_KEY=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

 

docker-compose.yml 수정:

environment:
  - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
  # ... 나머지 설정

 

.gitignore에 추가:

.env

 

N8N_SECURE_COOKIE

- N8N_SECURE_COOKIE=true   # HTTPS 사용 시 (Denny 환경)
- N8N_SECURE_COOKIE=false  # HTTP만 사용 시

 

항목 설명
용도 세션 쿠키의 Secure 플래그 설정
true HTTPS 연결에서만 쿠키 전송 (보안 강화)
false HTTP에서도 쿠키 전송 허용

 

 


7. 라우터 포트 포워딩 확인

Iptime T16000M 라우터에서 다음 포트 포워딩을 설정합니다. 대부분의 공유기에서 포트포워딩을 제공하므로, 해당 공유기(라우터)에서의 기본 방법을 적용합니다.

외부포트 내부포트 내부 IP 프로토콜
443 443 Mac Mini 내부 IP TCP
80 80 Mac Mini 내부 IP TCP

 

Mac Mini의 내부 IP 확인:

# Mac 내부 IP 확인
ipconfig getifaddr en0  # Wi-Fi
# 또는
ipconfig getifaddr en1  # Ethernet

8. DNS 설정 확인

n8n-play프로젝트 서비스에 사용할 커스텀 도메인 'play.peaknine.io'가 외부 IP '61.85.213.***'로 정상적으로 연결되는지 확인합니다:

# DNS 조회
nslookup play.peaknine.io
dig play.peaknine.io

# 예상 결과
# play.peaknine.io → 61.85.213.***

> dig play.peaknine.io                                                                                                 ✔ │ 12:17:55

; <<>> DiG 9.10.6 <<>> play.peaknine.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39968
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;play.peaknine.io.		IN	A

;; ANSWER SECTION:
play.peaknine.io.	3600	IN	A	61.85.213.***

;; Query time: 185 msec
;; SERVER: 168.126.63.1#53(168.126.63.1)
;; WHEN: Mon Dec 15 12:18:35 KST 2025
;; MSG SIZE  rcvd: 61

'> dig play.peaknine.io'의 DNS설정 상태를 보면 A레코드로 외부 공인IP로 정상적으로 연결되고 있음을 확인할 수 있습니다.


9.  Docker 컨테이너 실행

이제 'n8n-play의 프로젝트와 관련된 설정이 모두 완료되었습니다. Docker 컨테이너가 정상적으로 실행되는지 테스트해볼 차례입니다.

cd ~/Projects/n8n-play

# 기존 컨테이너 정리 (있다면)
docker-compose down

# 컨테이너 빌드 및 실행
docker-compose up -d

# 로그 확인
docker-compose logs -f

# 각 컨테이너 상태 확인
docker-compose ps

 

정상적으로 컨테이너가 실행된다면, 아래와 같은 콘솔 메시지를 확인할 수 있습니다. 특히, 리버스 프록시 설정을 위해 새롭게 추가된 'nginx'의 경우 자동으로 새롭게 이미지를 다운로드 받고 컨테이너를 생성하는 것을 확인 할 수 있습니다.

# Project n8n-play docker 실행하기
docker-compose up -d                                   
[+] Running 11/11
 ✔ nginx Pulled   # nginx의 경우 새롭게 설정되는 컨테이너로 이미지를 다운로드 합니다                                                                                                                  9.4s
   ✔ 9afea992cb63 Pull complete                        1.4s
   ✔ 223f9efb3159 Pull complete                        0.9s
   ✔ 2422bf732277 Pull complete                        0.9s
   ✔ e350a0f963e3 Pull complete                        0.9s
   ✔ e6b7df06dc76 Pull complete                        0.9s
   ✔ 0bd713040ebb Pull complete                        1.1s
   ✔ 29b7c987abf6 Pull complete                        0.9s
   ✔ d85e26d0c84d Pull complete                        0.8s
   ✔ 81eecc9295df Download complete                    0.0s
   ✔ e1779b8a5a13 Download complete                    0.0s
[+] Running 3/3
 ✔ Network n8n-play_n8n-network  Created               0.0s
 ✔ Container n8n-play-n8n-1      Started               0.2s
 ✔ Container n8n-play-nginx      Started

 

최종적으로 서비스가 정상적으로 실행되고 있는지 Docker Desktop에서 확인해 봅니다.

n8n-play 프로젝트에 n8n-1과 nginx 컨테이너가 정상적으로 실행되고 있습니다
n8n-play 프로젝트에 n8n-1과 nginx 컨테이너가 정상적으로 실행되고 있습니다


10. 서비스 테스트

로컬 테스트

외부에서의 실질적인 서비스 접근을 테스트하기 전에 로컬 네트워크에서의 서비스 정상 동작여부를 먼저 테스트하는 것을 추천드립니다. 외부에서의 접근은 네트워크 설정인 DNS, Port Forwarding 및 HTTPS SSL 접근 등 다양한 네트워크 이슈가 발생할 수 있습니다. 내부에서부터 설정 결과를 하나씩 진행하는 것이 서비스 트러블을 단계적으로 해결할 수 있는 좋은 습관입니다.

# HTTPS 연결 테스트
curl -v https://play.peaknine.io

# SSL 인증서 확인
openssl s_client -connect play.peaknine.io:443 -servername play.peaknine.io

# Webhook 엔드포인트 테스트
curl -X POST https://play.peaknine.io/webhook-test/test \
  -H "Content-Type: application/json" \
  -d '{"test": "hello"}'

HTTPS 연결 테스트 결과  |  curl -v https://play.peaknine.io 

HTTPS연결 테스트 결과 아래의 절차에 따라 정상적으로 HTTPS서비스가 시작되고 있음을 확인할 수 있습니다.

  • play.peaknine.io:443이 올바른  DNS서비스에 따라 서버 IP의 443 포트로 정상적으로 연결됩니다.
  • SSL HTTPS보안 프로토콜 TLS의 핸드쉐이킹 연결과정이 정상적으로 진행됩니다. (SSL의 연결은 TLSv1.3 프로토콜로 연결됩니다.)
  • HTTP/2 웹 프로토콜을 이용해 n8n 서버인 'https://play.peaknine.io'로 연결을 시도합니다.
  • 정상적인 보안정책에 대한 https header값을 설정합니다.
  • n8n서비스의 정상적인 페이지가 로딩됩니다. 
curl -v https://play.peaknine.io                                                               ✔ │ 11s │ 12:22:15

* Host play.peaknine.io:443 was resolved.
* IPv6: (none)
* IPv4: 61.85.213.191
*   Trying 61.85.213.191:443...
* Connected to play.peaknine.io (61.85.213.191) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=*.peaknine.io
*  start date: Dec 11 00:00:00 2025 GMT
*  expire date: Dec 11 23:59:59 2026 GMT
*  subjectAltName: host "play.peaknine.io" matched cert's "*.peaknine.io"
*  issuer: C=GB; O=Sectigo Limited; CN=Sectigo Public Server Authentication CA DV R36
*  SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://play.peaknine.io/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: play.peaknine.io]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: play.peaknine.io
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< server: nginx/1.29.4
< date: Mon, 15 Dec 2025 03:28:21 GMT
< content-type: text/html; charset=utf-8
< content-length: 14531
< cache-control: no-cache, no-store, must-revalidate, proxy-revalidate
< cross-origin-opener-policy: same-origin
< cross-origin-resource-policy: same-origin
< origin-agent-cluster: ?1
< referrer-policy: no-referrer
< x-content-type-options: nosniff
< x-frame-options: SAMEORIGIN
< x-permitted-cross-domain-policies: none
< x-xss-protection: 0
< accept-ranges: bytes
< etag: W/"38c3-19b2007bee0"
< vary: Accept-Encoding
< strict-transport-security: max-age=31536000; includeSubDomains
< x-frame-options: DENY
< x-content-type-options: nosniff
<
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width,initial-scale=1.0" />
		<link rel="icon" href="/favicon.ico" />
		<meta name="n8n:config:rest-endpoint" content="cmVzdA=="><meta name="n8n:config:sentry" content="eyJkc24iOiIiLCJlbnZpcm9ubWVudCI6ImRldmVsb3BtZW50IiwicmVsZWFzZSI6Im44bkAxLjEyMy41In0=">
		<link rel="stylesheet" href="/static/prefers-color-scheme.css">
		<script src="/static/base-path.js" type="text/javascript"></script>
		<script src="/static/posthog.init.js" type="text/javascript"></script>

브라우저 접속

실질적인 서비스 정상확인을 위해서 내부 또는 외부의 네트워크에서 n8n 에디터와 n8n webhook URL을 브라우저를 통해서 시험해봅니다. 웹훅 URL을 시험하기 위해서는 테스트 워크플로우를 생성한 뒤에 시험하는 것을 권장합니다.

브라우저를 통한 HTTPS 접속이 성공되어 n8n 에디터 서비스가 정상적으로 연결되었습니다
브라우저를 통한 HTTPS 접속이 성공되어 n8n 에디터 서비스가 정상적으로 연결되었습니다

webhook URL 테스트

n8n 에디터가 정상적으로 접속되며, 새로운 워크플로우를 생성해서 'webhook' 트리거를 추가해 봅니다. 웹훅 트리거 설정화면에서 웹훅 Test URL과 웹훅 Production URL이 설정파일에 등록된 URL로 정상적으로 표시되는지 확인합니다. 

Webhook Test URL과 Production URL이 설정파일에 등록된 HTTPS URL로 정상 표시됩니다
Webhook Test URL과 Production URL이 설정파일에 등록된 HTTPS URL로 정상 표시됩니다


11. 트러블슈팅

문제 1: SSL 인증서 오류

# Nginx 설정 문법 검사
docker exec n8n-play-nginx nginx -t

# 인증서 경로 확인
docker exec n8n-play-nginx ls -la /etc/nginx/ssl/

# Nginx 로그 확인
docker logs n8n-play-nginx

문제 2: WebSocket 연결 실패 (에디터 로딩 안됨)

# n8n 로그 확인
docker logs n8n-play-n8n-1

# 네트워크 연결 확인
docker network inspect n8n-play_n8n-network

문제 3: Webhook이 동작하지 않음

n8n 환경변수 확인:

docker exec n8n-play-n8n-1 env | grep -E "(WEBHOOK|N8N_)"

문제 4: 컨테이너 재시작 시 데이터 손실

# 볼륨 마운트 확인
docker inspect n8n-play-n8n-1 | grep -A 10 "Mounts"

# 데이터 디렉토리 권한 확인
ls -la ./n8n-data/

 

문제 5: n8n서비스는 정상이지만, Deprecation관련 경고가 있는 경우

      #-----------------------------------------------------------------------
      # Deprecation 경고 해결 (n8n 1.123.5 기준)
      # - 향후 버전에서 기본값이 변경되므로 미리 설정
      #-----------------------------------------------------------------------
      
      # DB_SQLITE_POOL_SIZE: SQLite 읽기 연결 풀 크기
      # - 0보다 큰 값으로 설정해야 함
      # - 동시 읽기 성능 향상
      # - 권장값: 4 (소규모), 10 (중규모)
      - DB_SQLITE_POOL_SIZE=4
      
      # N8N_RUNNERS_ENABLED: Task Runners 활성화
      # - 워크플로우 실행을 별도 프로세스에서 처리
      # - 향후 버전에서 기본값이 true로 변경 예정
      # - 메모리 격리, 안정성 향상
      - N8N_RUNNERS_ENABLED=true
      
      # N8N_BLOCK_ENV_ACCESS_IN_NODE: Code 노드에서 환경 변수 접근 차단
      # - true: 보안 강화 (Code 노드에서 process.env 접근 불가)
      # - false: 환경 변수 접근 허용 (기존 동작 유지)
      # - 향후 기본값이 true로 변경 예정
      # - 워크플로우에서 환경 변수를 사용한다면 false 유지
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=false
      
      # N8N_GIT_NODE_DISABLE_BARE_REPOS: Git 노드 bare repository 비활성화
      # - true: bare repository 사용 불가 (보안 강화)
      # - Git 노드에서 bare repo를 사용하지 않는다면 true 권장
      - N8N_GIT_NODE_DISABLE_BARE_REPOS=true

문제 6: Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide.

설정 파일 권한이 너무 넓다는 경고입니다. 보안 강화를 위해 수정하는 것이 좋습니다.

      #-----------------------------------------------------------------------
      # 파일 권한 설정
      #-----------------------------------------------------------------------
      
      # N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: 설정 파일 권한 자동 수정
      # - true: n8n이 자동으로 안전한 권한(0600)으로 변경 (권장)
      # - false: 권한 체크 비활성화
      # - 0644(모든 사용자 읽기 가능) → 0600(소유자만 읽기/쓰기)
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true

부록 1.  유용한 운영 명령어

# 컨테이너 재시작
docker-compose restart

# n8n만 재시작
docker-compose restart n8n

# 로그 실시간 확인
docker-compose logs -f --tail=100

# 리소스 사용량 확인
docker stats

# 인증서 만료일 확인
openssl x509 -enddate -noout -in ./ssl/play_peaknine_io.crt

# 컨테이너 내부 접속 (디버깅용)
docker exec -it n8n-play-n8n-1 /bin/sh

부록 2. 인증서 갱신 시 주의사항

Sectigo 인증서 갱신 후:

# 1. 새 인증서 파일 교체
cd ~/Projects/n8n-play/ssl
cat new_star_peaknine_io.crt new_star_peaknine_io.ca-bundle > play_peaknine_io.crt
cp new_star_peaknine_io.key play_peaknine_io.key
chmod 600 play_peaknine_io.key

# 2. Nginx 재시작
docker-compose restart nginx

# 3. 인증서 적용 확인
openssl s_client -connect play.peaknine.io:443 -servername play.peaknine.io 2>/dev/null | openssl x509 -noout -dates

부록 3. 인증서 파일의 확장명의 차이점  |  *.peaknine.io.crt   | *.peaknine.io.crt.pem

HTTPS 서비스를 위하여 호기롭게 SSL인증서를 구매했지만, 실질적으로 한 번도 해본적 없는 처음 대하는 문서파일 형식들입니다. 이곳 저곳의 설명서에서는 *.crt, *.key같이 간단하게만 나오는데, 내가 구매한 인증서는 무언가 복잡한 파일들이 잔뜩있어서 이해하기 힘든게 사실입니다. 특히 *.crt, *.key를 설명하지만 내 인증서 파일에는 비슷하지만 다른 확장자를 가진 파일들이 보입니다. *.crt.pem, *.key.pem등 이상해보이는 확장자가 붙어 있어요. 이번 장에서는 인증서 파일을 구성하는 키파일의 구조에 대해서 살펴보도록 합니다. 

*.crt와 *.crt.pem은 실질적으로 동일합니다

.crt와 .crt.pem은 파일 내용(인코딩)이 같다면 완전히 동일하게 작동합니다. 확장자는 단지 "이름표"일 뿐, 실제 중요한 건 파일 내부의 인코딩 방식입니다.

인증서 인코딩 방식 (핵심)

인코딩 형식 특징
PEM 텍스트 (Base64) -----BEGIN CERTIFICATE-----로 시작, 사람이 읽을 수 있음
DER 바이너리 사람이 읽을 수 없는 바이너리 데이터

PEM 형식 예시 (텍스트로 열어볼 수 있음)

-----BEGIN CERTIFICATE-----
MIIFjTCCBHWgAwIBAgIQDGOX2x7WmVfLzJE8NhS7aTANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE
aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMzAxMTAwMDAwMDBa
... (Base64 인코딩된 데이터) ...
-----END CERTIFICATE-----

DER 형식 (바이너리 - 텍스트로 열면 깨진 문자로 보임)

0▒▒▒0▒▒▒▒▒▒▒▒ (바이너리 데이터)

 

확장자별 일반적인 관례

확장자 일반적인 인코딩 설명
.pem PEM 명시적으로 PEM 형식임을 표시
.crt PEM 또는 DER 인증서 파일 (인코딩 불명확)
.cer PEM 또는 DER Windows에서 주로 사용
.crt.pem PEM "인증서(.crt)이고 PEM 형식"임을 명확히 표시
.key PEM 또는 DER 개인키 파일
.key.pem PEM PEM 형식의 개인키임을 명확히 표시
.der DER 바이너리 형식
.p7b PKCS#7 인증서 체인 (개인키 미포함)
.pfx / .p12 PKCS#12 인증서 + 개인키 묶음 (바이너리)

Setigo SSL 인증서가  가진 파일들의 용도

스크린샷 기준으로 정리하면:

파일 형식 용도

파일 형식 용도
.all.crt.pem PEM 인증서 + 체인 통합 (Nginx용)
.crt.pem PEM 도메인 인증서만 (체인 미포함)
.key.pem PEM 개인키 (Nginx용)
.jks Java KeyStore Java 애플리케이션용
.p7b PKCS#7 인증서 체인 (Windows IIS 등)
.pfx PKCS#12 Windows IIS, Azure 등
chain-bundle.pem PEM 중간 인증서 체인만

파일 형식 직접 확인하는 방법

# 파일이 PEM인지 확인 (텍스트로 시작하면 PEM)
head -1 ./ssl/wildcard_peaknine_io.crt

# PEM이면 이렇게 출력됨:
# -----BEGIN CERTIFICATE-----

# 인증서 상세 정보 확인 (PEM 형식일 때)
openssl x509 -in ./ssl/wildcard_peaknine_io.crt -text -noout

# 만약 DER 형식이라면 -inform DER 옵션 필요
openssl x509 -in ./ssl/cert.der -inform DER -text -noout

 

Nginx가 지원하는 형식

Nginx는 PEM 형식만 지원합니다.

# PEM 형식 - 작동함
ssl_certificate /etc/nginx/ssl/wildcard_peaknine_io.crt;      # .crt (PEM)
ssl_certificate /etc/nginx/ssl/wildcard_peaknine_io.crt.pem;  # .crt.pem (PEM)

# DER/바이너리 형식 - 작동 안 함
ssl_certificate /etc/nginx/ssl/cert.der;
ssl_certificate /etc/nginx/ssl/cert.pfx;