tech

WebRTC TURN/STUN 서버 구축기: COTURN으로 연결 성공률 높이기

1. 안녕하세요! COTURN입니다. (그런데 누구신지?)

WebRTC에서 “연결이 왜 안 되지?”라는 문제를 파고들면 결국 ICE(Interactive Connectivity Establishment) 과정과 마주하게 된다. ICE는 두 단말이 서로 통신 가능한 경로 후보(candidate)를 수집하고, 그중 최적의 경로를 선택하여 연결하는 절차다.

COTURN은 ICE 과정에서 필요한 STUN/TURN 서버 역할을 수행하는 대표적인 오픈소스 구현체다. P2P가 가능한 환경에서는 연결을 빠르고 간편하게 만들고, P2P가 불가능한 환경에서는 Relay(중계) 경로를 제공해 연결 실패를 막아주는 안전장치 역할을 한다.

이 문서는 Coturn을 Docker 기반으로 배포하고, 방화벽·포트 제한 환경에서도 안정적으로 운영할 수 있도록 설정한 뒤, Trickle ICE를 통해 relay 후보(typ relay)까지 검증하는 과정을 정리한다.

1.1 COTURN이 하는 일 (ICE 관점)

ICE는 크게 세 가지 후보를 수집한다.

  • Host candidate: 단말의 로컬 IP (동일 네트워크 내 통신 시 사용)
  • Srflx(Server Reflexive) candidate: STUN을 통해 확인한 “외부에서 보이는 IP/Port” 후보
  • Relay candidate: TURN 서버를 통해 미디어를 중계하는 후보

COTURN은 이 중 STUN 기능으로 srflx 후보를 생성하고, TURN 기능으로 relay 후보를 생성한다. 실제 운영 시 P2P(srflx)에만 의존하면 연결 성공률이 불안정해질 수 있으므로, TURN을 통해 성공률을 보장하는 인프라를 구축하는 것이 핵심이다.

1.2 STUN으로 NAT 넘어보기 (P2P)

대부분의 클라이언트는 사설 IP를 사용하는 NAT 환경 뒤에 있다. 이 상태에서는 상대 피어가 로컬 주소로 직접 접속할 수 없다. 그래서 ICE는 STUN을 통해 “외부에서 보이는 나의 주소”를 먼저 확인한다.

과정은 다음과 같다.

  • 클라이언트 → STUN 서버로 요청
  • STUN 서버 → “외부에서는 <공인 IP:포트>로 보인다”라고 응답
  • 클라이언트는 이를 srflx candidate로 등록

이 방식이 정상 동작하면 미디어는 TURN을 거치지 않고 P2P로 직접 전달된다. 레이턴시와 서버 비용 측면에서도 가장 유리하다.

하지만 STUN은 어디까지나 “주소를 알아내는 단계”일 뿐이다. NAT 유형이나 방화벽 정책에 따라 후보는 수집되지만 실제 미디어 경로는 성립하지 않는 경우가 자주 발생한다.

1.3 연결 정책이 복잡하다면? “TURN” (RELAY)

현실 환경에서는 다음과 같은 상황이 빈번하게 발생한다.

  • 기업·학교 망에서 UDP가 제한되거나 차단됨
  • Symmetric NAT 구조로 외부 매핑이 까다로운 환경
  • 공용 Wi-Fi처럼 임의 포트가 쉽게 차단되는 네트워크

이때 TURN은 사실상 우회 경로 역할을 한다.

  • 클라이언트가 TURN 서버에 인증 후 relay allocation을 요청한다.
  • 두 단말은 서로 직접 통신하지 않는다.
  • TURN 서버가 중간에서 미디어를 중계한다.

“STUN은 가능성을 높이고, TURN은 실패를 막는다.”

이 문서의 최종 목표도 여기에 있다.
어떤 네트워크 환경에서도 relay 후보가 안정적으로 생성되고, 실제 연결까지 이어지는지를 검증하는 것이다.

2. 언제 TURN이 필요한가? (결정 기준)

WebRTC에서는 “되면 P2P가 최고”다. 하지만 서비스 관점에서는 “안 되는 환경이 언제, 얼마나 자주 발생하느냐”가 더 중요하다. STUN만 구성하면 특정 네트워크에서 연결이 실패하거나 성공률이 사용자 환경에 따라 크게 흔들릴 수 있다.

따라서 TURN은 성능 옵션이 아니라 가용성(availability)을 보장하기 위한 보험으로 보는 편이 정확하다.

