Новинка Виртуальный VDS сервер в Нидерландах от 390р
Выберите продукт

MQTT over WebSocket через Nginx: Mosquitto, TLS, keepalive и timeouts

Разбираем, как поднять Mosquitto и безопасно отдать его в браузер через Nginx (WSS). Дам рабочие конфиги брокера и reverse proxy, оптимальные keepalive/timeouts, фильтр Origin и лимиты, тесты mosquitto_pub/sub и Paho JS, отладку, производительность и чек‑лист перед продом.
MQTT over WebSocket через Nginx: Mosquitto, TLS, keepalive и timeouts

Когда MQTT выходит за пределы устройств и попадает в браузер, нам неизбежно нужен WebSocket. Браузеры не умеют в сырой TCP на порту 1883, зато отлично работают с ws:// и wss://. Самый простой и управляемый путь — поставить брокер Mosquitto за Nginx, а на периметре завершать TLS и делать nginx reverse proxy для WebSocket апгрейда. В этой инструкции соберём рабочую схему, разберём конфиг, таймауты, keepalive, тестирование и типичные грабли.

Зачем MQTT over WebSocket и почему через Nginx

MQTT поверх WebSocket обеспечивает доступ из веб-клиентов без лишних костылей. Дальше — вопрос удобства эксплуатации и безопасности. Завершая TLS на Nginx, мы получаем:

  • Единый TLS-терминатор и понятный контроль сертификатов, шифров и HSTS.
  • Гибкую маршрутизацию и защиту от всплесков на уровне HTTP (лимиты, логирование, доступ по Origin).
  • Удобные таймауты и читаемые логи, пригодные для централизованного сбора.
  • Простой путь к масштабированию за счёт upstream-групп или перенаправлений.

Брокер Mosquitto остаётся внутри, слушает websockets-листенер только на 127.0.0.1 (или в приватной сети), а наружу выходит только Nginx с wss://. Это и безопасно, и прозрачно. Разворачивать связку удобно на облачном VDS, особенно если нужна изоляция и контроль ресурсов.

Архитектура: потоки и порты

