
В свете сами знаете чего, свой приватный мессенджер и возможность звонков на XMPP стали как никогда актуальны.
Есть быстрый и простой способ: за несколько минут вы поднимаете собственный Jabber-сервер. Дальше — дело техники: рассылаете приглашения маме, бабушке, теще, жене и соседке Даше. После этого можно спокойно звонить и переписываться в защищённом мессенджере, который полностью под вашим контролем.
Звонки идут в зашифрованном режиме, работают p2p. А если у собеседника хитрый NAT, то на помощь автоматически приходит встроенный STUN-сервер.
Клиенты есть под все платформы: Android, iOS, Windows, macOS и Linux.
Что понадобится?
VDS / VPS с белым IP
Домен
Пошаговая настройка
-
Установка docker и Docker Compose версии 2
Если уже установлен, то пропускайте этот шаг. Для Ubuntu / Debian
sudo apt update sudo apt install -y docker.io docker-compose-plugin sudo systemctl enable --now docker
Для RHEL / Rocky Linux / AlmaLinux
sudo dnf -y install dnf-plugins-core sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin sudo systemctl enable --now docker
Можно запустить и на старых дистрибутивах — всё будет работать. Но на этом останавливаться не будем. Скрипты требуют Docker Compose версии 2 и несовместимы с устаревшим docker-compose v1.
-
Прописываем DNS
A chat.your-domain.com → <белый IP VDS/VPS> CNAME groups.chat.your-domain.com → chat.your-domain.com CNAME share.chat.your-domain.com → chat.your-domain.com
Создаем одну A-запись и две CNAME
-
Открываем порты
Ubuntu / Debian
# Веб/ACME sudo ufw allow 80/tcp sudo ufw allow 443/tcp # XMPP sudo ufw allow 5222/tcp # c2s sudo ufw allow 5269/tcp # s2s (федерация, опционально) # STUN/TURN (основные и альтернативные порты) sudo ufw allow 3478/tcp sudo ufw allow 3478/udp sudo ufw allow 3479/tcp sudo ufw allow 3479/udp sudo ufw allow 5349/tcp # TLS sudo ufw allow 5349/udp sudo ufw allow 5350/tcp sudo ufw allow 5350/udp # RTP-реле TURN (медиа) sudo ufw allow 49152:65535/udp # (Опционально) proxy65 для file-transfer sudo ufw allow 5000/tcp
RHEL / Rocky Linux / AlmaLinux
# Веб/ACME sudo firewall-cmd --permanent --add-port=80/tcp sudo firewall-cmd --permanent --add-port=443/tcp # XMPP sudo firewall-cmd --permanent --add-port=5222/tcp sudo firewall-cmd --permanent --add-port=5269/tcp # STUN/TURN sudo firewall-cmd --permanent --add-port=3478/tcp sudo firewall-cmd --permanent --add-port=3478/udp sudo firewall-cmd --permanent --add-port=3479/tcp sudo firewall-cmd --permanent --add-port=3479/udp sudo firewall-cmd --permanent --add-port=5349/tcp sudo firewall-cmd --permanent --add-port=5349/udp sudo firewall-cmd --permanent --add-port=5350/tcp sudo firewall-cmd --permanent --add-port=5350/udp # RTP-реле TURN (медиа) sudo firewall-cmd --permanent --add-port=49152-65535/udp # (Опционально) proxy65 sudo firewall-cmd --permanent --add-port=5000/tcp sudo firewall-cmd --reload
-
Ставим snikket
cd /opt git clone https://github.com/snikket-im/snikket-selfhosted.git snikket cd snikket ./scripts/init.sh
После запуска
./scripts/init.sh
вас спросят имя вашего домена и email, после этого запускаем:./scripts/start.sh
Если все прошло ок, то https://<your domain>/ уже будет форма для ввода логина и пароля.
Теперь осталось сформировать и послать самому себе админский инвайт:
./scripts/new-invite.sh --admin --group default
-
Админка и инвайты для юзеров
После того как вы создали админский инвайт и прошли регистрацию, можно зайти на сайт
https://<ваш-домен>/
— и вы попадёте в админ-зону:Скриншот Из админ-зоны можно сразу отправить первое приглашение. Как только человек его примет и установит приложение — можно звонить и переписываться.
На этом всё!
Технические подробности
Если кому-то интересно, то давайте посмотрим, что мы получили из коробки:
Сервер XMPP (Jabber) — личный мессенджер без рекламы и слежки.
Поддержка федерации — общение с пользователями на других серверах XMPP.
Группы и чаты — приватные и публичные комнаты.
Файлы и мультимедиа — отправка фото, документов
Звонки — встроенный STUN/TURN для аудио- и видеосвязи (WebRTC).
Сквозное шифрование и автономность аккаунта — сквозное шифрование, независимость от телефонного номера
Мультиаккаунты и мультидевайсы — один юзер может быть на нескольких устройствах.
Docker-установка — быстрый запуск на Linux-сервере.
Минимальные требования — достаточно 1 ГБ RAM, поддерживается Raspberry Pi.
Бесплатно — всё бесплатно, звонки работают через встроенный TURN/STUN, ну либо есть ещё Snikket Hosting для тех, кому совсем лень и готов просто заплатить около $6/мес за готовый сервер с поддержкой медиарелеев по всему миру и автоматическим обновлением
Jitter‑буфер для WebRTC — служит буфером, выравнивает задержки и помогает стабильно передавать аудио, особенно при нестабильных сетях и сложных NAT.
Snikket сам получает и продлевает сертификаты Let’s Encrypt. У него внутри есть certbot-контейнер, который автоматически запрашивает и обновляет сертификаты и для веб‑портала, и для служб XMPP, STUN/TURN
Немного о jitter-буфере
Jitter‑буфер — это временное хранилище аудио‑пакетов, которое регулирует их подачу на клиент в равномерном потоке, чтобы избежать искажений и пропаданий звука при переменных задержках сети или нестабильном подключении.
В Snikket такой буфер встроен в WebRTC-движок, поэтому, даже если пользователь за NAT с нестабильной связью, звонок остаётся плавным и без резких «дёрганий» аудио.
Немного о звонках
По умолчанию звонки P2P. Клиенты стараются установить прямое соединение друг с другом через STUN.
Если NAT сложный (симметричный, CGNAT и т.п.), тогда в дело вступает TURN-сервер. В Snikket он встроен, можно оставить свой или подключить внешний. TURN просто пересылает UDP-пакеты между клиентами.
TURN не расшифровывает медиапоток. Всё зашифровано протоколом SRTP (Secure RTP) в рамках WebRTC.
Даже администратор сервера, на котором крутится TURN, не может прослушать или записать звонок, потому что у него нет ключей для расшифровки. Он видит только зашифрованный поток байтов.
То есть схема такая:
Обычный случай: звонок идёт напрямую P2P.
Сложный NAT: трафик «прокачивается» через TURN, но всё равно остаётся end-to-end encrypted, без возможности перехвата.
Маскировка аудиотрафика
Если всё работает стабильно — лучше ничего не менять, так вы сохраните качество звука. Но если провайдер или мобильный оператор начинает «резать» звонки, можно запустить TURN на порту 443 и замаскировать трафик под HTTPS.
Есть два способа:
Использовать дополнительный белый IP и повесить TURN на него, на порт 443.
Запустить nginx-сплиттер и разрулить всё на одном IP.
Оба варианта мы разберём чуть ниже. А пока — поднимем turnserver в Docker: он пригодится в любом случае.
mkdir /opt/turnserver
cd /opt/turnserver
Создаем в этой папке файл docker-compose.yml вот с таким содержанием:
version: "3.8"
services:
coturn:
image: instrumentisto/coturn:latest
container_name: coturn
restart: unless-stopped
network_mode: host
volumes:
- /opt/turnserver/turnserver.conf:/etc/coturn/turnserver.conf:ro
Вариант A: ДВА «белых» IP
Snikket живёт на IP₁ (HTTP/HTTPS/XMPP), а TURN — на IP₂ и слушает 443/TLS
напрямую.
DNS:
chat.your-domain.com → IP₁
turn.your-domain.com → IP₂
Snikket (IP₁) Указываем внешний TURN в файле /opt/snikket/snikket.conf добавляем строки
SNIKKET_TWEAK_TURNSERVER=0
SNIKKET_TWEAK_TURNSERVER_DOMAIN=turn.your-domain.com
SNIKKET_TWEAK_TURNSERVER_SECRET=ME_SECRET
Делаем: docker compose up -d
чтобы конфиг перечитался.
В файл /etc/coturn/turnserver.conf
кладем следующее:
listening-ip=IP_2
tls-listening-port=443
relay-ip=IP_2
fingerprint
use-auth-secret
static-auth-secret=ME_SECRET
realm=turn.your-domain.com
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
И далее docker compose up -d
для перечитывания
Плюсы: проще, меньше прослоек; отличная устойчивость, звонки реально «как HTTPS» на 443/TLS; меньше джиттера по сравнению с проксированием через stream.
Минусы: нужен второй IP.
Вариант B: ОДИН «белый» IP и nginx как сплиттер
Создаем поддомен turn.your-domain.com и вешаем на тот же самый белый ip.
Настраиваем nginx примерно так:
stream {
map $ssl_preread_server_name $backend {
turn.example.com turn_tls;
default https_upstream;
}
upstream turn_tls { server 127.0.0.1:5349; }
upstream https_upstream { server 127.0.0.1:8443; }
server {
listen 443 reuseport;
proxy_pass $backend;
ssl_preread on;
}
}
Отдаем HTTP на Snikket, чтобы он сам выпускал/продлевал сертификаты:
server {
listen 80; listen [::]:80;
server_name chat.your-domain.com groups.chat.your-domain.com share.chat.your-domain.com;
location / {
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:5080; # SNIKKET_TWEAK_INTERNAL_HTTP_PORT
}
}
Указываем внешний TURN в файле /opt/snikket/snikket.conf добавляем строки
SNIKKET_TWEAK_TURNSERVER=0
SNIKKET_TWEAK_TURNSERVER_DOMAIN=turn.your-domain.com
SNIKKET_TWEAK_TURNSERVER_SECRET=ME_SECRET
Делаем: docker compose up -d
чтобы конфиг перечитался.
В файл /etc/coturn/turnserver.conf
кладем следующее:
listening-ip=127.0.0.1
tls-listening-port=5349
relay-ip=<ПУБЛИЧНЫЙ_IP>
fingerprint
use-auth-secret
static-auth-secret=<ДЛИННЫЙ_СЕКРЕТ>
realm=example.com
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
docker compose up -d
чтобы конфиг перечитался.
Плюсы: один IP, звонки «как HTTPS» на 443/TLS; Snikket продолжает сам управлять LE.
Минусы: чуть сложнее; extra‑хоп (nginx stream) → немного повышается латентность/джиттер.
Комментарии (6)
AlexandreFrolov
20.08.2025 08:14Еще бы экран расшаривать, тогда можно было бы созвоны проводить по работе.
Rilkener Автор
20.08.2025 08:14Я слышал, что для таких задач часто используют Jitsi — он умеет и видео, и демонстрацию экрана. Сам не пробовал.
baldr
20.08.2025 08:14Так, вроде бы, как раз проблема вотсапа в том, что STUN-трафик детектится и блокируется?
Rilkener Автор
20.08.2025 08:14В Snikket это обходится просто: если обычный STUN не проходит, клиенты автоматически переходят на TURN. А TURN можно поднять на порту 443/TLS и замаскировать трафик под HTTPS — тогда его уже не отличить от обычного веба.
pewpew
Какие клиенты вы посоветуете, поддерживающие голосовой и видео траффик?
Rilkener Автор
Голос через WebRTC, поэтому клиенты нужны с поддержкой XMPP-звонков.
Самое простое — использовать приложение, которое предлагает Snikket прямо в инвайте (скриншот с примером инвайта):
Android / iOS — Snikket App (основан на Conversations/Monal, всё работает «из коробки»).
Windows / macOS / Linux — Gajim (последние версии поддерживают звонки), также можно попробовать Dino под Linux.
Для iOS дополнительно есть Siskin IM.
То есть, если вы рассылаете инвайт, пользователю даже не нужно гадать — ссылки на подходящие клиенты уже там есть.