SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor

VPS에서 화상 회의 서버 직접 구축하기: 대역폭 및 사양 가이드

VPS에서 Jitsi, BigBlueButton, Galene을 호스팅할 때 고려해야 할 대역폭 계산법을 다룹니다. 참가자 수에 따른 SFU 서버의 RAM 요구량과 NAT 환경에서의 UDP 포트 문제 등 실무적인 구축 노하우를 상세히 정리했습니다.

VPS에서 직접 호스팅하는 화상 회의는 대역폭 문제가 발생합니다

소규모 서버에서 직접 호스팅하는 화상 회의가 실패하는 이유는 단 한 가지이며, 설치 과정의 문제는 거의 없습니다. 현대적인 화상 회의 도구는 모두 SFU(Selective Forwarding Unit)라는 서버 구성 요소를 사용합니다. SFU는 각 참가자로부터 하나의 비디오 스트림을 받아 다른 모든 참가자에게 복사본을 전달하므로, 서버에서 나가는 트래픽은 참가자 수의 제곱에 비례하여 증가합니다. 공유 업링크를 사용하는 1 GB 또는 2 GB 용량의 VPS에서도 소프트웨어 자체는 잘 실행됩니다. 하지만 여러분이 생각하는 전체 회의를 감당할 수는 없습니다.

따라서 다음 순서대로 작업을 진행하십시오. 먼저 인원을 파악하고, 필요한 메가비트(megabits)를 계산한 뒤, 서버 사양을 선택하십시오. 설치는 복사하고 붙여넣기만 하면 20분 안에 끝납니다. 하지만 상대방이 내 목소리를 들을 수 있을지는 업링크가 결정합니다.

참가자 수가 늘어날수록 대역폭이 제곱으로 증가하는 이유는 무엇입니까?

메시(mesh) 방식부터 살펴보겠습니다. 각 브라우저는 자신의 카메라 영상을 인코딩하여 다른 모든 브라우저에 직접 전송하며, 미디어 서버는 비디오 처리에 관여하지 않습니다. 2인 메시 통화는 시그널링 서버만 있으면 충분하므로 호스팅 비용이 거의 들지 않습니다. 하지만 메시 방식은 4~5명 정도가 넘어가면 작동하지 않는데, 이는 가정용 네트워크를 사용하는 노트북이 자신의 비디오를 동시에 4~5개 복사본으로 업로드해야 하기 때문입니다.

SFU(Selective Forwarding Unit)는 다르게 작동합니다. 각 브라우저는 서버에 비디오를 한 번만 업로드합니다. 서버는 RTP(real-time transport protocol) 헤더를 읽고 비디오를 디코딩하지 않은 채로 다른 참가자들에게 패킷을 전달합니다. 이것이 SFU의 핵심이며, SFU가 CPU 부하는 적고 네트워크 부하는 큰 이유입니다.

이전 방식인 MCU(multipoint control unit)는 모든 수신 스트림을 디코딩한 뒤 하나의 화면으로 합성하고 다시 인코딩합니다. 이 경우 아웃바운드 대역폭은 매우 작지만 CPU 비용은 엄청납니다. 현재 비디오 서비스에서 MCU를 사용하는 경우는 거의 없으며, 이 가이드에서도 다루지 않습니다.

이제 SFU의 산술적 계산을 해보겠습니다. 모든 사람이 1.2 Mbps로 비디오를 전송하고 카메라를 끄지 않는다고 가정합니다. 서버는 N 곱하기 1.2 Mbps를 수신하는데, 이는 선형적이며 문제가 되지 않습니다. 하지만 서버는 N 곱하기 (N 마이너스 1) 곱하기 1.2 Mbps를 전송해야 합니다. N명의 참가자 각각이 나머지 N 마이너스 1개의 스트림을 받아야 하기 때문입니다. 이 두 번째 수치가 프로젝트의 성패를 결정짓는 요인입니다.

ChartSFU outbound traffic, arithmetic model at 1.2 Mbps per sender
The data behind this chart
[
  {
    "label": "4 people",
    "sfu_egress_mbps": 14.4,
    "egress_gb_per_hour": 6.5,
    "monthly_volume_tb": 0.13
  },
  {
    "label": "8 people",
    "sfu_egress_mbps": 67.2,
    "egress_gb_per_hour": 30.2,
    "monthly_volume_tb": 0.6
  },
  {
    "label": "15 people",
    "sfu_egress_mbps": 252,
    "egress_gb_per_hour": 113.4,
    "monthly_volume_tb": 2.3
  },
  {
    "label": "30 people",
    "sfu_egress_mbps": "1,044",
    "egress_gb_per_hour": 469.8,
    "monthly_volume_tb": 9.4
  },
  {
    "label": "50 people",
    "sfu_egress_mbps": "2,940",
    "egress_gb_per_hour": "1,323",
    "monthly_volume_tb": 26.5
  }
]

