Professor, Designer, Husband, Father, Gamer, Bagpiper

Over the past half year, I've set up a few instances of Hubs Cloud, first for VR2020 and then for personal, education, and research uses. I usually find myself wanting to also run a node server on the same domain, to let me run other services and serve up related content. I've been using the same recipe, gleaned from a few pages I found around the web, so I thought I would share it. (Ok, I'm also putting it here so I remember how I did this, for the next time!)

Companion Node Server for Hubs

For my setup, I follow the instructions in the Hubs AWS Quickstart to set up Hubs, and leave space for a node server on the top level domain. For the domains, I set them up on Route 53 and configure them using the Hubs Route 53 Domain Recipe number 2: I configure Hubs to run on a subdomain on Route 53, so I can put the node server at the top level.  Let's say I have "mysite.com" as my main domain, I have been installing Hubs on "hubs.mysite.com" and putting a node server on "mysite.com".  We first did this for IEEE VR 2020 in March, where ieeevr.online was a node server, and hubs.ieeevr.online was the Hubs instance.

Setting up a Node Server

For the node installation, I've been following the two-part tutorial on HackerNoon (part 1, part 2). (But, see below for some changes you'll want to make).

If you follow this closely, you'll end up with a pretty nice starting point for your node installation:

  • a small EC2 instance is running your server, and pm2 is configured to keep it running, and restart it as necessary.
  • the contents of your node server will be in a github respository.
  • when you push changes to the repository, you can run "npm run deploy" locally in the github repo to deploy and restart the server.

There are some additional things I needed, however, which I'll describe below.  

HTTPS via LetsEncrypt

First and foremost, I wanted to use HTTPS, not HTTP;  in fact, I want to redirect HTTP requests to use HTTPS and never serve up HTTP content.

In part 1 of the tutorial, when you set up a security group, you should add a line for HTTPS, and another inbound rule for port 3001.  I initially was supporting both HTTP and HTTPS, so I added an HTTPS line after their HTTP line (which shows port 443 instead of 80), and another Custom TCP Rule for port 3001.

To get the HTTPS certificates set up, I started with this tutorial on ITNEXT.  I installed certbot on my EC2 server, and followed the steps outlined there.  The one change I made is that I did not serve up the verification challenge file from a static server, instead of just hard-coded a line in the node express server.

Here is what my expanded, https-version of the simple index.js file from the tutorials looks like.  (This is for the domain "aelatgt.net" ... you would change those lines as appropriate for your installation of certbot).

const fs = require('fs');
const http = require('http');
const https = require('https');

const express = require('express');
const app = express()

app.use(express.static('public'))

app.get('/.well-known/acme-challenge/sdk7Ne4KyfDw6dLwD39qIqJ8BcFiAWTLYLeZjhE9ylc', (req,res) => {
    res.send("sdk7Ne4KyfDw6dLwD39qIqJ8BcFiAWTLYLeZjhE9ylc.VeFbm-Pcx9jG1LNNYKt1-ssk8U1QMse-QJsLzcWPGiI")
})
  
/////  Starting only https servers, in the end, so comment this out
// const httpServer = http.createServer(app);
// httpServer.listen(3000, () => console.log('HTTP Server running on port 3000'))

/////////
const privKeyFileName = '/etc/letsencrypt/live/aelatgt.net/privkey.pem'
const certFileName = '/etc/letsencrypt/live/aelatgt.net/cert.pem'
const chainFileName = '/etc/letsencrypt/live/aelatgt.net/chain.pem'

if (fs.existsSync(privKeyFileName) && fs.existsSync(certFileName) && fs.existsSync(chainFileName)) {
  const privateKey = fs.readFileSync(privKeyFileName, 'utf8');
  const certificate = fs.readFileSync(certFileName, 'utf8');
  const ca = fs.readFileSync(chainFileName, 'utf8');

  const credentials = {
    key: privateKey,
    cert: certificate,
    ca: ca
  };

  const httpsServer = https.createServer(credentials, app);
  httpsServer.listen(3001, () => console.log('HTTPS Server running on port 3001'))
} else {
  console.log("https certs are not available, not starting https server")
}```

Updating the NGINX configuration

The finally step to making this work is to update the nginx configuration in the Hackernoon tutorial to use HTTPS, and to redirect HTTP calls to HTTPS.  The nginx config in that tutorial looks like this:

server {
  listen 80;
  server_name tutorial;
  location / {
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  Host       $http_host;
    proxy_pass        http://127.0.0.1:3000;
  }
}```

An article by Victor Leung provided the guidance I needed to update my configuration.  The final config file looks like this:

server {
  listen 80;
  server_name aelatgt.net;
   return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;
  server_name aelatgt.net;
  location / {
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  Host       $http_host;
    proxy_pass        https://127.0.0.1:3001;
  }
    # Enable SSL
    ssl_certificate_key /etc/letsencrypt/live/aelatgt.net/privkey.pem;
    ssl_certificate /etc/letsencrypt/live/aelatgt.net/cert.pem;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
    ssl_prefer_server_ciphers on;
}```

Again, you'd want to change the certbot paths as appropriate for your installation (and I certainly need to update the server home page from the silly "The internet was a mistake" gif they use ... but, all things in due time!)

If I've left anything out, please let me know, I'll be happy to update this page with fixes or more details.