Nginx 反向代理 EMQX:HTTPS + MQTT over WSS 完整配置
先说结论
Nginx 反向代理 EMQX,443 端口同时处理 Web HTTPS 和 MQTT over WSS。设备端用
wss://连接,Web 端用https://访问。安全、统一、好管理。
做物联网平台时,设备端通过 MQTT 连接 Broker,Web 端通过 WebSocket 连接展示实时数据。如果平台部署了 HTTPS,MQTT 的明文 1883 端口和 HTTP 的 8083 WebSocket 端口就不能直接暴露。用 Nginx 做反向代理,统一走 443 端口 HTTPS,MQTT over WSS。
一、网络拓扑
互联网 │ ├── HTTPS 443 ──→ Nginx ──┬── / → Web前端 (Nginx静态文件) │ ├── /mqtt → EMQX WebSocket (8083) │ └── /api → 后端API (8080) │ └── MQTT 8883 ──→ Nginx ──→ EMQX TLS (8883)二、Nginx 配置
完整的 Nginx 配置:
# /etc/nginx/sites-available/iot-platform # HTTP → HTTPS重定向 server { listen 80; server_name iot.example.com; return 301 https://$server_name$request_uri; } # HTTPS主配置 server { listen 443 ssl; http2 on; server_name iot.example.com; # SSL证书 ssl_certificate /etc/letsencrypt/live/iot.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/iot.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers on; # HSTS add_header Strict-Transport-Security "max-age=31536000" always; # Web前端静态文件 root /var/www/iot-platform; index index.html; location / { try_files $uri $uri/ /index.html; } # API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; 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; } # MQTT over WebSocket location /mqtt { proxy_pass http://127.0.0.1:8083; 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_read_timeout 300s; proxy_send_timeout 300s; } } # MQTT TLS 直连代理 stream { server { listen 8883 ssl; ssl_certificate /etc/letsencrypt/live/iot.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/iot.example.com/privkey.pem; proxy_pass 127.0.0.1:1883; } }关键配置解释:
Upgrade和Connection "upgrade":WebSocket 升级必需proxy_read_timeout 300s:MQTT 长连接默认 60 秒超时会被断开,改成 300 秒stream模块:处理 TCP 层的 MQTT TLS 直连,和 HTTP 的server块分开http2 on:启用 HTTP/2,提升 Web 性能
三、Nginx stream 模块
默认 Nginx 不加载 stream 模块。在nginx.conf的最外层加载:
# /etc/nginx/nginx.conf user www-data; worker_processes auto; events { worker_connections 1024; } http { # ... 其他http配置 include /etc/nginx/sites-enabled/*; } # stream模块必须和http平级 stream { include /etc/nginx/streams-enabled/*; }# 创建stream配置目录sudomkdir/etc/nginx/streams-enabled# 启用stream模块(如果用apt安装的nginx,默认已编译)# 确认模块:nginx-V2>&1|grepstream四、EMQX 配置
EMQX 端关闭 TLS(让 Nginx 处理 TLS 终止):
# emqx.conflisteners:# MQTT TCP(Nginx stream代理到这个端口)tcp:default:bind:"0.0.0.0:1883"max_connections:100000# MQTT WebSocket(Nginx HTTP代理到这个端口)ws:default:bind:"0.0.0.0:8083"max_connections:10000websocket:mqtt_path:"/mqtt"# 关闭EMQX自带的SSL监听器# ssl:# default:# bind: "0.0.0.0:8883"# ssl_options:# keyfile: ...# certfile: ...# 允许WebSocket从Nginx代理连接websocket:origin:"https://iot.example.com"EMQX 只需要开 1883 和 8083,TLS 交给 Nginx 处理。这种方式叫TLS 终止(TLS Termination),EMQX 只处理明文数据,CPU 开销更低。
五、设备端连接
设备端用 MQTT over WSS:
// Web端(浏览器)constclient=newmqtt.Client("wss://iot.example.com/mqtt",// 走Nginx 443端口"client_"+Math.random().toString(16).substr(2,8));client.onConnect=()=>{console.log("Connected via WSS");client.subscribe("device/001/temperature");};client.connect();# Python设备端(设备直连MQTT TLS)importsslimportpaho.mqtt.clientasmqtt client=mqtt.Client()context=ssl.create_default_context()context.load_verify_locations("ca.crt")# Let's Encrypt证书链client.tls_set_context(context)client.connect("iot.example.com",8883,60)# 走Nginx stream代理# 或用WebSocketclient=mqtt.Client(transport="websockets")context=ssl.create_default_context()context.load_verify_locations("ca.crt")client.tls_set_context(context)client.connect("iot.example.com",443,60)# 走Nginx HTTP代理ESP32 Arduino 端:
#include<WiFiClientSecure.h>#include<PubSubClient.h>WiFiClientSecure esp_client;PubSubClientmqtt_client(esp_client);voidsetup(){WiFi.begin("SSID","password");// 加载CA证书(Let's Encrypt ISRG Root X1)constchar*ca_cert=\"-----BEGIN CERTIFICATE-----\n""MIIFazCCB...省略...\n""-----END CERTIFICATE-----\n";esp_client.setCACert(ca_cert);// 通过WSS连接(443端口)mqtt_client.setServer("iot.example.com",443);mqtt_client.setStream(esp_client);// 使用WebSocket路径// PubSubClient不直接支持WSS,需要用ESP-MQTT库}ESP-IDF 端用esp-mqtt组件支持 WSS:
#include"mqtt_client.h"constchar*mqtt_uri="wss://iot.example.com/mqtt";constchar*cert=\"-----BEGIN CERTIFICATE-----\n""...ISRG Root X1...\n""-----END CERTIFICATE-----\n";esp_mqtt_client_config_tmqtt_cfg={.uri=mqtt_uri,.cert_pem=cert,.client_id="device_001",.lwt_topic="device/001/status",.lwt_msg="offline",.lwt_qos=1,.lwt_retain=1,};esp_mqtt_client_handle_tclient=esp_mqtt_client_init(&mqtt_cfg);esp_mqtt_client_register_event(client,ESP_EVENT_ANY_ID,mqtt_event_handler,NULL);esp_mqtt_client_start(client);六、证书自动续期
Let’s Encrypt 证书 90 天过期,用 certbot 自动续期:
# 安装certbotsudoaptinstallcertbot python3-certbot-nginx# 申请证书(自动修改Nginx配置)sudocertbot--nginx-diot.example.com# 测试续期sudocertbot renew --dry-run# 添加cron定时续期sudocrontab-e# 每月1号凌晨3点检查续期031* * /usr/bin/certbot renew--quiet--post-hook"systemctl reload nginx"七、性能调优
高并发场景下 Nginx 参数:
# /etc/nginx/nginx.conf events { worker_connections 10240; # 每个worker最大连接数 use epoll; # Linux高效事件模型 multi_accept on; # 一次accept多个连接 } http { # 连接复用 keepalive_timeout 65; keepalive_requests 1000; # WebSocket特殊处理 map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Gzip压缩 gzip on; gzip_types text/css application/javascript application/json; # 缓冲区 proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; # 日志格式 log_format mqtt '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'rt=$request_time'; }系统层面:
# 文件描述符限制ulimit-n65535# 或永久修改 /etc/security/limits.confnginx soft nofile65535nginx hard nofile65535# 内核参数 /etc/sysctl.confnet.core.somaxconn=65535net.ipv4.tcp_max_syn_backlog=65535net.ipv4.ip_local_port_range=1000065535sudosysctl-p八、实测数据
Nginx + EMQX 5.0,2核4GB 服务器:
| 指标 | 直连EMQX | Nginx代理 |
|---|---|---|
| 最大WebSocket连接数 | 50000 | 50000 |
| 消息吞吐 | 100k/s | 95k/s |
| TLS握手延迟 | 0ms(已建连) | 20ms(首次) |
| 连接建立延迟 | 5ms | 15ms |
| CPU占用 | 40% | 55% |
| 内存占用 | 500MB | 700MB |
九、踩坑清单
- WebSocket 路径:Nginx 的
location /mqtt要和 EMQX 的mqtt_path一致。不一致会 404 - proxy_read_timeout:默认 60 秒。MQTT Keep Alive 通常设 30-120 秒,如果 Nginx 超时比 Keep Alive 短,连接会被 Nginx 断开
- HTTP/1.1:WebSocket 代理必须设
proxy_http_version 1.1,默认 1.0 不支持 Upgrade 头 - stream 模块位置:stream 块必须在 nginx.conf 的最外层,不能放在 http 块内
- 证书链:Let’s Encrypt 的 fullchain.pem 包含中间证书。只发 cert.pem 会导致部分设备验证失败
- TLS 版本:IoT 设备可能不支持 TLSv1.3。
ssl_protocols TLSv1.2 TLSv1.3兼容性最好 - ALPN:HTTP/2 需要 ALPN 协商。Nginx 默认支持,但如果设备端 mbedTLS 版本低可能协商失败
- Nginx reload 不中断:
nginx -s reload不会断开现有连接。但如果 stream 配置改了,需要nginx -s reopen - X-Forwarded-For:EMQX 拿到的源 IP 是 Nginx 的 127.0.0.1。要在 EMQX 中配置
proxy_protocol或从 X-Forwarded-For 头取真实 IP