위 표의 수치는 산술적인 계산 결과이며 특정 서버의 측정값이 아닙니다. 월간 데이터 열은 한 달에 20시간 통화하는 것을 가정합니다. 마지막 행부터 확인하십시오. 50명이 카메라를 켜면 단일 서버에서 2,940 Mbps의 지속적인 아웃바운드 트래픽이 필요합니다. 30명일 때는 1,044 Mbps가 필요합니다. 4명일 때는 14.4 Mbps가 필요한데, 이는 어떤 VPS에서도 무리 없이 처리할 수 있는 수준입니다. 4명일 때와 30명일 때를 비교하면 참가자 수는 7.5배 늘어나지만, 아웃바운드 트래픽은 70배 이상 증가합니다.

실제 배포 환경에서는 이 수치보다 낮게 나오는데, 그 이유를 정확히 아는 것이 중요합니다. Jitsi와 LiveKit은 모두 시뮬캐스트(simulcast)를 사용합니다. 송신자가 여러 품질 계층을 동시에 게시하면, SFU는 화면에 보이지 않는 사람에게는 낮은 품질의 계층을 전달합니다. Jitsi에는 최근 발언자들의 비디오만 전달하는 last-N 설정도 있습니다. 두 기능 모두 트래픽을 크게 절감해 줍니다. 하지만 이 기능들이 곡선의 형태 자체를 바꾸지는 않으며, 모든 참가자가 카메라를 켜고 서로를 고정(pin)하는 순간 절감 효과는 사라집니다.

VPS 업링크는 실제로 무엇을 제공하는가?

요금제 페이지에 기재된 "1 Gbps 포트"는 가상 네트워크 카드의 속도일 뿐, 다음 홉(next hop)까지의 속도를 보장하는 것은 아닙니다. 해당 링크는 동일한 물리 호스트를 사용하는 다른 임차인들과 공유되므로, 사용량이 많은 시간대의 지속적인 처리량은 포트 속도보다 낮으며, 화상 회의는 정확히 이러한 지속적인 부하를 발생시킵니다. 대부분의 요금제에는 월간 데이터 전송 허용량이 포함되어 있으며, 이를 초과하면 속도가 제한되거나 추가 요금이 청구됩니다.

이 허용량이 청구서의 금액을 결정짓는 핵심 요소입니다. 30명이 참여하는 20시간 분량의 통화를 한 달 동안 진행하면 서버에서 9.4 TB의 데이터가 발생하며, 시간당 469.8 GB가 소모됩니다. 50명이 참여하는 20시간 통화의 경우 26.5 TB가 발생합니다. RAM 용량을 확인하기 전에 전송 허용량을 먼저 확인하십시오. 만약 요금제 페이지에서 이 수치를 모호하게 표기하고 있다면, 그 모호함 자체가 곧 답변입니다. 저가형 VPS 제안을 올바르게 읽는 법은 다른 어떤 작업보다도 이러한 워크로드에서 훨씬 중요합니다.

Jitsi Meet: 기본 선택지와 요구 사항

대부분의 사용자는 Jitsi Meet로 시작하는 것이 좋습니다. 이 소프트웨어는 프로젝트의 공식 Debian 저장소에서 설치되며, 설치 과정에서 nginx와 인증서를 자동으로 구성합니다. 또한 videobridge(JVB)가 단일 UDP 포트만 사용하므로 방화벽 규칙을 간결하게 유지할 수 있습니다. Debian 11 이상 또는 Ubuntu 22.04 이상의 운영체제가 필요합니다.

sudo apt update
sudo apt install -y apt-transport-https curl gnupg
sudo add-apt-repository universe
sudo apt update
sudo curl -sL https://prosody.im/files/prosody-debian-packages.key -o /usr/share/keyrings/prosody-debian-packages.key
echo "deb [signed-by=/usr/share/keyrings/prosody-debian-packages.key] http://packages.prosody.im/debian $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/prosody-debian-packages.list
sudo apt install -y lua5.2
curl -sL https://download.jitsi.org/jitsi-key.gpg.key | sudo sh -c 'gpg --dearmor > /usr/share/keyrings/jitsi-keyring.gpg'
echo "deb [signed-by=/usr/share/keyrings/jitsi-keyring.gpg] https://download.jitsi.org stable/" | sudo tee /etc/apt/sources.list.d/jitsi-stable.list
sudo apt update
sudo apt install -y jitsi-meet

설치 프로그램은 호스트 이름을 물어본 뒤 인증서 선택 옵션을 제공합니다. Let's Encrypt 옵션을 선택하고, 현재 서버의 공인 IP 주소로 이미 도메인 연결이 완료된 도메인 이름을 입력하십시오. 인증서는 HTTP 챌린지 방식을 통해 발급되므로, 다른 곳을 가리키는 도메인을 입력하면 해당 단계에서 실패합니다.

그다음 포트를 개방하십시오. 아래는 핸드북에 명시된 포트들이며, ufw enable 명령을 통해 SSH 접속이 끊기지 않도록 먼저 설정하십시오.

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 10000/udp
sudo ufw allow 3478/udp
sudo ufw allow 5349/tcp
sudo ufw enable