Базовая раскладка портов в типичной инсталляции:

  • Клиент → Nginx: 443/tcp (wss://, TLS завершаем в Nginx).
  • Nginx → Mosquitto: 9001/tcp (ws:// до локального websockets-листенера).
  • Локальные или приватные издатели/подписчики: по желанию 1883/tcp без TLS только внутри контура.

Не открывайте наружу порт Mosquitto 9001 (websockets) и, как правило, 1883 (raw MQTT). Для внешнего трафика достаточно wss:// на 443/TCP через Nginx.

Настройка Mosquitto и Nginx для WSS на экранах терминала

Настраиваем Mosquitto для WebSocket

Пример минимального /etc/mosquitto/mosquitto.conf для работы за Nginx. Включаем аутентификацию, локальный TCP и websockets-листенер, ограничим keepalive и размер сообщений.

# /etc/mosquitto/mosquitto.conf
per_listener_settings true

# Без анонимов
allow_anonymous false
password_file /etc/mosquitto/passwd

# Персистентность (по желанию)
persistence true
persistence_location /var/lib/mosquitto/

# Локальный raw MQTT (для внутренних агентов)
listener 1883 127.0.0.1

# WebSocket для Nginx
listener 9001 127.0.0.1
protocol websockets

# Полезные лимиты
max_keepalive 600
message_size_limit 1048576

# Логи
log_type error
log_type warning
log_type notice
log_timestamp true

Создаём парольный файл и пользователя:

sudo mosquitto_passwd -c /etc/mosquitto/passwd iot_admin
sudo systemctl restart mosquitto

Для узлов с большим числом одновременных соединений увеличьте лимиты файловых дескрипторов через drop-in для systemd:

sudo systemctl edit mosquitto
[Service]
LimitNOFILE=65536
Restart=always
RestartSec=2s
sudo systemctl daemon-reload
sudo systemctl restart mosquitto

Помните, что Mosquitto не перечитывает весь конфиг по SIGHUP; существенные изменения требуют рестарта службы.

Nginx: TLS и reverse proxy для WebSocket

Нам нужен корректный апгрейд до WebSocket (заголовки Upgrade и Connection), адекватные таймауты и выключенное буферизование. TLS завершаем на периметре, выставляем современные протоколы и защищённые настройки сессий. Для публичного доступа используйте доверенный сертификат из раздела SSL-сертификаты. Если параллельно настраиваете HSTS и перенос домена — пригодится разбор переезда домена с 301 и HSTS.

# В http{}.
map $http_upgrade $connection_upgrade {
    default upgrade
    ''      close
}

# Локальный upstream до Mosquitto WebSocket listener
upstream mosquitto_ws {
    server 127.0.0.1:9001 max_fails=2 fail_timeout=10s;
    keepalive 32;
}

# Необязательный фильтр по Origin (замените example.com на ваш домен)
map $http_origin $ws_allowed {
    default 0;
    ~^https?://(www\.)?example\.com$ 1;
}

# Лимит логирования только рукопожатий 101 (по желанию)
map $status $ws_handshake { ~^101$ 1; default 0; }
log_format mqtt '$remote_addr - $host [$time_local] "$request" $status $body_bytes_sent '
                '"$http_user_agent" "u=$http_upgrade" "c=$connection"';

server {
    listen 443 ssl http2;
    server_name mqtt.example.com;

    # TLS (заглушки путей под ваши сертификаты)
    ssl_certificate /etc/ssl/certs/fullchain.pem;
    ssl_certificate_key /etc/ssl/private/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    access_log /var/log/nginx/mqtt_access.log mqtt if=$ws_handshake;
    error_log  /var/log/nginx/mqtt_error.log warn;

    # Точка подключения WebSocket клиентов
    location /mqtt {
        # Необязательная проверка Origin
        if ($ws_allowed = 0) { return 403; }

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $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_connect_timeout 5s;
        proxy_read_timeout 1h;
        proxy_send_timeout 1h;

        # WS не буферизуем
        proxy_buffering off;

        # Не переключаем upstream при сбоях — WS плохо переживает
        proxy_next_upstream off;

        proxy_pass http://mosquitto_ws;
    }
}

Несколько замечаний:

  • proxy_read_timeout должен быть больше, чем MQTT keepalive клиента. Если клиенты посылают PINGREQ каждые 60 секунд, ставьте хотя бы 5–10 минут или больше под ваш сценарий.
  • Для WebSocket буферизация должна быть выключена: proxy_buffering off, иначе можно получить задержки и распухание памяти.
  • Журналируйте только рукопожатия 101 — объём логов меньше, а полезные параметры запроса остаются.
  • Наличие http2 на сервере не мешает WebSocket: браузеры инициируют WS по HTTP/1.1, а обычные запросы спокойно идут по HTTP/2.

Ограничения соединений и защита от всплесков

Во время массового переподключения клиентов (например, при кратковременном сетевом сбое) полезно ограничить скорость рукопожатий и коннектов на уровне Nginx. Пример базовых лимитов:

# В http{}
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_req_zone  $binary_remote_addr zone=wsreq:10m rate=5r/s;

server {
    # ...
    location /mqtt {
        limit_conn perip 20;
        limit_req zone=wsreq burst=10 nodelay;
        # ... остальной конфиг из примера выше
    }
}

Значения подбирайте под поведение клиентов и ожидаемую нагрузку. Важен баланс: не «убить» легитимный шквал переподключений, но и не допустить лавинообразной нагрузки. Если нужен удобный GUI для сервера — пригодится сравнение панелей для VDS.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Keepalive и timeouts: практические пресеты

MQTT keepalive — это интервал, с которым клиент посылает PINGREQ, чтобы брокер (и все прокси) знали, что соединение живо. Если прокси не видит трафика дольше своего read_timeout, он закроет соединение.

  • Для браузеров и мобильных клиентов ставьте keepalive 30–60 секунд. В JS-клиентах это обычно keepAliveInterval.
  • В Nginx держите proxy_read_timeout минимум в 5–10 раз больше клиентского keepalive (например, 10–30 минут). Для нестабильных сетей иногда требуется 1–2 часа.
  • У Mosquitto лимитируйте max_keepalive (например, 600 секунд), чтобы плохо настроенные клиенты не висели без пингов.
  • TCP keepalive ядра (net.ipv4.tcp_keepalive_*) полезен для «застрявших» соединений на уровне NAT/фаерволов, но не заменяет MQTT keepalive.

Пример системных настроек TCP keepalive (опционально, для серверов с большим количеством долгоживущих соединений):

# /etc/sysctl.d/99-mqtt-ws.conf
net.ipv4.tcp_keepalive_time=600
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=5

Применить:

sudo sysctl --system

Тесты mosquitto_pub/sub и браузерный клиент по WSS

Тестируем: CLI и браузер

Проверим, что проксирование wss:// работает. Начнём с CLI-утилит из пакета Mosquitto. Система должна доверять вашему сертификату (или укажите путь к корневым сертификатам).

# Подписка через WSS на 443
mosquitto_sub -h mqtt.example.com -p 443 --capath /etc/ssl/certs --protocol websockets -t 'test/hello' -v -u iot_admin -P 'secret'

# Публикация сообщения
mosquitto_pub -h mqtt.example.com -p 443 --capath /etc/ssl/certs --protocol websockets -t 'test/hello' -m 'hi from wss' -u iot_admin -P 'secret'

В браузере (например, Paho MQTT JS) это выглядит так:

// Пример инициализации Paho MQTT JS по WSS через Nginx
const clientId = "web_" + Math.random().toString(16).slice(2, 10);
const client = new Paho.MQTT.Client("mqtt.example.com", 443, "/mqtt", clientId);

client.onConnectionLost = (res) => console.warn("lost", res.errorMessage);
client.onMessageArrived = (msg) => console.log(msg.destinationName, msg.payloadString);

client.connect({
  useSSL: true,
  userName: "iot_admin",
  password: "secret",
  keepAliveInterval: 60,
  reconnect: true,
  cleanSession: true,
  onSuccess: () => {
    console.log("connected");
    client.subscribe("test/hello", { qos: 1 });
  },
  onFailure: (e) => console.error("connect failed", e)
});

Обратите внимание на путь /mqtt — он должен совпадать с локацией в конфиге Nginx. Mosquitto не использует путь при обработке WebSocket (для него это просто транспорт), но прокси — использует.

Отладка: что ломается чаще всего

  • 400/403 на рукопожатии. Проверьте заголовки Upgrade и Connection. В Nginx для WS обязательно нужны proxy_set_header Upgrade $http_upgrade и proxy_set_header Connection $connection_upgrade. Если включили фильтр по Origin — убедитесь, что он пропускает ваш сайт.
  • Обрыв через ровно N минут. Признак слишком малого proxy_read_timeout на Nginx или слишком большого MQTT keepalive у клиента. Уменьшите keepalive клиента и/или увеличьте read_timeout прокси.
  • "upstream prematurely closed connection" в error_log. Чаще всего брокер завершил соединение (лимиты, авторизация) или вы подключились не по WebSocket, а сырой MQTT на /mqtt. Проверьте протокол клиента и логи Mosquitto.
  • Проблемы с сертификатом. Для CLI укажите --capath /etc/ssl/certs или путь к CAfile, которым подписан серверный сертификат. В браузере используйте публично доверенный сертификат.
  • Зависания после апдейта. nginx -s reload безопасно перезагружает конфиг без разрыва активных WS, а Mosquitto для изменений конфига чаще требует restart — планируйте окна.

Производительность и масштабирование

Для десятков тысяч соединений на узел следите за тремя классами ограничений: файловые дескрипторы, сетевые очереди ядра и память на прокси/брокере.

  • nofile для Nginx и Mosquitto (см. LimitNOFILE выше). В Nginx проверьте worker_connections и итоговый предел worker_processes * worker_connections.
  • Очереди сокетов: net.core.somaxconn, net.ipv4.tcp_max_syn_backlog, если наблюдаете Listen queue overflow.
  • Логи: не логируйте каждый WS-трафик, только рукопожатия. Иначе диски и CPU уйдут в логирование.

Горизонтальное масштабирование по нескольким брокерам возможно, но требует «прилипчивости» соединений. В классическом Nginx HTTP upstream есть ip_hash, но за NAT он может работать плохо. Для MQTT часто используют маршрутизацию по ClientID на уровне приложения или брокер-кластеры с синхронизацией состояния. Если всё же балансируете через Nginx, оставляйте proxy_next_upstream off, иначе WS не переживёт переключение корректно.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды

Чек-лист перед продом

  • Mosquitto: allow_anonymous false, заведены пользователи, ограничен max_keepalive, проверена message_size_limit, локальный websockets-листенер на 127.0.0.1:9001.
  • Nginx: корректные заголовки Upgrade/Connection, длинные proxy_read_timeout, proxy_buffering off, отключён proxy_next_upstream для WS.
  • TLS: валидный сертификат, включены TLSv1.2/1.3, HSTS, отключены session tickets, если нет ротации ключей.
  • Безопасность: опциональный фильтр по Origin, лимиты limit_req/limit_conn, закрыты наружу порты Mosquitto.
  • Тесты: mosquitto_sub и mosquitto_pub по wss://, браузерный клиент, проверка реконнекта при рестарте брокера.
  • Мониторинг: метрики по числу соединений и задержкам, алерты на рост 4xx/5xx в Nginx и ошибки аутентификации в Mosquitto.

Итоги

Схема «Nginx как обратный прокси + Mosquitto за ним» даёт предсказуемый и безопасный способ отдать MQTT в браузер через wss://. Небольшой, но важный набор настроек — апгрейд заголовков, длинные таймауты, отключённая буферизация, аккуратные лимиты и базовая защита на периметре — избавит вас от рваных сессий и лишней возни. Дальше остаётся работать с логикой приложения и обдуманно масштабировать брокер там, где это действительно нужно.

Поделиться статьей

Вам будет интересно

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...