Secure remote access to your IoT devices

When you are hacking with IoT devices at home you get to face the challenge of accessing remotely to them, that is from outside your home network. I’m not saying your home network is a safe place, beware. But that thing outside, you know, “the Internet”, it’s so scary… Unfortunately, most IoT devices are just not ready for the jungle. Neither the commercial ones, nor the hacked ones you might have. I wouldn’t dare to open a port in my router to anything inside unless it’s encrypted. So what should we do?


There exist different apps that let you control your devices from anywhere you might be. Think of Blynk for instance. You can use SSL with the blynk-library for ESP8266 or Arduino. You can also do MQTT over SSL. But either one eats so much memory you can do very little else. ESP8266 is barely capable of handling one SSL connection, don’t ask it to also perform as a webserver.

The other popular solution is to create a secured entry point to your network and the most common configuration for that is a reverse proxy using Nginx with SSL. This is what I have at home and since I’m doing some changes to it (soon on another post) I have decided to write down here all the steps required to configure a Raspberry Pi as a reverse proxy to access you0 IoT devices (or any other service you might have at home) from the outside in a secure way.

I’m using a Raspberry Pi 3 at home which hosts several services (Node-RED, InfluxDB, Grafana, Mosquitto,…). It uses Raspbian 8 based on Debian Jessie. I guess you could use older version of the RPi but this one is really fast and I have had no problems so far.

Nginx is a web server, just like Apache. I have been using it as a web server for the last 6 years maybe because it’s so much light weight and faster than Apache and you can pretty much anything you want with it. But we will not be setting up a webserver here, but a reverse proxy. Nginx stands out as a reverse proxy precisely because it’s so light weight it adds very little overhead to the communication.

What is a reverse proxy, you might say? A reverse proxy is a service that sits typically on the edge of your network, just behind your router, and retrieves resources from inside the network. While doing that it might do some other tasks like translating adresses, encrypting/decrypting the communication or hidding resources, to name a few. Here we are interested in the encrypting/decrypting part. Our reverse proxy will encrypt the communication going outside our network and decrypt it when it comes back to us. So even thou our devices do not “speak” SSL all the communications going outside our network throught the proxy will be encrypted on behalf of them.


Install Nginx

Ok, hands on. I’m assuming you have a machine with a Debian Jessie OS (be it a Raspbian 8 or any other). If you have some other OS there might be some differences on the commands below but the overall picture should be the same.

First thing to do is to install Nginx. It couldn’t be easier:

sudo apt-get update
sudo apt-get install nginx

Done. You now should have a Nginx server running on your machine. By default it exposes the contents of “/var/www/html/” on port 80 (HTTP, not secured). If you write the URL of the machine on your browser you should see a welcome message from Nginx. Nice.

Install Let’s Encrypt agent

Now let’s install the SSL certificates. We don’t want to use port 80 but 443 (HTTPS) but before disabling it, let’s see how to create and install the certificates. You can create them yourself or use the awesome service Let’s Encrypt. You can easily create and deploy Let’s Encrypt certificates using EFF Certbot. Certbot comes as a command line tool that automates everything.

if you want to install Certbot on a Debian Jessie you will need to install it from the backports repository. Backports are packages that are being “ported back” from newer versions of the OS. The latests stable Debian is 9.1.

echo "deb jessie-backports main contrib" | sudo tee /etc/apt/sources.list.d/backports.list
sudo apt-key adv --keyserver --recv-keys 8B48AD6246925553
sudo apt-key adv --keyserver --recv-keys 7638D0442B90D010
sudo apt-get update
sudo apt-get install certbot -t jessie-backports

Get SSL certificates for your proxy

Now we just have to tell Certbot to request and deploy a new certificate from Let’s Encrypt. First Certbot will create a key pair and start the communication with Let’s Encrypt providing the public key. Let’s Encrypt must validate that we are administrators of the domain we are requesting the certificate for. So they will provide a challenge: a certain file has to be created under that domain. Certbot will create that file for us, sign the nonce with the private key and tell Let’s Encrypt that we are ready for the challenge. If everything goes OK now Let’s Encrypt will register that our public key is authorized to manage the certificates for our domain. You can read a (probably better) explanation about the process in the how it works page in