TCP 80번과 443번 포트는 웹 애플리케이션을 서비스하고 인증서를 갱신하는 데 사용됩니다. UDP 10000번 포트는 모든 오디오와 비디오 데이터를 전송하며, 사용자들이 가장 자주 잊는 포트입니다. UDP 3478번과 TCP 5349번 포트는 Jitsi 패키지와 함께 설치되는 coturn 서버용이며, 네트워크에서 UDP를 차단하는 사용자를 위한 대체 경로 역할을 합니다.

sudo systemctl status jitsi-videobridge2
sudo ss -ulnp | grep 10000

첫 번째 명령을 실행하면 서비스가 활성 상태로 보고되어야 합니다. 두 번째 명령은 브리지가 UDP 10000번 포트에서 수신 대기 중임을 보여주어야 합니다. 아무런 결과가 출력되지 않는다면 브리지가 시작되지 않은 것이며, /var/log/jitsi/jvb.log 명령을 통해 그 이유를 확인할 수 있습니다.

서버 규모 산정과 관련하여, Jitsi 핸드북은 자체적인 권장 사양을 제시하며 BigBlueButton은 훨씬 더 큰 사양을 권장합니다.

ChartSizing each project publishes for one production server
The data behind this chart
[
  {
    "label": "Jitsi Meet",
    "ram_gb": 8,
    "cpu_cores": 4,
    "uplink_mbps": "1,000"
  },
  {
    "label": "BigBlueButton 3.0",
    "ram_gb": 16,
    "cpu_cores": 8,
    "uplink_mbps": 250
  }
]

Jitsi 핸드북은 본격적인 운영을 위해 8 GB의 RAM과 4개의 전용 CPU 코어를 권장하며, 네트워크 대역폭은 1,000 Mbps면 충분한 경우가 많다고 설명합니다. 또한 소규모 환경은 4 GB 또는 2 GB RAM으로도 운영 가능하다고 언급합니다. 해당 페이지에서 기억해야 할 중요한 세부 사항이 하나 있습니다. 신호 처리를 담당하는 XMPP 서버인 Prosody는 오직 하나의 코어만 사용할 수 있습니다. 코어를 추가해도 브리지 성능에는 도움이 되지만 신호 처리 성능에는 아무런 영향을 주지 않습니다.

모두가 통화에 참여했는데 왜 아무도 비디오를 볼 수 없습니까?

이는 VPS에서 Jitsi를 운영할 때 발생하는 전형적인 오류입니다. 참가자 목록은 채워지고 채팅은 작동하지만, 모든 비디오 타일이 검은색으로 유지됩니다. videobridge는 자신의 인터페이스에서 발견한 주소를 광고합니다. 가상 머신에 사설 주소를 할당하고 공인 IP를 매핑해 주는 제공업체 환경에서는 JVB가 사설 주소만 인식하게 됩니다. 따라서 모든 클라이언트가 10.0.0.5와 같은 주소로 미디어를 전송하려고 시도하며, 패킷은 목적지에 도달하지 못합니다.

브리지에 두 주소를 모두 알려주어야 합니다. /etc/jitsi/videobridge/jvb.conf에 정적 매핑을 추가하십시오:

ice4j {
  harvest {
    mapping {
      static-mappings = [
        {
          local-address = "10.0.0.5"
          public-address = "203.0.113.10"
        }
      ]
    }
  }
}

sudo systemctl restart jitsi-videobridge2 명령으로 재시작하십시오. ip -4 addr show에서 로컬 주소를 확인하고, 제공업체의 제어판에서 공인 주소를 확인하여 입력하십시오. 이전 가이드에서는 /etc/jitsi/videobridge/sip-communicator.propertiesorg.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESSorg.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS 키를 사용하여 동일한 설정을 수행했습니다. 해당 방식도 여전히 작동하지만, 새로 설치하는 경우에는 위의 매핑 블록을 사용하는 것이 좋습니다.

또 다른 원인은 설정하지 않은 방화벽입니다. 대부분의 제공업체는 서버 내부의 ufw와는 별도로 제어판에서 네트워크 방화벽을 운영하며, UDP 10000 포트는 양쪽 모두에서 열려 있어야 합니다. 어느 계층에서 패킷이 차단되는지 확인하려면 외부에서 누군가 참여하는 동안 서버에서 sudo tcpdump -ni any udp port 10000를 실행하십시오. 패킷이 전혀 보이지 않는다면 서버에 도달하기 전에 차단된 것이므로 운영체제 외부의 문제입니다. 패킷은 도착하는데 타일이 검은색으로 유지된다면 브리지가 클라이언트가 도달할 수 없는 주소로 응답하고 있는 것이며, 이는 매핑 문제입니다. ufw 설정 자체가 확실하지 않다면 VPS에 실제로 필요한 ufw 규칙에서 사용자들이 자주 실수하는 규칙 순서에 대해 다루고 있습니다.

BigBlueButton: 무겁고 독단적이며 서버 전체를 요구함

BigBlueButton은 교육용으로 설계되었습니다. 화이트보드, 소그룹 활동실, 설문조사, 프레젠테이션 영역을 갖추고 있으며, 녹화 파이프라인은 부가 기능이 아닌 핵심 기능으로 취급됩니다. 또한 이 목록에서 압도적으로 가장 무거운 옵션이며, 기존 서버에 추가하는 패키지 형태가 아닙니다.

