samiles's avatar

Setting on Node.js Socket.io on Laravel Forge

Hi,

I have had a Socket.io server running just fine with my Forge site over HTTP. Since upgrading to HTTPS the connection has broken. I need to set up Socket.io running on my server with SSL. I have multiple sites installed on the server.

I was originally connecting to http://socket.mydomain.com but simply switching to https://socket.mydomain.com and installing SSL on the sub domain doesn't work. It gives a Nginx Bad Gateway error.

Does anyone know how to set this up? My front end code to connect is:

<script type="text/javascript" src="https://socket.example.com/socket.io/socket.io.js"></script>
<script>
var io = io('https://socket.example.com', { secure: true });
</script>

The code of my Socket.io script is:

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

var options = {
    key:    fs.readFileSync('/etc/nginx/ssl/default/54082/server.key'),
    cert:   fs.readFileSync('/etc/nginx/ssl/default/54082/server.crt')
};
var app = https.createServer(options);

var io = require('socket.io').listen(app);

app.listen(3000);

I know the Node.js code is running as it is receiving events and is showing the output it is receiving from Laravel in the terminal - just cannot connect on the front end.

Any ideas on how to achieve this set up and fix the SSL issue? What should the addresses be? I've found no tutorials on setting up with SSL. Thanks

0 likes
8 replies
Ronster's avatar

This is how I have done it for my site soccermasters.eu. I have a wildcard certificate installed on the server.

var fs = require('fs');

var pkey = fs.readFileSync('./server.key');
var pcert = fs.readFileSync('./server.crt')

var options = {
    key: pkey,
    cert: pcert
};

var server = require('https').createServer( options ),
    io     = require('socket.io')(server),
    port   = 8080;

// Logger config
console.log('SocketIO > listening on port ' + port);

var clients = {};

io.on('connection', function (socket){
    // channels here
});
willvincent's avatar

You're not specifying a port in your client side code. The socketio client side JS is being served by node, right? So that should be requested on port 3000, and your socket should also point at port 3000. The way your client side code is presently it's directing everything at nginx, not node.

I assume you aren't actually proxying the node server behind nginx...

spar_x's avatar

Thanks for your code @Ronster ! It actually solved my problem and my socket.io server is now working over https!

I wonder if you or anyone could help me clean up my code a little though. I am not very good with node and all that.. it IS working which is good but it is now throwing the following error which it wasn't before (when I was using it over regular http):

WebSocket connection to 'wss://mydomain.com:3000/socket.io/?EIO=3&transport=websocket&sid=OnsWeQn1bjYhjVDDAAAB' failed: Connection closed before receiving a handshake response

Despite the error however everything is actually working fine. I guess that's because my client isn't trying to use a wss:// connection at all and just connecting via regular https://

Still could someone please take a look at my code below. Basically what I am doing is creating a node server that subscribes to a bunch of redis channels (it gets the channels dynamically over an api call via unirest), and whenever something is pushed to those channels it pushes it along over socket.io so that my client gets realtime notifications in its front-end. I hope that makes sense. I repeat.. this is working atm.. I just feel my code is probably weird and messy because I don't grasp node and all that. I got this setup and working by following some online examples of course.

First here is my previous implementation over http

const PORT = 3000;
const HOST = '0.0.0.0';

var express = require('express'),
    http = require('http'),
    server = http.createServer(app);

var app = express();

const redis = require('redis');
const unirest = require('unirest');
const io = require('socket.io');

if (!module.parent) {
    server.listen(PORT, HOST);
    const socket = io.listen(server);

    socket.on('connection', function (client) {

        const redisClient = redis.createClient()

        unirest.get('http://myDomain.com/channels')
            .end(function (response) {

                if (response.error) {
                    console.log('GET error', response.error)
                } else {

                    channels = response.body.data;

                    for (i = 0; i < channels.length; i++) {
                        redisClient.subscribe(channels[i]);
                    }
                }
            })

        redisClient.on("message", function (channel, message) {
            client.send([channel, message]);
        });

        client.on('disconnect', function () {
            redisClient.quit();
        });
    });
}

And here it is with some modifications thanks to your code example above (and probably redundant dead bodies) which is making it work over https

var fs = require('fs');

var pkey = fs.readFileSync('/etc/nginx/ssl/mydomain.com/12345/server.key');
var pcert = fs.readFileSync('/etc/nginx/ssl/mydomain.com/12345/server.crt')

var options = {
    key: pkey,
    cert: pcert
};

var server  = require('https').createServer( options ),
    io      = require('socket.io')(server),
    redis   = require('redis'),
    unirest = require('unirest'),
    PORT    = 3000,
    HOST    = '0.0.0.0';

// Logger config
console.log('SocketIO > listening on port ' + PORT);

if (!module.parent) {
    server.listen(PORT, HOST);
    const socket = io.listen(server);

    socket.on('connection', function (client) {

        const redisClient = redis.createClient()

        unirest.get('https://myDomain.com/channels')
            .end(function (response) {

                if (response.error) {
                    console.log('GET error', response.error)
                } else {

                    channels = response.body.data;

                    for (i = 0; i < channels.length; i++) {
                        redisClient.subscribe(channels[i]);
                    }
                }
            })

        redisClient.on("message", function (channel, message) {
            client.send([channel, message]);
        });

        client.on('disconnect', function () {
            redisClient.quit();
        });
    });
}

So you see only the top part has been changed. Unlike the http version I am no longer using express and declaring the app. Is this part completely redundant? Any idea why I'm getting the error over https even though my client works fine despite the error. Any chance you can clean up the code a little?

Many thanks!!! Hope maybe this can help someone else too.

Cheers!

1 like
spar_x's avatar

And this is how I connect on my client side. Maybe this is what's causing the error. Is there a way to connect only to the https:// and not the wss:// ?

var socket = io.connect('https://mydomain.com:3000');
var channels=null;

socket.on('connect', function (data) {

    setStatus('connected');

    $.ajax({
        url: '/channels',
        success: function(response) {

            channels = response.data;

            for (i = 0; i < channels.length; i++) {
                socket.emit('subscribe', {channel: channels[i]});
            }
        }
    });
});

socket.on('reconnecting', function (data) {
    setStatus('reconnecting');
});

socket.on('message', function (data) {
    //console.log(data);
    //do something
});
rschaaphuizen's avatar

Just made it working with your examples. I'm running the socket.js as a daemon in laravel forge but it's failing on retrieving the certificate (because i need to run it as root with sudo). How do you guys handle retrieving the certificate without using sudo in the command (which will fail in de daemon command anyway)

Please or to participate in this conversation.