An interesting question came up on Stack Overflow recently that I suggested a hypothetical answer for how to do hostname based proxying for MQTT.
In this post I’ll explore how to actually implement that hypothetical solution.
History
HTTP added the ability to do hostname based proxying when it instroduced the Host
header in HTTP v1.1. This meant that a single IP address could be used for many sites and the server would decide which content to serve based on the his header. Front end reverse proxies (e.g. nginx) can use the same header to decide which backend server to forward the traffic to.
This works well until we need encrypt the traffic to the HTTP server using SSL/TLS where the headers are encrypted. The solution to this is to use the SNI header in the TLS handshake, this tells the server which hostname the client is trying to connect to. The front end proxy can then either use this information to find the right local copy of the certificate/key for that site if it’s terminating the encryption at the frontend or it can forward the whole connection directly to the correct backend server.
MQTT
Since the SNI header is in the initial TLS handshake and is nothing to do with the underlying protocol it can be used for ay protocol, in this case MQTT. This measn if we set up a frontend proxy that uses SNI to pick the correct backend server to connect to.
Here is a nginx configuration file that proxies for 2 different MQTT brokers based on the hostname the client uses to connect. It is doing the TLS termination at the proxy before forwarding the clear version to the backend.
user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } stream { map $ssl_server_name $targetBackend { test1.example.com 192.168.1.1:1883; test2.example.com 192.168.1.2:1883; } map $ssl_server_name $targetCert { test1.example.com /certs/test1-chain.crt; test2.example.com /certs/test2-chain.crt; } map $ssl_server_name $targetCertKey { test1.example.com /certs/test1.key; test2.example.com /certs/test2.key; } server { listen 1883 ssl; ssl_protocols TLSv1.2; ssl_certificate $targetCert; ssl_certificate_key $targetCertKey; proxy_connect_timeout 1s; proxy_pass $targetBackend; } }
Assuming the the DNS entries for test1.example.com
and test2.example.com
both point to the host running nginx then we can test this with the mosquitto_sub
command as follows
$ mosquitto_sub -v -h test1.example.com -t test --cafile ./ca-certs.pem
This will be proxied to the broker running on 192.168.1.1
, where as
$ mosquitto_sub -v -h test2.example.com -t test --cafile ./ca-certs.pem
will be proxied to the broker on 192.168.1.2.
Cavets
The main drawback with this approach is that it requires that all the clients connect using TLS, but this is not a huge problem as nearly all devices are capable of this now and for any internet facing service it should be the default anyway.
Acknowledgment
How to do this was mainly informed by the following Gist
One thought on “Hostname Based Proxying with MQTT”