2026년 8월 기준으로 지원되는 경로는 Ubuntu 22.04 환경의 BigBlueButton 3.0이며, jammy-300 버전 플래그로 선택합니다. 프로젝트에서 명시하는 프로덕션 요구 사항은 16 GB의 메모리(swap 활성화 필수), 8 개의 고성능 싱글 스레드 CPU 코어, 250 Mbps의 대칭형 대역폭, 그리고 녹화본을 보관할 경우 500 GB의 디스크 공간(녹화 기능을 비활성화하면 50 GB)입니다. 사용하는 포트는 TCP 80 및 443번이며, 추가로 UDP 16384에서 32768번 범위를 사용합니다.

wget -q https://raw.githubusercontent.com/bigbluebutton/bbb-install/v3.0.x-release/bbb-install.sh
less bbb-install.sh
bash bbb-install.sh -w -v jammy-300 -s bbb.example.com -e info@example.com -g

프로젝트 공식 예제는 해당 스크립트를 bash로 직접 파이프 연결하여 실행하도록 안내합니다. 하지만 스크립트를 다운로드하여 먼저 내용을 읽어보아야 합니다. 이 스크립트는 nginx 설정을 재작성하고, 자체 미디어 및 오디오 스택을 설치하며, 패키지 버전을 고정하고, 호스트 이름을 점유하기 때문입니다. 이는 결함이 아니라 설계 의도입니다. BigBlueButton은 해당 머신을 완전히 독점하는 것을 전제로 합니다. -w 플래그는 방화벽을 구성하고, -s은 호스트 이름이며, -e은 Let's Encrypt가 등록할 주소이고, -g은 Greenlight 프런트엔드를 추가합니다. 만약 동일한 서버에서 다른 서비스를 위해 TLS를 종료하고 있다면, BigBlueButton을 다른 곳으로 옮기거나 스크립트가 수정하기 전에 nginx 리버스 프록시 설정이 수행하는 작업을 확실히 이해해야 합니다.

차트에 있는 두 행을 주의 깊게 비교하십시오. BigBlueButton은 Jitsi가 제안하는 사양보다 두 배의 메모리와 두 배의 코어를 요구하는 반면, 대역폭은 4분의 1 수준을 요구합니다. 두 수치는 측정 방식이 다르고 가정하는 회의실 규모도 다르므로, 동일 선상에서 비교하기보다는 각 프로젝트의 시작점으로 간주해야 합니다. CPU 격차는 실재하며, 이는 BigBlueButton이 단순히 비디오를 전달하는 것 외에 수행하는 수많은 작업에서 기인합니다.

Galène: 가벼운 선택지

Galène은 Go로 작성된 소형 SFU입니다. 단일 정적 바이너리로 빌드되며 자체 웹 클라이언트를 포함하고, TURN 서버까지 내장하고 있어 XMPP 서버, Java 런타임, Rails 애플리케이션 등을 별도로 유지 관리할 필요가 없습니다. 소규모 서버에서 10명 정도가 참여하는 안정적인 통화 환경이 필요하다면, 하드웨어 증설을 고려하기 전에 이 소프트웨어를 먼저 시도해 보십시오.

sudo apt update && sudo apt install -y git golang-go
git clone https://github.com/jech/galene
cd galene
CGO_ENABLED=0 go build -ldflags='-s -w'
mkdir groups

Ubuntu 24.04의 golang-go 패키지는 Go 1.22 버전을 제공합니다. 만약 go build에서 더 최신 버전의 Go가 필요하다는 오류가 발생하면, 배포판 패키지와 씨름하지 말고 go.dev에서 최신 툴체인을 직접 설치하십시오.

Galène에서 그룹(group)은 회의실을 의미하며, 각 그룹은 JSON 파일로 정의됩니다.

echo '{"users": {"vimes": {"password":"sybil", "permissions":"op"}}}' > groups/night-watch.json
./galene &

https://your.server:8443/group/night-watch/을 열고 vimes 계정으로 로그인하십시오. 해당 자격 증명은 프로젝트의 README에 명시된 기본값이므로, 외부에서 포트에 접근할 수 있게 되기 전에 반드시 변경해야 합니다. 실제 배포 환경을 위해 프로젝트에서는 다음과 같은 systemd 유닛을 권장합니다.

[Unit]
Description=Galene
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/galene
User=galene
Group=galene
ExecStart=/home/galene/galene
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

사용되는 포트는 웹 인터페이스용 TCP 8443, 내장 TURN 서버용 TCP 및 UDP 1194, 그리고 미디어 전송을 위한 높은 번호의 UDP 포트 범위입니다. 방화벽 규칙을 간소화할 수 있도록 이 포트 범위를 고정하십시오.

./galene -udp-range 40000-40100