So basically Certbot takes care of the whole process. Only, we need to ensure that Let’s Encrypt will be able to read the challenge file from our server under that certain domain. Now, I have dynamic IP at home so I use services to access my server at home (usually only for SSH communications). Dynamic DNS or even subdomains are not supported by most certificate authorities, but Let’s Encrypt does support them. It may happen that browsers complain about the certificate or you might face an error saying there have been too many requests for a certain domain (like, managed by when requesting the certificate.

To solve this second issue I map a subdomain in one of my public (true) domains to the dynamic domain as a CNAME. So imagine you have a “” dynamic domain always pointing at your home IP. And you are also the proud owner of “” (valued at 55k€ right now). All you have to do is to create a CNAME record in your main domain pointing your dynamic domain:        CNAME

Of course if you map a domain to your dynamic one you might as well solve the first problem above. For the moment I’m living with it…

You will then issue a request to get the certificates for On your home router you will also have to open a port to your proxy server. Opening a port simply means mapping the communications coming to a certain port to a machine inside your network. You will need to look for “NAT” on your router configuration and tell the router to redirect all communications on port 80 (HTTP) to your proxy server. You will need to do the same for port 443 (HTTPS).

Now you are ready to instruct Certbot to request the certificate:

sudo certbot certonly --webroot -w /var/www/html -d

If you are getting errors double check that you can reach your Raspberry Pi from outside your network. I normally place a file with some random content on “/var/www/html/hello.txt” and then I visit “” from outside my home network, disabling WiFi on my mobile phone, for instance. If there were no problems you can check where the certificate and key have been stored on the system like this:

[email protected]:~ $ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

Found the following certs:
Certificate Name:
Expiry Date: 2017-12-01 09:38:00+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/
Private Key Path: /etc/letsencrypt/live/

Configure the reverse proxy

Now that we have the certificate let’s configure Nginx to work as an SSL reverse proxy. I usually create a “/etc/nginx/sites-available/proxy” file with these contents:

server {

    # general server parameters
    listen                      443;
    server_name       ;
    access_log                  /var/log/nginx/;       

    # SSL configuration
    ssl                         on;
    ssl_certificate             /etc/letsencrypt/live/;
    ssl_certificate_key         /etc/letsencrypt/live/;
    ssl_session_cache           builtin:1000  shared:SSL:10m;
    ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers                 HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers   on;
    location /livinglamp/ {

      # header pass through configuration
      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;

      # extra debug headers
      add_header              X-Forwarded-For $proxy_add_x_forwarded_for;

      # actual proxying configuration
      proxy_ssl_session_reuse on;
      proxy_pass              http://livinglamp.local/;
      proxy_redirect          default;
      #proxy_redirect         ^/(.*)$ /livinglamp/$1;
      proxy_read_timeout      90;# WS Headers

      # websocket support, thanks to Miguel Otero
      proxy_http_version      1.1;
      proxy_set_header        Upgrade $http_upgrade;
      proxy_set_header        Connection “upgrade”;



There as some things here to note. First we specify the server name ( and we listen on port 443 (HTTPS). Remember you will have to open that port in your router pointing to your reverse proxy machine. Then we configure the certificates, the routes are the same we got previously when running “sudo certbot certificates”. Then we can define as many locations as devices or services we want to map under that domain. Here I just have one (/livinglamp/) mapped to an Itead S20 I have on my living room flashed with ESPurna. The local address of the device is used here (livinglamp.local). As you can guess, we are saying that all traffic comming to “” should be redirected to “http://livinglamp.local/”. We are translating a public URL to a private one and changing from HTTPS to HTTP.

The tricky part here is the proxy_redirect option. It defines the “translation back” and will depend on how your device fetches other local resources like images, for instance. Also this configuration does not deal with query strings, the part of the URL that comes after the ‘?’.

Autorenew certificates

Once you have this working you can go back to your home router settings and delete or disable the map you did on port 80 (HTTP). The only ports you should open are those meant for encrypted communications (HTTPS, SSL, MQTT of SSL,…).

No. Don’t do that. You will need that port to autorenew your SSL certificates. Let’s Encrypt certificates have a validity of 90 days so you will be reneweing them often or forget and the whole set up  will fail. That’s why autorenewing is cool.

Leave that connection open but beware that anything under “/var/www/html” will be publicly visible. We will be using a crontab to try to renew them weekly, if they are not close to the expirity date it will just end silently. We will have to do it from the root crontab:

sudo crontab -e

The file will probably be populated with some explanation and maybe some crontab rules already. We will add these two lines at the bottom:

@weekly /usr/bin/certbot renew --quiet --no-self-upgrade -w /var/www/html/
@daily  service nginx reload

Basically we are asking the crontab to execute the autorenew procedure once a week, and since Nginx won’t reload the new certificates we are also reloading it once a day.

That’s all. I hope this helps you having a more secure experience accessing your IoT devices from the Internet. Of course any comments or ammends you might have, just use the comments section below.

CC BY-SA 4.0 Secure remote access to your IoT devices by Tinkerman is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

30 thoughts on “Secure remote access to your IoT devices

  1. Pingback: Using Google Assistant to control your ESP8266 devices - Tinkerman

    1. Xose Pérez Post author

      Yeah, I use ssh tunnels often but it’s not an option when using 3rd party services like IFTTT.

  2. Pingback: Hacking the Sonoff RF Bridge 433 - Tinkerman

  3. Chris B

    Having a hell of a time setting up mqtt for basic sonoff with node red at present. Have you got a write up for it. And have problems opening reverse shell as using Apache, because used Peter Scarhill all in one script. Any help will be highly appreciated.

  4. Pingback: ESPurna updated to 1.9.4. | esp8266hints

  5. Griskevicius

    Reverse proxy is very good idea. But I’m looking for solution, to secure wireless network. I’m talking about enterprise wifi networks, with radius servers. Because it is not very hard to get connected to your network. Even if you are using VLANs, firewalls ant etc. If we could secure wireless transport layer, then MQTT over SSL ant HTTP without SSL is much more acceptable. I have not yet tested espurna and 8266 devices with enterprise network, so maybe I’m just talking.

    1. Xose Pérez Post author

      Do you mean allowing access depending on the IP, for instance? It could be useful to allow only requests from webhooks servers, for instance. You can use “allow” and “deny” directives in Nginx for that.

  6. riazbapoo

    Hi there … does this mean (if I set this up on a pi 3 for example) that ill be able to ssh to my pi without the need for port forwarding or using a ddns ?

    1. Xose Pérez Post author

      In the post I only talk about creating a reverse proxy over SSL using Nginx for HTTP communications. If you need to SSH to your RPi without port forwarding in your router or ddns you can take a look at SSH tunneling, that’s the technique I use to access machines behind a firewall from my server.

  7. Tof

    Thanks for the idea.

    I do not want to open an access from the Internet to my home LAN. I think for the moment incoming data from outside will be made through an MQTT server on Internet where NodeRed on my LAN will communicate with.

    But this gave me the idea to use reverse proxy to get all my little devices on the same web address on my LAN. So instead of rembering all their IP or name.local, I can get them all under a subfolder of the same web server, and an index listing them all.

    For instance Sonoff RF-Bridge with ESPurna (with proxy-pass for websocket):
    (I had to increase proxy_read_timeout because of websocket connection got closed to early)

    1. Xose Pérez Post author

      Good idea, but how does this scale? You will have to add two new entry points to every device in your network…

  8. Tof

    Well, in my example it was a very early implementation with static information, and indeed in this case I should edit the /site-available/site.conf for each new entry point.

    But Nginx is a fantastic piece of software and very flexible. So it is possible to define lots of things as variables, to use regex or to use already existing nginx variable like upstream servers.

    To scale it: in the code below here is an example a conf with dynamic locations and proxy_pass based on hostname resolution.
    Hopefully I control DNS server on my LAN (, so DNS can say to Nginx that “bridge” is IP And so Nginx will do reverse proxy to

    Note: I shall improve the regex in my example with alphanumeric only, and maybe other rules.

  9. Shippy

    Hi Rose !

    Glad to read this well organized article ! Am reading up on SSH for below.

    You mentioned your article on SSH tunneling for devices behind firewalls-NAT?


    – Can you point me to a nice bash script(s) for automating reverse SSH with auto SSH login assuming a public server and [email protected] with password as “yespass”, for example?

    The idea here is that home routers/devices are behind ISP/NAT firewalls, so it would be nice to be away yet be able to log into your home devices via dialing into a VPS run server (you control and manage.) No need for keys- just simple password(s) would suffice.

    2. Including some script as above set up a simple authn/authz scenario whereby the VPS server can act upon a boot conf file from clients and change some permissions based on user/password challenge, e.g., client MAC address and user email etc.


    1. Xose Pérez Post author

      There are plenty of recipes on the Internet to create a reverse tunnel. Creating a tunnel is as easy as opening it from your destination machine like this:

      ssh -i ~/.ssh/id_mypublicssh -N -R 2242:localhost:22

      Now you can log into that machine from by:

      ssh [email protected] -p 2242

      Usual scenarios involve ensuring the tunnel is open at all times (or opening time windows). About your question on the VPS server, sorry but I just don’t know where to start 🙁

  10. Stef

    Hi Xose,

    I think I am trying to do the opposite of what you describe here. I want my ESPs to connect to third-party APIs over https like ThingSpeak and PushOver. But https is just too much for the ESP. Would Nginx help here too?

    1. Xose Pérez Post author

      You can do HTTPS with an ESP8266 (and of course with an ESP32) but it takes it to the limit and you won’t be able to do much more. About your question. I have never done it but it’s definitely worth trying it!

  11. Miguel Otero

    Hello, thanks for this article.
    I have implemented it, but to access a device with ESPurna, it is not enough with this, since although it is able to locate the ui, later it can not make a connection through websockets, with which it is impossible to modify the configuration.

    In case someone needs it, I have solved it by including the following in the proxy configuration:

    # WS Headers
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection “upgrade”;

  12. Eddie Edwards

    Excellent article, I was able to set this up in a few hours today, with my own CNAME on my DNS, for the dynamic DNS, then I used IFTTT and I was able to communicate from Google Home to an ESP8266 (as per another article on this site) via this reverse proxy configured on an RPi. Fantastic stuff!

    I had a few hiccups:

    – some errors were thrown when I did the key stuff for the backports repo. But I was able to just ignore those steps, hit Y on the apt-get later on when it said stuff was unsigned, and no problems occurred.
    – as an nginx n00b I found I had to ln -s the proxy config file from /etc/nginx/sites-available to /etc/nginx/sites-enabled, in order for nginx to actually load the config.
    – if you start nginx with this config, it will expect to be able to resolve devicename.local at startup time. So your ESP8266 has to be configured and running when you start nginx, if you’re using typical mDNS setup. There are meant to be ways around this but for now, for one device, it’s fine to have it working like this.

    Some cool things:

    – on the ESP8266, you can use server.header(“X-Forwarded-For”) (after server.collectHeaders) to determine if the access is from internal WiFi (no header) or via the rproxy (header exists); then you can disable features externally … for instance my device has its own webpage which you can command it from, but I disabled the commands externally because they don’t have any kind of password attached.
    – the same server.header() logic can be used to remap URLs in HTTP or Location: for a redirect – say I redirect to / on the device’s own webpage, but when going via the rproxy I’d need to redirect to /devicename/ so the URL is valid from the point of view of the external HTTP client.


Leave a Reply (all comments are moderated, be patient)

This site uses Akismet to reduce spam. Learn how your comment data is processed.