How to configure NGINX to reverse proxy to mqtt using SSL

I want to be able to use nginx to reverse proxy (I don’t understand why it’s called “reverse”) to the mosquito aka mqtt add-on so that I can use mqtt.example.com to connect to the broker using SSL. I want nginx to use route all traffic from port 80 to port 443. In other words I don’t want to open new ports for mqtt. The clients don’t specify a port and they get sent to the SSL port.

The following steps are how I got to the point where I’m able to access mosquitto over port 80. However, I’m stuck on getting it even work on SSL, much less having it routed there. With regualr websites, I’m able to get a Let’s Encrypt SSL certificate installed, as well as routing to 443 for SSL by using CertBot. It usually takes care of everything for you.

sudo certbot --nginx -d mqtt.example.com

However that isn’t working for me, and I don’t know why. I’ve tried both Certbot options for allowing HTTP or forcing HTTPS and neither work. In fact, when I try the “both” option I can’t connect with the test service anymore. Here are the steps I took to set things up. Any help would be appreciated, but I’d really like to make it work using CertBot because that will be updating the certs. Thanks for the use of your eyes. :slight_smile:

I put this together from reading the docs and some of the suggestions from this thread and help from @rbray89.

Steps to get mqtt.example.com working with nginx

Install and configure mosquitto add-on broker

Use hassio to install the mosquitto addo-on. Update the configuration settings in the UI. The changes to the default are setting plain_websockets to *true, anonymous to false and adding a login username/password set.

{
  "plain": true,
  "plain_websockets": true,
  "ssl": false,
  "ssl_websockets": false,
  "anonymous": false,
  "logins": [
    {
      "username": "foo",
      "password": "bar"
    }
  ],
  "customize": {
    "active": false,
    "folder": "mosquitto"
  },
  "certfile": "fullchain.pem",
  "keyfile": "privkey.pem"
}

Configure mqtt component in configuration.yaml.

Modify the configuration.yaml to setup the mqtt component.

mqtt:
  client_id: home-assistant-1
  username: foo
  password: bar
  broker: 127.0.0.1

Create nginx server block

At this point, make sure you have nginx installed. This guide is a pretty good walkthrough to installing nginx, a firewall, and creating a test site. This is not using the nginx addon, but rather is a seperate service. I have mine running on a different machine than what is running hassio. Make test site and use certbot (described in the guide) to make sure that http://test.example.com get’s redirected to https://test.example.com. After you have that test working, create a server block for mqtt:

sudo nano /etc/nginx/sites-enabled/mqtt.example.com

server {
   server_name mqtt.example.com;
   listen 80;
   location /
   {
      proxy_pass http://172.16.68.67:1884; #address of home assistant machine
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
   }
 }

Validate the server block

sudo nginx -t

Restart service

sudo service nginx restart

Use HiveMQ to test externally. Use mqtt.example.com as the host, use port 80, and specify the username and password.

Use CertBot to create and install a cert and modify the server block

sudo certbot --nginx -d mqtt.example.com

Unfortunately this last step not only doesn’t work, it breaks port 80 working. @rbray89, do you have any idea of what I’m missing?

OK, I see your issue now. You’re tryint to use the nginx plugin for certbot. Most documentation I’ve read says you don’t want to do this as it is explicitly broken in some circumstances. You want to use the standalone certbot and expose the webroot in nginx. You can do this with:

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

server {
    server_name mqttws.example.com www.example.com example.com;
    listen 80;

    root /var/www/html;
    index index.php index.html index.htm;

    location ~ /.well-known {
        allow all;
    }
}

Then to get certbot working:

sudo wget https://dl.eff.org/certbot-auto 
sudo chmod +x certbot-auto
sudo certbot-auto certonly --webroot --webroot-path /var/www/html  -d example.com -d mqttws.example.com -d www.example.com -d octopi.example.com --email [email protected]

Then in cron, add the renewal service:

sudo crontab -e
12 13  * * *  /usr/bin/certbot-auto renew --quiet --no-self-upgrade --renew-hook "/bin/systemctl restart nginx.service"

Only thing with this is that you must first disable your sites that nginx uses certs for, run certbot, then re-enable your nginx sites and restart nginix. Once you have your first round of certs though, updating them will be automatic. It is just that nginix will fail if it doesn’t have any certs.

This was my server block for mqtt when I was doing this on a manually setup nginx:

server {
    server_name mqttws.example.com;
    listen 443;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_dhparam /etc/nginx/ssl/dhparams.pem;

    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
    ssl on;
    ssl_protocols TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    proxy_buffering off;


    location / {
        proxy_pass http://localhost:1884/; # The server you want to redirect to
        
        proxy_redirect default;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Upgrade $http_upgrade;
#        proxy_set_header Connection "upgrade";

        proxy_set_header Connection $connection_upgrade;
#        auth_basic "Restricted Content";
#        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

@rbray89

No, I’m not using the nginx plugin. I have nginx installed on a separate machine. The only plugin I’m using here is the mosquito addon.

@FutureTense
Please re-read. You are trying to use the Nginix “plugin” for CERTBOT (eg. using certbot --nginx) I believe this is where your problems are stemming from.

Ah… I’ll try installing Certbot separately. Is there a way to disable the nginx Certbot plugin? It looks like the call to the plugin looks like the same to the standalone.

ETA: Is certbot-auto the standalone?

The --webroot argument makes it standalone. It should be functionally very similar to “–nginx”, but without modifying your nginx config. You might be able to use --nginx certonly, but I’ve never used that myself.

certbot-auto is just the latest certbot script. See here:
nginx:


webroot:

How do you disable sites? Is this done in the server block? If I decide to start over and just get all my sites running on http, will running certbot-auto modify them accordingly like the cerbot plugin?

You should be setting up your sites in /etc/nginx/sites-available and then symlinking to them from in /etc/nginx/sites-enabled. Ideally, each sub domain should be a site, so that you can just remove and re-add the symlink to disable/enable that site. A less ideal approach would be to just comment out the server bock as you mentioned. Using “certbot-auto --webroot” will not touch your nginx sites (which is why it is often preferred in cases like this) All you have to do is provide the .well-known directory in your webroot.

The basic first-time init process is:

  1. Add .well-known and webroot site to nginx.
  2. Disable https sites in Nginx if you have them. (so that when nginx starts up it doesn’t die due to missing cert errors. Note that if you already have all your certs you can skip this step)
  3. Run certbot
  4. Re-enable any disabled sites.
  5. Add cron job for cert renewal

I’ve gone through that procedure with the nginx certbot addin. I also have separate files per site and am using symbolic linking. What I don’t understand is what do I need to do to disable a site? Remove the symbolic link, or is it something else?

Removing the symlink and restarting nginx is all that needs to be done.

I just want to make sure I understand. cerbot-auto does NOT change your serverblocks, correct?

As long as you don’t use --nginx, correct.

Which server block does that go in? Default? Do I need to add each subdomain in there?

I used the default block as I didn’t host anything on port 80. If you disable your other sites this should work for you.

@rbray89 still can’t get it working. I added some info logging to nginx

11 [info] 8998#8998: *24 client sent plain HTTP request to HTTPS port while reading client request headers, client: 73.200.34.241, server: mqtt.noakland.com, request: “GET /mqtt HTTP/1.1”, host: “mqtt.example.com:443

I’m using a web based client test site. Are you able to test your SSL on the following?

I don’t think that client supports HTTPS. The log indicates it is trying to do HTTP on the HTTPS socket, so it will be rejected. I was able to test with owntracks on my android device, as well as a few other MQTT apps.

Whatever client you use to test, you shouldn’t be specifying the port manually. instead, specify the protocol. Eg. https://mqtt.example.com