VPS 환경에서는 -turn 옵션이 중요합니다. -turn ':1194'는 모든 공인 IPv4 주소에서 수신 대기합니다. -turn '203.0.113.1:1194'는 클라이언트가 실제로 인식하게 될 주소를 Galène에 알려주며, 서버의 실제 주소가 사설 IP일 때 필수적입니다. -turn ''은 내장 서버를 비활성화하여 data/ice-servers.json을 통해 외부 서버를 지정할 수 있게 합니다. 기본값은 auto이며, ice-servers.json이 존재하지 않을 때는 :1194와 동일하게 동작합니다.

data/config.json에서 proxyURL을 설정하고 WebSocket 업그레이드 헤더와 함께 /ws 경로를 프록시 처리하여 nginx를 웹 인터페이스 앞에 둘 수 있습니다. 이 구성의 범위를 이해해야 합니다. 클라이언트는 여전히 TURN 포트로 직접 UDP 흐름과 TCP 연결을 생성하므로, 리버스 프록시는 페이지와 시그널링만 처리합니다. 미디어 데이터는 프록시를 거치지 않습니다.

Galène 문서에 따르면 매우 적은 서버 자원만 필요하다고 명시되어 있으나, 구체적인 수치는 제공하지 않으므로 별도의 기대치를 갖지 않는 것이 좋습니다. 앞서 언급한 대역폭 계산 방식은 동일하게 적용됩니다. SFU 외의 불필요한 구성 요소를 제거함으로써 메모리와 관리 복잡도를 절감할 수 있습니다.

Owncast: 대역폭이 선형적으로 유지되는 일대다 스트리밍

많은 "화상 회의" 요구 사항은 사실 한 사람이 발표하고 청중은 채팅으로 참여하는 형태입니다. 귀하의 경우가 그렇다면 SFU는 잘못된 도구이며 경제성 측면에서 완전히 다른 접근이 필요합니다. Owncast는 OBS나 유사한 인코더로부터 RTMP 스트림을 받아 일반적인 HTTPS를 통해 HLS로 송출합니다. 시청자당 대역폭은 이차 함수가 아닌 선형적으로 증가하며, 출력물이 일반 HTTP 세그먼트이므로 이를 오브젝트 스토리지나 CDN 뒤로 배치하여 오리진 서버의 비용 부담을 없앨 수 있습니다.

curl -sL https://owncast.online/install.sh -o install-owncast.sh
less install-owncast.sh
bash install-owncast.sh

프로젝트 문서에서는 이 소프트웨어를 root 권한으로 실행하지 말 것을 권장하며, 원격 스크립트는 실행하기 전에 반드시 내용을 검토하라고 명시합니다. 위에서 다운로드 단계를 분리한 이유가 바로 이것입니다. 설치 프로그램은 현재 릴리스와 ffmpeg 바이너리(시스템에 없는 경우)를 가져옵니다. 설치 디렉터리에서 ./owncast를 실행하고 /admin의 8080 포트에서 관리자 패널을 엽니다. 기본 로그인 계정은 admin이며, 기본 스트림 키인 abc123이 비밀번호로 설정되어 있습니다. 도메인을 서버에 연결하기 전에 반드시 비밀번호를 변경하십시오.

HLS 설계로 인해 두 가지 결과가 발생합니다. HLS는 전체 세그먼트를 전송하므로 시청자는 실시간보다 수 초에서 수 분 정도 지연된 화면을 보게 되며, 따라서 양방향 대화는 불가능합니다. 또한 경로상에 UDP나 TURN이 전혀 없으므로 WebRTC 호출이 연결되지 않는 네트워크 환경에서도 접속이 가능합니다.

Owncast는 이 문서에서 다루는 도구 중 유일하게 의도적으로 트랜스코딩을 수행합니다. 활성화하는 각 출력 품질은 들어오는 스트림을 ffmpeg로 다시 인코딩하는 과정이며, 방송 시간 내내 실행됩니다. 소규모 VPS를 사용한다면 한두 가지 품질만 제공하십시오. 다섯 가지 품질을 모두 활성화하면 네트워크는 유휴 상태임에도 CPU가 포화 상태에 이를 것입니다.

이미 운영 중인 Matrix 서버에서 Element Call 사용하기

이미 Matrix를 운영 중이라면, 영상 기능은 별도의 제품을 추가하는 것이 아니라 기존 환경에 기능을 더하는 과정입니다. 다만 여전히 여러 패키지를 관리해야 합니다. Element Call이 홈 서버 뒤에서 정상적으로 작동하려면 두 가지가 필요합니다. 첫 번째는 미디어 전달을 담당하는 LiveKit SFU입니다. 두 번째는 클라이언트에 LiveKit WebSocket URL과 연결을 위한 서명된 JWT(JSON web token)를 전달하는 MatrixRTC 인증 서비스인 element-hq/lk-jwt-service입니다. 이 서비스는 Matrix federation API를 사용하므로, 앞단에 TLS 리버스 프록시가 필요하며 federation이 접근할 수 있는 도메인 이름이 있어야 합니다.

