188

I'm trying to get socket.io running with my SSL certificate however, it will not connect.

I based my code off the chat example:

var https = require('https');
var fs = require('fs');
/**
 * Bootstrap app.
 */
var sys = require('sys')
require.paths.unshift(__dirname + '/../../lib/');

/**
* Module dependencies.
*/

var express = require('express')
  , stylus = require('stylus')
  , nib = require('nib')
  , sio = require('socket.io');

/**
 * App.
 */
var privateKey = fs.readFileSync('../key').toString();
var certificate = fs.readFileSync('../crt').toString();
var ca = fs.readFileSync('../intermediate.crt').toString();

var app = express.createServer({key:privateKey,cert:certificate,ca:ca });

/**
 * App configuration.
 */

...

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', { layout: false });
});

/**
 * App listen.
 */

app.listen(443, function () {
  var addr = app.address();
  console.log('   app listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Socket.IO server (single process only)
 */

var io = sio.listen(app,{key:privateKey,cert:certificate,ca:ca});
...

If I remove the SSL code it runs fine, however with it I get a request to http://domain.example/socket.io/1/?t=1309967919512

Note it's not trying HTTPS, which causes it to fail.

I'm testing on chrome, since it is the target browser for this application.

I apologize if this is a simple question, I'm a node/socket.io newbie.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Beyond
  • 2,050
  • 2
  • 13
  • 12
  • 1
    Is your client trying to connect to a 'wss://' prefixed URI. – kanaka Jul 06 '11 at 16:29
  • 1
    nope it doesnt get there, it makes the request to http://domain.com/socket.io/1/?t=1309967919512 then dies. – Beyond Jul 06 '11 at 16:40
  • How are you specifying the address to connect to? "domain.com" sounds like a placeholder in the socket.io client-side library. Can you post your client Javascript code that you are using to connect? – kanaka Jul 06 '11 at 16:56
  • 1
    the project is on github: https://github.com/BCCasino/BCCasino – Beyond Jul 06 '11 at 17:54
  • basically becasue its node.js socket.io magically handles the client side stuff, all you do is run socket.connect – Beyond Jul 06 '11 at 17:54
  • in github the code in question is test2.js and index.jade (backend and frontend respetively) – Beyond Jul 06 '11 at 18:39
  • I was trying to make a connection using wss uri it gives me URISyntaxException. Any idea why? – Samuel Robert Apr 22 '16 at 05:15

13 Answers13

199

Use a secure URL for your initial connection, i.e. instead of "http://" use "https://". If the WebSocket transport is chosen, then Socket.IO should automatically use "wss://" (SSL) for the WebSocket connection too.

Update:

You can also try creating the connection using the 'secure' option:

var socket = io.connect('https://localhost', {secure: true});
kanaka
  • 70,845
  • 23
  • 144
  • 140
  • we do this. we goto https : // www.thebitcoinwheel.com and it still makes a request to http automatically, this is something with the socket.io code and is the point of the question. – Beyond Jul 06 '11 at 18:38
  • 19
    `{secure: true}` should not be needed if you specify 'https' in the url. Here is an excerpt from socket.io client source `secure: 'https' == uri.protocol` (version 0.9.16), it sets secure option to true if https is detected in the url. – XiaoChuan Yu Jul 28 '13 at 22:08
  • 9
    I tried this with an https URL and indeed `{secure: true}` was not required to function correctly. – Chiara Coetzee Sep 05 '13 at 22:14
  • 5
    I believe it would be prudent to assure that the connection is secure by using both secure:true and issuing an https url to the client side. This way no matter what you know it will be a secure connection. – gabeio Oct 21 '13 at 14:29
72

The following is how I set up to set it up with express:

    var app = require('express')();
    var https = require('https');
    var fs = require( 'fs' );
    var io = require('socket.io')(server);

    var options = {
        key: fs.readFileSync('./test_key.key'),
        cert: fs.readFileSync('./test_cert.crt'),
        ca: fs.readFileSync('./test_ca.crt'),

        requestCert: false,
        rejectUnauthorized: false
    }

    var server = https.createServer(options, app);
    server.listen(8080);
    

    
    io.sockets.on('connection', function (socket) {
        // code goes here...
    });
    
    app.get("/", function(request, response){
        // code goes here...
    })
   

Update : for those using lets encrypt use this

var server = https.createServer({ 
                key: fs.readFileSync('privkey.pem'),
                cert: fs.readFileSync('fullchain.pem') 
             }, app);
JΛYDΞV
  • 8,532
  • 3
  • 51
  • 77
emonik
  • 1,540
  • 1
  • 17
  • 24
  • 2
    This is the only solution that worked for me. Thanks for saving my time. – hodgef Aug 21 '16 at 05:15
  • worked good for me after a bit of trial and error with the certs – RozzA Sep 21 '16 at 11:17
  • 4
    This solution worked perfect for me, thanks. If you're using the free certs from letsencrypt.org then you can use the following code.. `var server = https.createServer({ key: fs.readFileSync('/etc/letsencrypt/live/domain.name/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/domain.name/cert.pem'), ca: fs.readFileSync('/etc/letsencrypt/live/domain.name/chain.pem'), requestCert: false, rejectUnauthorized: false },app); server.listen(3000);` – Hugo Rune Aug 21 '17 at 10:24
  • 2
    Thanks a lot for this answer. It has been of tremendous help to me. – Harsha Jasti Jul 06 '18 at 18:26
  • I cracked my head looking for a solution in SocketIO and the issue was to config express, fantastic... – Pablo Pazos Dec 22 '19 at 01:02
  • 3
    "`rejectUnauthorized: false` WARNING: it leaves you vulnerable to MITM attacks!" – Martin Schneider Jan 21 '20 at 11:53
  • 2
    Thanks, worked like a charm with letsencrypt and .pem files – Eric Feb 01 '20 at 21:02
  • This almost worked for me. I had to move the line `var io = require('socket.io')(server);` AFTER the one in which the server was declared though. – avance Aug 10 '22 at 19:32
36

On the same note, if your server supports both http and https you can connect using:

var socket = io.connect('//localhost');

to auto detect the browser scheme and connect using http/https accordingly. when in https, the transport will be secured by default, as connecting using

var socket = io.connect('https://localhost');

will use secure web sockets - wss:// (the {secure: true} is redundant).

for more information on how to serve both http and https easily using the same node server check out this answer.

Community
  • 1
  • 1
Kuf
  • 17,318
  • 6
  • 67
  • 91
13

If your server certificated file is not trusted, (for example, you may generate the keystore by yourself with keytool command in java), you should add the extra option rejectUnauthorized

var socket = io.connect('https://localhost', {rejectUnauthorized: false});
clevertension
  • 6,929
  • 3
  • 28
  • 33
  • would be appreciated if you added an example explaining how you used the keytool to create that key for node. 'Cause keys are so complicated and there just aren't enough tutorials about it. – bvdb Mar 08 '19 at 09:23
  • keytool is a tool inside the Java Development Kit (JDK). you can referer this https://docs.oracle.com/javase/10/tools/keytool.htm#JSWOR-GUID-5990A2E4-78E3-47B7-AE75-6D1826259549 – clevertension Mar 11 '19 at 01:11
6

Depending on your needs, you could allow both secure and unsecure connections and still only use one Socket.io instance.

You simply have to instanciate two servers, one for HTTP and one for HTTPS, then attach those servers to the Socket.io instance.

Server side :

// needed to read certificates from disk
const fs          = require( "fs"    );

// Servers with and without SSL
const http        = require( "http"  )
const https       = require( "https" );
const httpPort    = 3333;
const httpsPort   = 3334;
const httpServer  = http.createServer();
const httpsServer = https.createServer({
    "key" : fs.readFileSync( "yourcert.key" ),
    "cert": fs.readFileSync( "yourcert.crt" ),
    "ca"  : fs.readFileSync( "yourca.crt"   )
});
httpServer.listen( httpPort, function() {
    console.log(  `Listening HTTP on ${httpPort}` );
});
httpsServer.listen( httpsPort, function() {
    console.log(  `Listening HTTPS on ${httpsPort}` );
});

// Socket.io
const ioServer = require( "socket.io" );
const io       = new ioServer();
io.attach( httpServer  );
io.attach( httpsServer );

io.on( "connection", function( socket ) {

    console.log( "user connected" );
    // ... your code

});

Client side :

var url    = "//example.com:" + ( window.location.protocol == "https:" ? "3334" : "3333" );
var socket = io( url, {
    // set to false only if you use self-signed certificate !
    "rejectUnauthorized": true
});
socket.on( "connect", function( e ) {
    console.log( "connect", e );
});

If your NodeJS server is different from your Web server, you will maybe need to set some CORS headers. So in the server side, replace:

const httpServer  = http.createServer();
const httpsServer = https.createServer({
    "key" : fs.readFileSync( "yourcert.key" ),
    "cert": fs.readFileSync( "yourcert.crt" ),
    "ca"  : fs.readFileSync( "yourca.crt"   )
});

With:

const CORS_fn = (req, res) => {
    res.setHeader( "Access-Control-Allow-Origin"     , "*"    );
    res.setHeader( "Access-Control-Allow-Credentials", "true" );
    res.setHeader( "Access-Control-Allow-Methods"    , "*"    );
    res.setHeader( "Access-Control-Allow-Headers"    , "*"    );
    if ( req.method === "OPTIONS" ) {
        res.writeHead(200);
        res.end();
        return;
    }
};
const httpServer  = http.createServer( CORS_fn );
const httpsServer = https.createServer({
        "key" : fs.readFileSync( "yourcert.key" ),
        "cert": fs.readFileSync( "yourcert.crt" ),
        "ca"  : fs.readFileSync( "yourca.crt"   )
}, CORS_fn );

And of course add/remove headers and set the values of the headers according to your needs.

Gabriel Hautclocq
  • 3,230
  • 2
  • 26
  • 31
4

check this.configuration..

app = module.exports = express();
var httpsOptions = { key: fs.readFileSync('certificates/server.key'), cert: fs.readFileSync('certificates/final.crt') };        
var secureServer = require('https').createServer(httpsOptions, app);
io = module.exports = require('socket.io').listen(secureServer,{pingTimeout: 7000, pingInterval: 10000});
io.set("transports", ["xhr-polling","websocket","polling", "htmlfile"]);
secureServer.listen(3000);
Jay Jariwala
  • 185
  • 2
  • 10
2

Server-side:

import http from 'http';
import https from 'https';
import SocketIO, { Socket } from 'socket.io';
import fs from 'fs';
import path from 'path';

import { logger } from '../../utils';

const port: number = 3001;

const server: https.Server = https.createServer(
  {
    cert: fs.readFileSync(path.resolve(__dirname, '../../../ssl/cert.pem')),
    key: fs.readFileSync(path.resolve(__dirname, '../../../ssl/key.pem'))
  },
  (req: http.IncomingMessage, res: http.ServerResponse) => {
    logger.info(`request.url: ${req.url}`);

    let filePath = '.' + req.url;
    if (filePath === './') {
      filePath = path.resolve(__dirname, './index.html');
    }

    const extname = String(path.extname(filePath)).toLowerCase();
    const mimeTypes = {
      '.html': 'text/html',
      '.js': 'text/javascript',
      '.json': 'application/json'
    };

    const contentType = mimeTypes[extname] || 'application/octet-stream';

    fs.readFile(filePath, (error: NodeJS.ErrnoException, content: Buffer) => {
      if (error) {
        res.writeHead(500);
        return res.end(error.message);
      }
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content, 'utf-8');
    });
  }
);

const io: SocketIO.Server = SocketIO(server);

io.on('connection', (socket: Socket) => {
  socket.emit('news', { hello: 'world' });
  socket.on('updateTemplate', data => {
    logger.info(data);
    socket.emit('updateTemplate', { random: data });
  });
});

server.listen(port, () => {
  logger.info(`Https server is listening on https://localhost:${port}`);
});

Client-side:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Websocket Secure Connection</title>
</head>

<body>
  <div>
    <button id='btn'>Send Message</button>
    <ul id='messages'></ul>
  </div>
  <script src='../../../node_modules/socket.io-client/dist/socket.io.js'></script>
  <script>
    window.onload = function onload() {
      const socket = io('https://localhost:3001');
      socket.on('news', function (data) {
        console.log(data);
      });

      socket.on('updateTemplate', function onUpdateTemplate(data) {
        console.log(data)
        createMessage(JSON.stringify(data));
      });
      const $btn = document.getElementById('btn');
      const $messages = document.getElementById('messages');

      function sendMessage() {
        socket.emit('updateTemplate', Math.random());
      }

      function createMessage(msg) {
        const $li = document.createElement('li');
        $li.textContent = msg;
        $messages.appendChild($li);
      }

      $btn.addEventListener('click', sendMessage);
    }
  </script>
</body>

</html>
Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • But how do you make port 3001 secure? Only 443 is secure on my VPS. When I try and use 3001, I get a red polling link in Chrome Dev tools. This is because the port is insecure. – Charles Robertson Aug 30 '22 at 23:25
2

For enterprise applications it should be noted that you should not be handling https in your code. It should be auto upgraded via IIS or nginx. The app shouldn't know about what protocols are used.

Urasquirrel
  • 1,461
  • 1
  • 18
  • 34
2

In case someone need a shorter form

var fs = require('fs');
var https = require('https');

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

var options = {
  key: fs.readFileSync('/path-to/ssl.key'),
  cert: fs.readFileSync('/path-to/ssl.cert')
};

var server = https.createServer(options, app);
var io = require('socket.io')(server);
Naser Nikzad
  • 713
  • 12
  • 27
1

This is my nginx config file and iosocket code. Server(express) is listening on port 9191. It works well: nginx config file:

server {
    listen       443 ssl;
    server_name  localhost;
    root   /usr/share/nginx/html/rdist;

    location /user/ {
        proxy_pass   http://localhost:9191;
    }
    location /api/ {
        proxy_pass   http://localhost:9191;
    }
    location /auth/ {
        proxy_pass   http://localhost:9191;
    }

    location / {
        index  index.html index.htm;
        if (!-e $request_filename){
          rewrite ^(.*)$ /index.html break;
        }
    }
    location /socket.io/ {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass   http://localhost:9191/socket.io/;
    }


    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    ssl_certificate /etc/nginx/conf.d/sslcert/xxx.pem;
    ssl_certificate_key /etc/nginx/conf.d/sslcert/xxx.key;

}

Server:

const server = require('http').Server(app)
const io = require('socket.io')(server)
io.on('connection', (socket) => {
    handleUserConnect(socket)

  socket.on("disconnect", () => {
   handleUserDisConnect(socket)
  });
})

server.listen(9191, function () {
  console.log('Server listening on port 9191')
})

Client(react):

    const socket = io.connect('', { secure: true, query: `userId=${this.props.user._id}` })

        socket.on('notifications', data => {
            console.log('Get messages from back end:', data)
            this.props.mergeNotifications(data)
        })

Kathy
  • 409
  • 4
  • 7
0

Server side:

var ssl_options = {
    ca: [fs.readFileSync('../ssl/cert1.crt'), fs.readFileSync('../ssl/cert2.crt'), fs.readFileSync('../ssl/cert3.crt')],
    key: fs.readFileSync('../ssl/xxx.key'),
    cert: fs.readFileSync('../ssl/xxx.example.crt'),
};
var wssServer = https.createServer(ssl_options,app);  // Express app
wssServer.listen(4433, '0.0.0.0');
global.io = require("socket.io")();
io.listen(wssServer);
io.on( "connection", function( socket ) {
    console.log( "user connected" );
});

Client-side (no luck with the built-in WebSocket API):

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js"></script>
<script>
   const socket = io("https://xxx.example:4433",{ transports: ['websocket', 'polling', 'flashsocket'] });
</script>
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Chong Lip Phang
  • 8,755
  • 5
  • 65
  • 100
0

I needed to get this to work with Debian 10, ISPConfig 3 and Let's Encrypt. Took me a while to work out the specifics. Maybe this saves someone else some time…

Server-side:

const fs = require('fs');
const https = require('https');
const express = require('express');
const socketio = require('socket.io');
const app = express();
const https_options = {
    key: fs.readFileSync('/var/www/clients/client1/web1/ssl/your-domain.com-le.key'),
    cert: fs.readFileSync('/var/www/clients/client1/web1/ssl/your-domain.com-le.crt'),
    ca: fs.readFileSync('/root/.acme.sh/your-domain.example/fullchain.cer'),
    requestCert: false,
    rejectUnauthorized: false
}
const server = https.createServer(https_options, app);
server.listen(3000, () => {
    console.log('server started ok');
});
const io = socketio(server, {
    cors: {
        origin: "https://your-domain.example",
    },
    secure: true
});
io.on('connection', (sock) => {
    console.log('someone connected');
}

Client-side:

const sock = io('https://your-domain.example:3000/');
sock.on('status', (text) => {
    add_text($('#status'), text);
});
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
0

I got it working like this:

const server = require("https");
const { Server } = require("socket.io");

const io = new Server({
    cors: {
      origin: "*",
    },
  });

  const folder = path.join(__dirname, "ssl");
  const privateKey = fs.readFileSync(
    path.join(folder, "server_key.pem"),
    "utf8"
  );
  const certificate = fs.readFileSync(
    path.join(folder, "server_cert.pem"),
    "utf8"
  );

  const SSL_CONFIG= {
    key: privateKey,
    cert: certificate,
    ca: [certificate],
    requestCert: false,certificate, tested and it works
    rejectUnauthorized: false,
  };

  const WEB_SERVER = server.createServer(SSL_CONFIG);

  WEB_SERVER.listen(1102);
  io.listen(WEB_SERVER);
Shameel Uddin
  • 511
  • 2
  • 10