2.1 STUN만으로 충분한 경우

  • 사용자가 주로 가정용 공유기나 일반 모바일 환경을 이용할 때
  • UDP 통신이 대부분 허용될 때
  • Symmetric NAT 비율이 낮을 때

2.2 TURN이 필요한 대표 케이스

(1) Symmetric NAT 환경

Symmetric NAT는 외부 매핑이 통신 대상(IP/Port)에 따라 달라진다. 이로 인해 STUN으로 얻은 srflx 후보가 상대 피어에게 그대로 유효하지 않은 경우가 발생한다. 이 환경에서는 TURN relay가 사실상 유일한 해결책이다.

참고로 NAT는 Static/Dynamic, SNAT/DNAT 등 다양한 기준으로 분류되지만, WebRTC 성공률 관점에서는 Cone 타입과 Symmetric 타입 구분이 가장 직접적인 영향을 준다.

(2) 기업·학교·공공기관 망

UDP 제한, 특정 포트만 허용되는 경우가 많다. TURN over TCP/TLS까지 준비하면 성공률이 눈에 띄게 개선된다.

(3) 정책이 자주 변하는 공용 Wi-Fi

어제는 되던 연결이 오늘은 실패하는 환경이다. TURN은 이런 변동성을 흡수하는 역할을 한다.

(4) 허용 포트가 제한된 환경

min-port/max-port를 통한 포트 범위 고정과 방화벽 정책의 1:1 매칭이 필요하다.

2.3 서비스 관점 결론: TURN은 보험이다

실무적으로 선택지는 세 가지다.

  1. STUN only
    • 내부 테스트나 PoC에는 가능
    • 실서비스에는 리스크가 큼
  2. STUN + TURN(UDP)
    • 기본적으로 추천되는 구성
    • UDP 제한 환경에서는 여전히 실패 가능
  3. STUN + TURN(UDP + TCP + TLS)
    • 성공률 최우선 구성
    • 운영 복잡도는 증가

실서비스에서는 STUN + TURN(UDP + TCP + TLS) 조합을 가장 권장한다. 이는 운영 복잡도는 증가하지만, 어떤 네트워크 환경에서도 연결 성공률을 극대화할 수 있는 가장 안전한 선택지다.

“될 때는 P2P, 안 되면 반드시 relay로 살린다.”

3. 전체 구성 & 네트워크 요구사항

TURN/STUN은 서버만 띄운다고 끝나지 않는다. 어떤 포트와 프로토콜로 접근하는지, 미디어가 어떤 포트 대역으로 흐르는지가 핵심이다.

3.1 구성 요소 (Architecture)

  • Client A / Client B: WebRTC 피어(브라우저 또는 앱)
  • Signaling 서버(WSS/HTTPS): SDP·ICE 후보 교환
  • Coturn 서버: STUN/TURN 역할 수행

흐름은 다음과 같다.

  1. Signaling으로 SDP 교환
  2. ICE 후보(host/srflx/relay) 수집
  3. P2P 가능 시 직접 연결
  4. 실패 시 TURN relay를 통해 중계

Signaling은 제어 경로이고, Coturn은 ICE 및 미디어 경로를 담당한다.

3.2 포트 & 프로토콜 요구사항

  • 제어/할당 포트: 3478 (UDP/TCP)
  • TLS(turns): 5349/TCP
  • Relay 미디어 포트: min-port ~ max-port 범위

가장 흔한 장애는 3478은 열려 있지만 relay 포트 범위가 막혀 있는 경우다. 이 경우 relay 후보는 생성되지만 실제 연결은 실패한다.

3.3 방화벽/보안그룹 정책 예시

용도프로토콜포트방향비고
STUN/TURN 기본UDP3478Inboundsrflx/relay 영향
TURN over TCPTCP3478InboundUDP 제한 fallback
TURN over TLSTCP5349Inboundturns(성공률 옵션)
Relay 미디어UDP50000-50100Inboundmin-port~max-port와 동일
(선택) 관리/모니터링TCP조직 정책Inbound별도 네트워크 권장

Outbound 정책이 있는 환경이라면 별도 협의가 필요하다.

3.4 NAT/LB 환경의 핵심 (external-ip)

서버가 NAT 또는 LB 뒤에 있다면 Coturn이 내부 IP를 candidate로 광고할 수 있다. 클라이언트가 실제로 접속하는 공인 IP와 광고되는 주소는 반드시 일치해야 한다.

대표 증상은 다음과 같다.

  • relay 후보는 보이는데 ICE failed
  • 특정 네트워크에서만 연결 실패