LiveKit의 권장 포트 구성은 다음과 같습니다.

  • TCP 7880: API 및 클라이언트 WebSocket용 (TLS를 종료하는 프록시 뒤에 배치)
  • TCP 7881: ICE over TCP용 (클라이언트가 UDP를 통해 나갈 수 없을 때 사용)
  • UDP 50000 ~ 60000: 미디어 전송용 (방의 참가자 한 명당 두 개의 포트 사용)
  • UDP 3478 및 TCP 5349: 내장 TURN 서버를 활성화할 경우 사용 (로드 밸런서가 앞에 있지 않다면 5349 포트를 443으로 변경해야 함)

참가자 한 명당 두 개의 포트를 사용한다는 점이 부담스럽게 느껴질 수 있으나 실제로는 그렇지 않습니다. 10,000개의 포트 범위는 수천 명의 참가자를 수용할 수 있으며, 포트 범위가 부족하기 전에 서버의 업링크 대역폭이 먼저 한계에 도달합니다. 포트 범위는 전체를 개방하십시오. 일부만 개방할 경우 특정 사용자에게는 작동하고 다른 사용자에게는 작동하지 않는 문제가 발생하며, 이는 디버깅하기 가장 까다로운 유형의 오류입니다. 이와 관련된 홈 서버 측 설정은 별도의 작업이며, VPS에서 Synapse 홈 서버 운영하기에서 다룹니다.

특정 사용자만 연결되지 않는 이유: TURN과 UDP를 차단하는 네트워크

먼저 용어를 정리합니다. ICE(interactive connectivity establishment)는 두 WebRTC 엔드포인트가 서로 통신할 수 있는 경로를 찾는 과정입니다. STUN(session traversal utilities for NAT)은 클라이언트가 외부에서 보이는 자신의 공인 IP 주소를 확인하게 해주는 작은 서비스입니다. TURN(traversal using relays around NAT)은 릴레이 서버입니다. 직접적인 경로가 없을 때 양측은 TURN 서버로 미디어를 보내고, 서버가 이를 전달합니다.

네트워크 환경을 알 수 없는 참가자를 위해 TURN이 필요합니다. 어떤 사용자는 아웃바운드 UDP가 완전히 차단된 기업이나 캠퍼스 네트워크에 있습니다. 또 다른 사용자는 목적지마다 다른 소스 포트를 할당하는 캐리어급 NAT(Carrier-Grade NAT) 뒤에 있습니다. 이를 대칭형 NAT(symmetric NAT)라고 하며, 이 경우 STUN이 보고한 주소는 사용할 수 없게 됩니다.

증상은 명확합니다. 대부분의 사용자는 정상적으로 참여하지만, 특정 사용자만 참가자 목록과 채팅은 보이고 영상 영역은 검은 화면에 로딩 아이콘만 표시됩니다. 브라우저가 후보(candidate)를 수집했으나 어떤 쌍으로도 연결되지 않아 ICE가 실패 상태로 종료된 것입니다. Chrome에서 chrome://webrtc-internals를 열어 시도하면 후보 쌍과 실패 내역을 확인할 수 있습니다. 해당 사용자에게 모바일 데이터를 사용하여 다시 시도해 보라고 하십시오. 모바일에서 정상 작동한다면 네트워크가 원인이며 TURN이 해결책입니다.

443 또는 5349 포트를 사용하는 TCP 기반 TURN은 거의 모든 환경에서 작동하는 대체 수단입니다. 443 포트의 TLS를 차단하는 네트워크는 이미 웹 접속을 차단한 것과 다름없기 때문입니다. Jitsi 패키지는 coturn을 자동으로 설치하고 설정합니다. 이것이 문서화된 방화벽 규칙에 UDP 3478과 TCP 5349가 포함된 이유입니다. Galène은 1194 포트에 TURN이 내장되어 있습니다. LiveKit은 설정에서 켤 수 있는 내장 TURN 서버를 제공합니다. 직접 coturn을 운영한다면 다음을 참고하십시오.

sudo apt install -y coturn
sudo systemctl enable --now coturn
listening-port=3478
tls-listening-port=5349
fingerprint
use-auth-secret
static-auth-secret=REPLACE_WITH_A_LONG_RANDOM_STRING
realm=turn.example.com
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
min-port=49152
max-port=65535
no-multicast-peers

Debian 및 Ubuntu에서 패키지 서비스는 /etc/default/coturn 파일에서 TURNSERVER_ENABLED=1을 설정하기 전까지 시작되지 않습니다. 설치는 되었으나 활성화되지 않은 coturn은 외부에서 볼 때 TURN 서버가 아예 없는 것과 동일하게 보입니다. 이 한 줄의 설정 때문에 많은 사람이 밤을 지새우며 문제를 해결하곤 합니다.

