Expose local port as HTTPS endpoint on the internet

I recently needed to expose local port of my dev machine to the internet. I realized I could combine SSH port forwarding, nginx and letsencrypt. Ubuntu 16.04 is my OS of choice on the server.

I did this mostly to try out letsencrypt. If this looks too complicated for you, you might want to consider using ngrok or similar service.

Port forwarding

Exposing local port using port forwarding is pretty simple assuming you have linux server which you can access by ssh. Let's expose our local port 8000 as port 80 on yourserver.example.com.

$ ssh -N [email protected] -R 80:localhost:8000

This was easy. Now the harder part, HTTPS.

Getting the certificate

I followed tutorial written by Mitchell Anicas. If you get lost on a way please refer back to it. First, let's install nginx and letsencrypt.

$ sudo apt-get update
$ sudo apt-get install nginx letsencrypt

Letsencrypt needs a way to verify that you control the domain name by visiting http://yourserver.example.com/.well-known/SOMETHING. We will use Webroot plugin to automatically create the file for us. Depending on your nginx configuration, you may need to explicitly allow access to the /.well-known directory.

Open default nginx site config:

$ sudo nano /etc/nginx/sites-available/default

Inside the server block, add new location:

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

Now apply our new configuration:

$ sudo systemctl reload nginx

Let's finally get the certificate!

$ sudo letsencrypt certonly -a webroot --webroot-path=/var/www/html -d yourserver.example.com

Letsencrypt will probably ask for your email and if everything succeeds you should be able to see your certificate in following directory(substituting in your domain name):

$ sudo ls -l /etc/letsencrypt/live/your_domain_name

HTTPS proxy

HTTPS configuration is hard, luckily there are configurations we can just copy. It's a good practice to generate strong Diffie-Hellman group.

$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Next, let's create snippet with decent SSL configuration.

$ sudo nano /etc/nginx/snippets/ssl-params.conf
# from https://cipherli.st/ and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

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_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

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

Be careful about add_header X-Frame-Options DENY;, this setting will prevent your site from being embedded in IFRAME. Last task left for us before creating a new tunnel is site configurations. Replace yourserver.example.com with your own domain name. Taking port 80 just for port forwarding is not really helpful with nginx installed, let's use port 1111 instead.

$ sudo nano /etc/nginx/sites-available/default
server {
    server_name yourserver.example.com;
    root /var/www/html;

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:1111;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Ssl on;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }

    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    include snippets/ssl-params.conf;
    ssl_certificate /etc/letsencrypt/live/yourserver.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourserver.example.com/privkey.pem;

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

Reload the nginx configuration and try visiting your server with https. You should see green lock and Bad Gateway.

$ ssh -N [email protected] -R 1111:localhost:8000

After you created the tunnel you should see the server you are running on your machine on port 8000. In case you don't have server ready you can start python server which serves files in current directory.

$ python -m SimpleHTTPServer 8000