결론적으로 NAT/LB 환경에서는 external-ip 설정이 필수다.

3.5 체크리스트

  • UDP 제한 대비 TCP/TLS fallback 고려
  • 3478/5349 및 relay 포트 범위 방화벽 허용
  • min-port/max-port와 방화벽 정책 일치
  • NAT/LB 환경이면 external-ip 설정

4. 오픈소스 세팅 레시피

목표는 다음과 같다.

  • Coturn을 컨테이너 기반으로 운영
  • relay 포트 범위를 제한해 방화벽 정책과 정합
  • TCP/TLS 확장과 운영 안정성 확보

4.1 Docker 이미지 구축 가이드

운영 환경에서는 검증된 Coturn 이미지를 사용하고, 설정 파일과 인증서를 볼륨으로 주입하는 방식이 관리 측면에서 유리하다.

  • 단점: 커스텀 빌드 옵션은 제한적
  • 장점: 설정 변경 및 롤백이 용이

4.2 Docker Compose 템플릿

services:
  coturn:
    image: coturn/coturn:latest
    container_name: coturn
    restart: always
    network_mode: host
    volumes:
      - ./coturn/turnserver.conf:/etc/turnserver.conf:ro
      - ./certs/fullchain.pem:/etc/ssl/certs/fullchain.pem:ro
      - ./certs/privkey.pem:/etc/ssl/private/privkey.pem:ro
    command: ["turnserver", "-c", "/etc/turnserver.conf", "--no-cli"]

운영 팁으로는 버전 태그를 고정하고, relay 포트 범위는 작게 시작해 트래픽에 맞춰 확장하는 방식이 안전하다.

4.3 허용된 포트로만 유도하기

운영 환경에서는 보안을 위해 미디어 트래픽이 흐르는 포트 범위를 엄격하게 제한해야 한다.

  • 설정 방식: Coturn 설정 파일에서 min-portmax-port를 지정하여 relay 범위를 제한한다.
  • 방화벽 연동: 서버 설정에서 지정한 포트 범위와 인바운드 방화벽 허용 범위를 1:1로 일치시킨다.
  • 결과: 미디어 트래픽이 사전에 허용된 특정 범위 내에서만 흐르도록 유도할 수 있다.

relay 포트 범위가 지나치게 좁으면 동시 세션 증가 시 포트 고갈 현상이 발생할 수 있다. 서비스 트래픽 규모를 고려하여 범위를 설정하고, 필요에 따라 단계적으로 확장하는 것을 권장한다.

4.4 유용한 운영 정보

  • 로그 분석 우선: 인증, 포트, external-ip, TLS 관련 이슈 발생 시 로그를 확인하는 것이 가장 빠른 해결 방법이다.
  • 프로토콜 다변화: UDP 통신만 고집하지 말고, TCP와 TLS(turns) fallback을 함께 구성해야 높은 연결 성공률을 확보할 수 있다.
  • NAT/LB 환경 주의: “후보는 생성되지만 실제 연결이 실패”한다면, external-ip 설정이 공인 IP와 일치하는지 가장 먼저 확인해야 한다.

5. Config 설정 및 항목 설명

Coturn 운영에서 설정 파일은 단순한 옵션 모음이 아니라, 보안·연결 성공률·운영 안정성을 동시에 결정하는 핵심 요소다. 특히 인증 방식, candidate에 광고되는 주소, relay 포트 범위는 실서비스에서 반드시 명확히 통제해야 한다.

운영 관점에서 가장 우선적으로 정리해야 할 포인트는 다음 세 가지다.

  • 인증 설정으로 무단 사용을 방지할 것
  • external-ip 설정으로 candidate 주소를 정확히 맞출 것
  • relay 포트 범위를 방화벽 정책과 1:1로 정합시킬 것

이 세 가지가 흔들리면 TURN 서버는 “켜져 있지만 쓸 수 없는 상태”가 되기 쉽다.

5.1 기본 구성 (MVP)

아래 설정은 Coturn을 처음 띄워 기능을 검증하기 위한 최소 구성(MVP) 예시다.

# /etc/turnserver.conf (MVP)

listening-port=3478

realm=example.com
server-name=coturn-example

lt-cred-mech
user=testuser:testpassword

fingerprint
no-loopback-peers
no-multicast-peers

