Home Assistant tricks Linux Tutorials

How to install MQTT websockets on NGINX

MQTT is the nerve system for home-assistant and having a secure encrypted connection with your broker is critical for privacy. Imagine running Own-tracks on your phone or connecting to your broker from a hotspot that sniffs on your traffic. Even if you have an mqtt username and password set, these are sent in plain text to the server, thus they will be read by the attacker.

Installing nginx and mosquitto mqtt on linux

If you are familiar with linux system, you know that installing packages is very easy. One useful package to install is mosquitto-clients. This will install mosquitto_pub and mosquitto_sub commands that allows you to subscribe and publish to the broker. Important: these commands DO NOT WORK with websockets, so you need to keep the insecure listener enabled on port 1883.

$ sudo apt-get install nginx mosquitto mosquitto-clients certbot python-certbot-nginx
[... install log...]

# To test mosquitto
# Open a terminal to subscribe to all topics
$ mosquitto_sub -v -h localhost -t '#'

# open another terminal to publish a test message
$ mosquitto_pub -h localhost -t 'test' -m "This is a message"

# in the first terminal you should see the message coming in

At this point you have an insecure mqtt broker listening on the default port 1883. There is no authentication and everyone can publish and listen to your topics. Let’s disable this, add user authentification and enable encryption.

To enable user authentification in moqsquitto you first need to create an username and password. Luckily, mosquitto comes with a tool for this

$ #create an user called test with the password test
$ sudo mosquitto_passwd -c /etc/mosquitto/passwd test
Password: test
Reenter password: test

Mosquitto mqtt secure websockets configuration

To enable websocket listener in Mosquitto you need create a new configuration file e.g /etc/mosquitto/conf.d/secure.conf. Alternatively you can update /etc/mosquitto/mosquitto.conf. Don’t forget to run sudo service mosquitto restart after.

password_file /etc/mosquitto/passwd
allow_anonymous false

#unsecure connection, accept connections only from localhost
listener 1883 127.0.0.1

#secure websockets
listener 8081 127.0.0.1
protocol websockets

If you try to connect now without an username and password you should get the following error. You can also try authentication works.

$ mosquitto_sub -v -h localhost -t '#'
Connection Refused: not authorised.
Connection Refused: not authorised.
^C
$ mosquitto_sub -v -h localhost -t '#' -u test -P test
test This is a message
^C

So now we have mosquitto listening on port 1883 for normal connections and on port 8081 for websockets. Both should be accessible ONLY from localhost.

Configuring NGINX for mosquitto mqtt secure websockets

For letsencrypt certificates to work you first need a domain. Nowadays domains are cheap, so I guess everyone has one already. Let’s imagine we want to access mqtt on mqtt.iotassistant.io. Don’t forget to create the subdomain in your DNS manager first. 

Create a new configuration file named /etc/nginx/sites-enabled/mqtt with the following content:

upstream mqtt.iotassistant.io {
  server localhost:8081;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {

  # listen on both hosts
  server_name mqtt.iotassistant.io;
  
  location / {
     proxy_http_version 1.1;    
     proxy_pass http://localhost:8081;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";   
     proxy_set_header Host $host;
  }
}

Installing the certificate is done with the command letsencrypt (the first time you run the command, you’ll have to answer a few questions).

$ sudo letsencrypt 
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: mqtt.iotassistant.io
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for mqtt.iotassistant.io
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/mqtt

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/mqtt

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://mqtt.iotassistant.io

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=mqtt.iotassistant.io
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/mqtt.iotassistant.io/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/mqtt.iotassistant.io/privkey.pem
   Your cert will expire on 2020-05-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. To non-interactively renew *all* of
   your certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

After the certificate is installed, your nginx domain config file should look like this:

upstream mqtt.iotassistant.io {
  server localhost:8081;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {

  # listen on both hosts
  server_name mqtt.iotassistant.io;
  
  location / {
     proxy_http_version 1.1;    
     proxy_pass http://localhost:8081;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";   
     proxy_set_header Host $host;
  }


    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mqtt.iotassistant.io/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mqtt.iotassistant.io/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}


server {
    if ($host = mqtt.iotassistant.io) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


  server_name mqtt.iotassistant.io;
    listen 80;
    return 404; # managed by Certbot


}

Normally, certbot should automatically renew your certificate, so you don’t have to worry about this.

Connecting and testing mqtt websockets with python

The first test to check that the certificate was installed correctly is to go to your mqtt subdomain ( https://mqtt.iotassistant.io in my case) from a browser. If you see 503 Bad Gatway error, that is NORMAL. It is important to have an https secure connection without errors.

Now you can use this simple python script to test your secure mosquitto mqtt broker:

#!/usr/bin/python3

import sys
import paho.mqtt.client as mqtt

mqtt_client = mqtt.Client(transport='websockets')
mqtt_client.tls_set()
#username and pass
mqtt_client.username_pw_set("test", "test") 
mqtt_client.connect("mqtt.iotassistant.io", 443, 30)
mqtt_client.loop_start()
mqtt_client.publish("test", "Test message").wait_for_publish()

For a more convenient python mqtt wrapper, please check my post about installing DSMR reader for home-assistant using MQTT.

In your subscriber terminal you should see the message

$ mosquitto_sub -v -h localhost -t '#' -u test -P test
test Test message

Leave a Reply