이제 아무도 언급하지 않는 비용에 대해 설명합니다. 릴레이는 릴레이된 모든 참가자의 전체 미디어 트래픽을 양방향으로 처리합니다. coturn이 SFU와 같은 서버에 있으면 트래픽 대부분이 루프백을 통과하므로 업링크 대역폭 대신 CPU 자원을 소모합니다. 또한 TLS 리스너는 미디어가 이미 포함하고 있는 DTLS 위에 패킷을 한 번 더 암호화합니다. TURN을 별도의 장비로 분리하면, 해당 장비는 SFU와 동일한 수준의 대역폭 계획이 필요합니다. 또한 TCP 기반 TURN은 실시간 미디어를 신뢰성 있는 스트림으로 변환합니다. 따라서 패킷 손실 시 건너뛰는 대신 재전송이 발생하며, 손실이 잦은 링크에 있는 릴레이 참가자는 짧은 끊김 대신 지연 시간이 누적됩니다. 릴레이는 연결을 가능하게 하는 최후의 수단이지만, 직접 연결보다 품질은 떨어질 수밖에 없습니다.

SFU는 언제 트랜스코딩을 수행하며, 그 비용은 얼마입니까?

SFU는 패킷을 전달만 할 뿐 비디오를 디코딩하지 않으므로, 이론상 불가능해 보이는 규모의 회의실도 4개의 코어만으로 처리할 수 있습니다. 하지만 두 가지 기능이 이 특성을 깨뜨리며, 체크박스를 눌러 이 기능을 활성화한 사용자들을 당황하게 만듭니다.

첫 번째는 녹화입니다. Jitsi는 Jibri를 사용하여 녹화하며, Jibri의 공식 문서에는 그 작동 방식이 명시되어 있습니다. Jibri는 가상 프레임버퍼에서 렌더링되는 Chrome 인스턴스를 실행하고, ffmpeg를 사용하여 출력물을 캡처 및 인코딩합니다. 즉, 전체 회의를 렌더링하는 완전한 브라우저와 비디오 인코더가 통화 시간 내내 계속 실행되는 것입니다. 동일한 문서에 따르면 단일 Jibri 인스턴스에서는 한 번에 하나의 녹화만 지원하며, Jibri는 디스플레이나 오디오 장치를 사용하는 다른 애플리케이션이 없는 별도의 물리 서버나 가상 머신에서 실행해야 합니다. 녹화는 단순히 체크박스를 켜는 기능이 아니라, 별도의 서버가 필요한 작업입니다.

두 번째는 전화 접속입니다. 전화선을 회의에 연결하려면 48 kHz의 Opus를 전화망이 수용하는 형식(보통 8 kHz의 G.711)으로 양방향 실시간 변환해야 합니다. 오디오 트랜스코딩은 비디오 트랜스코딩보다 훨씬 저렴하지만, 통화 연결마다 실행되며 통화 내내 멈추지 않으므로 통화자 수에 비례하여 비용이 증가합니다. 전화 접속 번호가 필요하다면 직접 호스팅하는 VoIP 서버가 해당 작업을 수행하며, Jibri와 마찬가지로 전용 서버에서 운영해야 합니다.

실제로 어느 정도 규모의 서버가 필요한가?

두 명의 사용자라면 거의 사양이 필요하지 않습니다. Jitsi는 참가자가 정확히 두 명일 때 기본적으로 피어 투 피어(peer to peer) 모드를 활성화합니다. 이 모드에서는 videobridge를 통해 데이터를 전송하지 않고 직접 연결을 사용합니다. 세 번째 사람이 참여하면 다시 브리지 모드로 전환됩니다. 따라서 1 GB VPS는 일대일 통화 서버로는 훌륭하지만 4인용 서버로는 부족합니다. "테스트할 때는 잘 작동했다"는 보고가 흔한 이유가 바로 이것입니다.

카메라를 켠 상태로 최대 10명 정도가 참여한다면, 8명 참여 시 67.2 Mbps의 대역폭은 일반적인 VPS 업링크로 충분히 감당할 수 있습니다. 녹화를 하지 않는다면 2개의 가상 CPU와 4 GB RAM으로 Jitsi나 Galène을 운영하기에 충분합니다. CPU 그래프보다는 전송량 카운터를 확인하십시오.

30명 규모라면 최악의 경우 1,044 Mbps의 지속적인 대역폭이 필요하며, 이를 20시간 동안 사용하면 9.4 TB에 달합니다. 이 정도 규모에서는 서버 비용보다 대역폭 비용을 먼저 계산해야 합니다. last-N 설정을 켜서 브리지가 최근 발언자의 영상만 전달하도록 하고, 참가자의 카메라를 기본적으로 끄도록 설정하십시오. 또한, 계산된 데이터 전송량을 감당할 수 있는 대역폭 허용량을 가진 곳에 SFU를 배치해야 합니다.

그 이상의 규모라면 단일 VPS로는 적합하지 않습니다. 행사가 사실상 방송 형태라면 Owncast와 CDN을 사용하는 것이 훨씬 저렴합니다. 혹은 단일 시그널링 계층 뒤에 여러 개의 videobridge를 두어야 하는데, 이는 처음 시작한 프로젝트와는 다른 차원의 작업입니다.

마지막으로 라우팅에 관한 조언입니다. 대부분의 팀은 화상 회의보다 채팅을 훨씬 더 오랜 시간 사용하며, 채팅 서비스는 호스팅 비용이 저렴하고 운영도 쉽습니다. 일상적인 대화는 자체 호스팅 Slack 대안을 구축하여 처리하고, 화상 회의 서버는 예정된 통화 시에만 사용하는 방식이 소규모 VPS 예산으로 운영을 지속할 수 있는 방법입니다.