각 항목의 의미는 다음과 같다.

  • listening-port: STUN/TURN 기본 포트다. 일반적으로 3478을 사용한다.
  • realm: long-term credential 인증의 기준 값이다. 운영 환경에서는 서비스 도메인을 기준으로 고정하는 것이 좋다.
  • lt-cred-mech / user: 장기 인증 방식이다. MVP 단계에서는 정적 유저로 시작할 수 있지만, 운영 환경에서는 turnadmin 기반 관리가 권장된다.
  • fingerprint / no-loopback-peers / no-multicast-peers: 기본적인 안전 옵션이다. 불필요한 트래픽과 오남용 가능성을 줄여준다.

이 구성만으로도 STUN 및 TURN allocation 자체는 가능하다. 다만 실서비스에 바로 적용하기에는 부족하다.

5.2 운영용 추천 베이스

relay 포트 범위(min-port/max-port)는 방화벽 허용 범위와 반드시 동일해야 하며, 서버가 NAT/LB 뒤에 있다면 external-ip를 설정해 candidate에 광고되는 주소가 클라이언트가 접속하는 공인 주소와 일치하도록 맞춰야 한다. 또한 기업망/공공망처럼 UDP 제약이 있는 환경을 대비하려면 turns(5349/TCP)와 인증서(cert/pkey)를 준비해 TLS fallback까지 확보하는 편이 안정적이다.

# /etc/turnserver.conf (Recommended)

# TURN/STUN + TURNS 포트
listening-port=3478
tls-listening-port=5349

# Identity
realm=example.com
server-name=coturn-example

# Auth (운영에서는 turnadmin 권장)
lt-cred-mech
user=testuser:testpassword

# NAT/LB 환경이면 설정 (공인/사설 주소 정합)
# external-ip=<PUBLIC_IP>                # 공인 IP 직결
# external-ip=<PUBLIC_IP>/<PRIVATE_IP>   # NAT/LB 뒤(공인/사설 분리)
# external-ip=<PUBLIC_IP>/<PRIVATE_IP>

# Relay 포트 범위 (방화벽과 1:1 매칭)
min-port=50000
max-port=50100

# TLS(turns)
cert=/etc/ssl/certs/fullchain.pem
pkey=/etc/ssl/private/privkey.pem

# Security
fingerprint
no-loopback-peers
no-multicast-peers
# no-cli  # 운영 정책에 따라

이 설정의 핵심 포인트는 다음과 같다.

  • relay 포트 범위는 반드시 방화벽 정책과 동일해야 한다.
  • NAT/LB 뒤에 있다면 external-ip 설정은 선택이 아니라 필수다.
  • UDP 제한 환경을 대비해 turns(5349/TCP)를 함께 열어두는 것이 안전하다.

운영에서 흔한 함정은 다음과 같다. TLS 인증서 경로/권한 문제로 turns만 실패하거나, relay 범위 방화벽 미허용으로 relay가 실패하거나, external-ip 문제로 후보는 나오는데 연결이 실패하는 경우다.

6. Docker 실행 및 정상 가동 확인

  • TLS 인증서 경로/권한 → turns만 실패
  • relay 범위 방화벽 미허용 → relay 실패
  • external-ip 문제 → 후보는 나오는데 연결 실패

TURN 서버는 “컨테이너가 떠 있다”와 “실제로 연결이 된다”가 완전히 다른 문제다. 운영 중 가장 흔한 문제는 설정은 맞아 보이는데 실제 연결이 실패하는 상황이다.

6.1 실행 및 상태 확인

docker compose up -d
docker compose ps

컨테이너 상태 확인 후 로그를 바로 확인하는 것이 좋다.

docker logs -f coturn

6.2 포트 리스닝 확인

ss -lntup | grep -E '3478|5349'

여기서 주의할 점은 relay 포트다. relay 포트는 allocation이 발생했을 때 동적으로 사용되므로, 항상 리스닝 상태로 보이지 않을 수 있다. 따라서 단순 포트 체크만으로는 충분하지 않다. 이후 TURN allocation/Trickle ICE로 검증한다.

6.3 기능 검증 핵심: STUN → TURN

운영에서 자주 발생하는 착각은 다음과 같다.

  • STUN이 된다 = TURN도 된다 ❌

TURN은 allocation 단계가 정상적으로 완료되어야 relay 후보가 안정적으로 생성된다. 이 단계를 반드시 별도로 검증해야 한다.

6.4 빠른 진단 가이드

증상주요 원인
turns(TLS) 접속 실패인증서 경로/권한 문제 또는 5349 포트 미개방
relay 후보 미생성인증 정보(ID/PW) 불일치 또는 3478 포트 접근 불가
후보는 나오지만 연결 실패external-ip 설정 오류 또는 relay 범위 방화벽 차단

