Laravel Echo Server (Socket.IO server) and Redis Broadcaster with multiple app instances behind a load balancer

Anouar Abdessalam
5 min readDec 25, 2018

--

I assume you are already familiar with setting up Laravel Echo Server for an application on a single server instance. I won’t be covering that in this article.

Note: Laravel Echo Server functions as a WebSocket server.

Example of Application Load Balancing Architecture:

Issue to address:

In this architecture, we cannot set up a WebSocket server in each application instance as we typically do when running a single application. Doing so would result in multiple Echo servers running, one for each application instance (in this case, two Echo servers). Moreover, this approach won’t function correctly due to our applications’ traffic being routed behind a load balancer. To address this issue, we need to attend to two crucial aspects:

1- Centralize the WebSocket server (Echo server).

To centralize our Echo server, we need to install it on a single instance and then connect both app1 and app2 to it. We will create a new instance and use it for both Redis and the Echo server. In my case, I will use Laravel Forge to manage my instances.

Redis/Echo server instance configuration: After creating your Redis/Echo server instance, add your site to it as you normally do. Deploy your application code on it and ensure that you install the dependencies, especially if you plan to load the socket.io library in your layout. Use the following request:

<script src=”//{{ Request::getHost() }}/socket.io/socket.io.js”></script>

SSH into your Redis/Echo server instance and globally install the Laravel Echo server on it.

$ npm install -g laravel-echo-server

Access your application folder and initialize your Laravel Echo Server configuration.

$ cd example.com/
$ laravel-echo-server init

When you are done, your configuration file should look something like this:

{
"authHost": "[laravel app url]",
"authEndpoint": "/broadcasting/auth",
"databaseConfig": {
"redis": {},
},
"host": "[echo server hostname]",
"port": "[echo server port]",
"protocol": "http",
...
...
}

The [Laravel app URL] is your absolute Laravel application URL without a trailing slash (e.g., https://example.com). Typically, it is formatted as https://example.com.

In this example, the [Echo server hostname] refers to the private IP address of our Redis/Echo server instance.

The [Echo server port] is the port number we will use to connect to the Echo server, for this example, I will use 65080.

Because our requests to the Echo server will be made via port 65080, we need to add a firewall rule in the Redis/Echo server to accept HTTP requests on port 65080. Laravel Forge makes this process very simple, you just need to access the network page and, under firewall rules, add your rule. You can leave the “From IP” field empty.

App1/App2 Configuration: If you are using Forge, navigate to your App1 and App2 instances. In the network tab, ensure that they are configured to connect to the Redis/Echo server.

As you can see, my App1 instance is connected to the Redis/Echo server. Please perform the same configuration for App2. Now, our Redis/Echo server instance is set up, and both App1 and App2 instances are connected to it. Let’s proceed to the second step.

2-Handle WebSocket connections inside the load balancer.

I have an SSL certificate set up for my domain. As you may have noticed, I use ‘http’ in my Echo server configuration instead of ‘https’. I made this choice because I plan to utilize a proxy module within NginX that redirects WebSocket traffic. Instead of connecting our WebSocket traffic to ‘https://example.com:65080/socket.io' and attempting to secure it, we will connect our WebSocket traffic to ‘https://example.com/socket.io'. Behind the scenes, NginX proxy module will be configured to intercept requests for ‘/socket.io’ and internally redirect them to our Echo server over non-SSL on port 65080. This ensures that all traffic remains encrypted between the browser and the web server, as our web server will handle SSL encryption/decryption. The only aspect left unsecured is the traffic between our web server and the Echo server, which might be acceptable in many cases.

Now, SSH into your load balancer and edit the NginX configuration file. If you have provisioned your load balancer with Forge, it will look something similar to the following:

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/example.com/before/*;
# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/example.com/before/*;
# FORGE CONFIG (DO NOT REMOVE!)
include upstreams/example.com;
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
# FORGE SSL (DO NOT REMOVE!)
ssl_certificate /etc/nginx/ssl/example.com/464063/server.crt;
ssl_certificate_key /etc/nginx/ssl/example.com/464063/server.key;
ssl_protocols TLSv1.2;
charset utf-8;
access_log off;
error_log /var/log/nginx/example.com-error.log error;
# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/jobslake.com/server/*;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;

proxy_pass http://675891_app/;

proxy_redirect off;
# Handle Web Socket Connections
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location /socket.io {
proxy_pass http://10.0.1.245:65080;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/jobslake.com/after/*;

As you can see, I added a new configuration to the Nginx server within the server {} block.

In our Load Balancer, NginX will intercept requests for /socket.io and internally redirect them to our Echo server over non-SSL on port 65080.

proxy_pass http://10.0.1.245:65080

The only remaining step now is to fix the host in the client-side for both app1 and app2 instances. Navigate to resources/assets/js/bootstrap.js and delete the port from the host. The NginX proxy will handle that for us.

if (typeof io !== 'undefined') {   window.Echo = new EchoLibrary({      broadcaster: 'socket.io',      host: window.location.hostname
})
}

Now that we have everything set up, all that’s left is to start the Echo server. SSH into your Redis/Echo server and initiate the server from your application root:

$ cd example.com/
$ laravel-echo-server start

Your Echo server is prepared to accept WebSocket connections on port 65080. Utilize Supervisor to initiate it as a background process.

Application architecture, including Redis/Echo Server:

hope you were able to follow along with me. This is my first article :)

Good luck!

Follow me on Twitter
Add me on LinkedIn

--

--