MQTT Bridge with Mosquitto and nginx

Author: akeil
Date: 2017-03-05
Version: 1

Set up a Mosquitto [1] MQTT broker which is available in the internet. nginx [2] is used as a reverse proxy and to handle SSL encryption. An additional Mosquitto instance on the local network is used as a bridge to forward MQTT messages from the local network to the internet and vice versa.

This allows to easily connect devices which do not support authentication or encryption on the local network with with other devices or services connected via the internet.

/images/mqtt-bridge-overview.png

Overview for an MQTT bridge scenario. Clients on the local network use an insecure connection to talk to the local MQTT broker while clients on the internet use a secure connection.

Mosquitto (public)

The public Mosquitto instance needs some configuration changes. The configuration file is located at /etc/mosquitto/mosquitto.conf.

Since we will be running behind nginx, make the MQTT service listen on localhost only:

bind_address localhost

Change MQTT Service Port (optional)

Change the default port number from 1883 to something else:

listener 9883

This is strictly not necessary as the default is normally used for unencrypted connections and we will configure nginx to listen on port 8883 (the default port for MQTT over SSL).

Finally, set up mosquitto to require authentication with username and password:

allow_anonymous false
password_file = /etc/mosquitto/passwords

Create the password file and the first user with:

# mosquitto_passwd -c /etc/mosquitto/passwords username

(You will be prompted for a password.)

To test authentication, attempt to subscribe to any topic. A valid username and password should work, a subscription attempt with invalid credentials should be rejected.

$ mosquitto_sub -p 9883 -t '#' -u username -P secret

nginx

Configure nginx as a reverse proxy for TCP streams [3] and to terminate the SSL encryption [4].

First, define Mosquitto as an upstream service. Use either 1883 or whatever alternative port is set for the default (unencrypted) MQTT service.

Next, set up a server to listen on port 8883 (MQTT over SSL) and tell it to pass requests to the upstream service.

To enable SSL, configure the location of the SSL certificate and private key. Set additional SSL options as needed; in this case taken from cipherli.st [5].

Note

The example assumes a Let's Encrypt [6] certificate.

The nginx configuration looks like this (with stream being a toplevel-directive):

stream {

    upstream mosquitto {
        server localhost:9883;
    }

    server {
        listen 8883 ssl;
        proxy_pass mosquitto;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        # from https://cipherli.st/
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_ecdh_curve secp384r1;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;

        ssl_dhparam /etc/ssl/certs/dhparam.pem;
    }

}

MQTT-Bridge

On the local (LAN) MQTT broker, edit the configuration to add a bridge:

connection bridge
address <ip-or-hostname>:8883

remote_username foo
remote_password xxx

remote_clientid LAN-broker
local_client public-broker

# must specify cafile to enable SSL
bridge_cafile /etc/ssl/certs/DST_Root_CA_X3.pem
bridge_insecure false

# topic mappings
# topic PATTERN [out|in|both] QOS LOCAL-PREFIX REMOTE-PREFIX
topic foo both 0

Remember to specify a port with the bridge connection, otherwise 1883 is used. The bridge_cafile setting is required to enable SSL. See Mosquitto conf [7] for more details.

Restart mosquitto to apply the new settings.

The Mosquitto log file for the local broker should contain a line like this:

Connecting bridge bridge (<ip-or-hostname>:8883)

On the remote side, the bridge looks like a normal client connection:

New client connected from ::1 as <remote_clientid> (c0, k60, u'<remote_username>').

To test, subscribe to foo on the local broker...

$ mosquitto_sub -h <localbroker> -t foo

... and publish to foo on the remote broker:

$ mosquitto_pub -h <remotebroker> -p 8883 -u username -P secret -t foo -m test

You should see the "test" message appear on the session subscribed to the local broker.

Topic Mappings

A topic mapping consists of a pattern to subscribe to, the direction of the messages (in, out or both and a QoS setting. Additionally, a local prefix and remote prefix can be defined.

Only the pattern is required. Direction defaults to out, QoS defaults to 0.

Examples:

# forward all 'foo' messages to the bridge
topic foo
topic foo out 0  # same as above

# two-way forwarding for everything under "foo"
topic foo/# both

# (only) receive all foo messages
topic foo/# in

This can be used to forward only selected topics and to insert topics from the bridged broker into the local topic tree.

If no topic directive is present, no messages are forwarded across the bridge.


[1] https://mosquitto.org/
[2] https://nginx.com/
[3] https://www.nginx.com/resources/admin-guide/tcp-load-balancing/
[4] https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-termination/
[5] https://cipherli.st/
[6] https://letsencrypt.org/
[7] https://mosquitto.org/man/mosquitto-conf-5.html