2
votes

As always I have searched my fingers bloody finding an answer to my issue, so I am reaching out to all you geniuses again! :)

I have set up a Node.js server with socket.io (using express) and it runs nice using port 8443. Well it runs :). Since many of my customer doesn't seem to allow traffic on port 8443 they are not able to use my services.

I am wondering how to setup Node.js on port 443 since the site using the Node-server is already using this port (Https). If I try to use port 443 on my Node-server i get: warn - error raised: Error: listen EACCES

Part of my Node-js code:

var fs = require('fs');
var https = require('https');
var express = require('express');
var socket = require('socket.io');
var port = 8443;

var sslOptions = {
  pfx: fs.readFileSync('mykey.pfx'),
     passphrase: ********
};

var app = express();

app.all('/*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, *');
  next();
});

var server = https.createServer(sslOptions, app);

var io = socket.listen(server, {
    "log level" : 1,
    "match origin protocol" : true,
    "transports" : ['websocket', 'flashsocket'
   , 'xhr-polling'
   , 'jsonp-polling']
});

//No need to list all my socket events I guess

server.listen(port);

Client code to connect to my Node server:

var socket = io.connect("https://www.*MYWEBSITE*.com", { secure: true, port: 8443});
3
I would answer you cant, because only one service can listen on the same port, otherwise you don't know how should accept each call. So short of putting a proxy in-front of both, and understanding the different requests, and forwarding to the correct services on there own ports, but that ether requires their service to move to a different port, so the proxy can sit on 443. Or I could be wrong. - Simeon Pilgrim
But can one say that 443 is the "default" https port? Is there any other port that is considered to be "default" Node.js which all my customers firewalls will accept? - Joakim M
I'm having the same issue. Is it possible to setup proxy in such a way that the request is received in 443 and then it is redirected to node server? I tried it once but when socket switches to ws:// instead of https:// I don't know what to do. - user1741851
@JoakimM Do you have an access to webserver of the site listening 443 port? If so, you can proxy all requests starting with /socket.io to 8443 port. Doing so firewalls will "see" only 443 traffic, but some advanced webserver configuring might be required. Here is an example of proxying websocket traffic with nginx webserver - Oleg
Is there a way to use http-proxy in Node js to do this instead? nginx seems to complex for me (at first glance). - Joakim M

3 Answers

6
votes

I think you've asked a couple different questions here. I'll answer the first one on it's own and it should solve your original problem, but some others have brought up a couple handy deployment options that are worth knowing too.

First, you don't need to run the socket.io server on it's own port. You can just have the socket.io server bind itself to the express app. For example:

// ... require stuff
var app = express();

// ... set up your express middleware, etc

var server = https.createServer(sslOptions, app);
// attach your socket.io server to the express server
var io = require("socket.io").listen(server);
server.listen(port);

Regardless of how you set up your node application it's worth knowing how to set up nginx as a reverse proxy. This is nice because you don't have to be root to use ports < 1024 when running your node app and you can bind many applications to the same IP address through virtual hosts.

Here's an nginx server configuration block that will reverse proxy a node application (and supports websockets).

server {
    listen 80;
    server_name app.com www.app.com # a list of hosts for this application
    access_log /var/log/nginx/access/app.log; # you'll need to create /var/log/nginx/access
    error_log  /var/log/nginx/error/app.log; # and you'll need to create /var/log/nginx/error

    # prevents 502 bad gateway error
    large_client_header_buffers 8 32k;

    location / {

        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

        proxy_set_header Accept-Encoding "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-NginX-Proxy true;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;

        proxy_buffers 8 32k;
        proxy_buffer_size 64k;

        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://127.0.0.1:8000; # put the port of your node app here
        proxy_redirect off;
    }
}
2
votes

You get Error: listen EACCES because you cannot use "privileged ports" (ports < 1024) as non-root user. You could run your node process as root, but don't do that. Better have nginx (battle tested) handle the HTTPS and proxying for you.

See for some info http://nginx.com/blog/websocket-nginx/

2
votes

As already mentioned:

  1. you can't have two services bound to the same port. If IIS (or apache or whatever) is listening on port 443, you must turn it off first.

  2. socket.io and your app listen on the same port.

  3. if you want to bind to a low numbered port (<1024) you need to start your app as root but then de-escalate privileges so that it runs with the least privileges necessary. This can be accomplished using process.setuid:

This example was made using express 4 and socketio 1.0:

var debug = require('debug')('httpssetuid');
var app = require('../app');
var http = require('http');
var https = require('https');
var fs = require('fs');
var exec = require('child_process').exec;
var EventEmitter = require('events').EventEmitter;
var ioServer = require('socket.io');

var startupItems = [];
startupItems.httpServerReady = false;
startupItems.httpsServerReady = false;

var ee = new EventEmitter();

ee.on('ready', function(arg) {
  startupItems[arg] = true;
  if (startupItems.httpServerReady && startupItems.httpsServerReady) {
    var id = exec('id -u ' + process.env.SUDO_UID, function(error, stdout, stderr) {
      if(error || stderr) throw new Error(error || stderr);
      var uid = parseInt(stdout);
      process.setuid(uid);
      console.log('de-escalated privileges. now running as %d', uid);
      setInterval(function cb(){
        var rnd = Math.random();
        console.log('emitting update: %d', rnd);
        io.emit('update', rnd);
      }, 5000);
    });
  };
});

app.set('http_port', process.env.PORT || 80);
app.set('https_port', process.env.HTTPS_PORT || 443);

var httpServer = http.createServer(app);

var opts = {
  pfx: fs.readFileSync('httpssetuid.pfx')
};
var httpsServer = https.createServer(opts, app);

var io = new ioServer();

httpServer.listen(app.get('http_port'), function(){
  console.log('httpServer listening on port %d', app.get('http_port'));
  ee.emit('ready', 'httpServerReady');
});

httpsServer.listen(app.get('https_port'), function(){
  console.log('httpsServer listening on port %d', app.get('https_port'));
  ee.emit('ready', 'httpsServerReady');
});

io.attach(httpServer);
io.attach(httpsServer);

io.on('connection', function(socket){
  console.log('socket connected: %s', socket.id);
});

If stopping IIS is not an option, your best bet is to configure it as a reverse proxy for your node app.