7. Trickle ICE 테스트 방법

TURN 서버가 “정말로 준비되었는지”를 확인하는 가장 확실한 방법은 Trickle ICE 테스트다. 로그보다 빠르고, 클라이언트 관점에서 결과를 바로 확인할 수 있다.

7.1 핵심 포인트

typ relay 후보가 실제로 나와야 TURN이 정상이다.

7.2 준비물

  • TURN 서버 도메인 또는 IP
  • 포트 정보(3478 / 5349)
  • username / credential
  • transport(udp / tcp)
  • Trickle ICE 테스트 페이지

7.3 iceServers 설정 템플릿

STUN(선택):

STUN or TURN URI: "stun:TURN_HOST:3478"

TURN UDP:

STUN or TURN URI: "turn:TURN_HOST:3478?transport=udp"
TURN username: "testuser"
TURN password: "testpassword"

TURN TCP:

STUN or TURN URI: "turn:TURN_HOST:3478?transport=tcp"
TURN username: "testuser"
TURN password: "testpassword"

TURNS(TLS):

STUN or TURN URI: "turns:TURN_HOST:5349?transport=tcp"
TURN username: "testuser"
TURN password: "testpassword"

각 TURN 설정에는 username과 password를 함께 입력한다.

7.4 테스트 시나리오 3종 세트

  • A) UDP에서 typ relay 확인
  • B) TCP에서 typ relay 확인
  • C) TLS(turns)에서 typ relay 확인

7.5 결과 해석

  • 1차 통과: typ relay 후보가 등장
  • 2차 통과: relay 후보 IP와 포트가 설정된 범위 내에 있음
  • 후보는 있으나 실패: external-ip 또는 방화벽 문제 가능성 높음

7.6 실패 메시지 맵

  • no relay candidate → 인증, 포트, transport 설정 문제
  • 401 Unauthorized → realm 또는 credential 불일치
  • relay 후보는 있으나 ICE failed → external-ip 또는 relay 포트 범위
  • turn은 되는데 turns만 실패 → 인증서, 5349 포트, 도메인 문제

8. 운영 팁 & 트러블슈팅

8.1 운영 팁

TURN 서버 운영은 “한 번 세팅하고 끝”이 아니라, 지속적인 관측과 점검이 필요한 영역이다.

  • 서버 설정과 방화벽 정책은 항상 세트로 관리한다.
  • UDP만으로 끝내지 않고 TCP/TLS fallback을 준비한다.
  • relay 사용량과 ICE 성공률을 함께 관측한다.

포트 정책은 “서버 설정 ↔ 방화벽” 세트로 관리하고, UDP + TCP/TLS fallback을 준비한다.

8.2 에러 유형별 진단

  • no relay candidates: 포트, 인증, 접근 문제
  • 401 오류: realm 또는 credential 불일치
  • 후보는 나오나 연결 실패: external-ip, relay range
  • turns만 실패: 인증서 또는 TLS 포트 문제

8.3 운영 체크리스트

  • ICE 성공률과 relay 사용량 모니터링
  • 제어 포트 및 relay 포트 범위 일치 여부
  • 인증서 만료 및 갱신 플로우 점검

9. 마무리: Come 2 COTURN

STUN은 연결의 가능성을 열어주고, TURN은 발생할 수 있는 연결 실패를 완전히 차단한다. 다양한 사용자 네트워크 환경을 제어할 수 없는 실서비스에서 Coturn은 서비스 품질을 결정짓는 핵심 요소다.

핵심 요약 3가지:

  1. 포트 정책 확립: 3478/5349 및 relay 범위를 방화벽과 일치시킨다.
  2. 정교한 설정: external-ip와 인증 정보를 정확히 지정한다.
  3. 철저한 검증: Trickle ICE를 통해 모든 프로토콜에서의 relay 후보 생성을 확인한다.

TURN 운영에는 인프라 비용이 따르지만, 이는 곧 연결 실패라는 기회비용을 줄이는 가장 확실한 투자다. 안정적인 WebRTC 연결을 원한다면, COTURN은 최선의 선택이다.


관련 문서

조건섭 기자

누가 따라 해도 항상 똑같은 결과를 가질 수 있기를 바라면서 글을 작성해 보았습니다. 동료들과 함께 했던 고민 들을, 조금이라도 덜어드렸으면 좋겠습니다.


TOP