FAQ

Jitsi 회의에 참여는 되는데 서로 보고 들을 수 없는 이유는 무엇입니까?

채팅과 참가자 목록은 TCP 443 포트의 시그널링 채널을 통해 전달되지만, 오디오와 비디오는 UDP 10000 포트를 통해 videobridge로 전송됩니다. 참가자 목록은 표시되는데 모든 화면이 검게 나온다면 시그널링 경로는 정상이나 미디어 경로가 차단된 상태입니다. 서버 자체 방화벽과 제공업체 제어판의 외부 네트워크 방화벽에서 UDP 10000 포트가 열려 있는지 확인하십시오. 그 후 브리지가 자신의 공인 IP 주소를 알고 있는지 확인해야 합니다. 사설 IP를 사용하고 공인 IP가 매핑된 가상 머신이라면 ice4j.harvest.mapping/etc/jitsi/videobridge/jvb.conf 아래에 정적 매핑을 추가하고 jitsi-videobridge2를 재시작하십시오. 누군가 참여할 때 sudo tcpdump -ni any udp port 10000를 실행하면 원인을 파악할 수 있습니다. 패킷이 전혀 들어오지 않는다면 운영체제 외부의 상위 네트워크에서 차단되고 있는 것입니다.

30명 규모의 화상 회의에는 대역폭이 얼마나 필요합니까?

모든 참가자가 카메라를 켜고 SFU가 모든 사람에게 최고 품질의 레이어를 전달하는 최악의 경우, 서버에서 초당 약 1,044 Mbps의 트래픽이 발생하며 이는 시간당 469.8 GB에 해당합니다. 이는 N 곱하기 (N 마이너스 1) 스트림에 각각 1.2 Mbps를 곱한 산술적 계산이며 실제 환경의 측정값은 아닙니다. 실제 사용 시에는 Simulcast와 last-N 설정으로 대역폭이 크게 줄어드는데, 이는 특정 시점에 모든 참가자가 화면에 표시되지는 않기 때문입니다. 하지만 모든 참가자가 동시에 카메라를 켜는 전체 회의 상황을 고려하여 최악의 경우에 가깝게 자원을 산정하십시오.

1 GB RAM의 VPS에서 Jitsi Meet를 운영할 수 있습니까?

설치는 가능하며 2인 통화는 작동합니다. 이는 Jitsi가 2인 통화 시에는 videobridge를 거치지 않고 P2P 모드를 사용하기 때문입니다. 하지만 그룹 통화 서버로는 적합하지 않습니다. Prosody, videobridge, Java 런타임 모두 메모리를 많이 사용합니다. 핸드북에서는 실제 운영을 위해 8 GB를 권장하며, 메모리 부족보다 대역폭 계산상의 한계에 먼저 도달하게 될 것입니다. 1 GB 사양의 서버만 사용 가능하다면 Jitsi보다는 Galène이 더 적합합니다.

VPS에 공인 IP가 있어도 TURN 서버가 여전히 필요합니까?

네, 필요합니다. TURN이 해결하는 문제는 통화 상대방 측에 있습니다. 아웃바운드 UDP를 차단하는 기업 네트워크나 목적지마다 다른 소스 포트를 할당하는 CGNAT(Carrier-Grade NAT) 뒤에 있는 참가자는 서버의 IP가 공인 주소라 하더라도 직접적인 미디어 경로를 생성할 수 없습니다. TCP 443 또는 5349 포트를 사용하는 TURN은 방화벽에서 일반적인 웹 트래픽처럼 보이는 릴레이를 제공합니다. Jitsi 패키지 설치 시 coturn이 기본적으로 설정되는 이유가 바로 이것이며, 문서화된 방화벽 규칙에서 UDP 3478과 TCP 5349 포트를 여는 이유이기도 합니다.

BigBlueButton은 왜 Jitsi Meet보다 훨씬 더 많은 하드웨어 자원을 요구합니까?

단순히 비디오를 전달하는 것 이상의 기능을 수행하기 때문입니다. BigBlueButton의 공식 권장 사양은 16 GB RAM과 8 코어인 반면, Jitsi 핸드북의 권장 사양은 8 GB RAM과 4 코어입니다. BigBlueButton은 오디오 회의 스택, 공유 화이트보드 및 프레젠테이션 레이어, 녹화 및 후처리 파이프라인, 사용자 계정을 포함한 웹 프론트엔드를 모두 동일한 서버에서 실행합니다. 또한 플랫폼에 대한 제약이 엄격하여 2026년 8월 기준 Ubuntu 22.04에서 버전 3.0 설치만을 지원합니다. 두 수치 모두 각 프로젝트의 공식 문서에서 가져온 것이며, 실제 작업 부하를 측정한 값이 아닌 시작점(기준값)으로 이해해야 합니다.

#jitsi#webrtc#video-conferencing#self-hosting#bandwidth