linux · tech

How to install Laravel Echo Server in production

Laravel Echo Server – LES from now on – is a NodeJS and server to use with Laravel Echo broadcasting.

From what we gathered, LES does not have any way to have a decent clustered / HA setup: the internal state is in memory, only the channel subscriptions are shared via database (Sqlite3 or Redis), so we agreed with our client that a floating Virtual IP handled by a Pacemaker+Corosync cluster would do, even if that meant the state would be lost in case of a cluster switch.

Installing NodeJS and NPM

The first step was to install NodeJS and the Node Package Manager on our host:

# curl -LO ''
# bash setup_8.x
# yum -y install nodejs gcc-c++ make

Creating a user and a group for LES

We wanted LES to run as the same nginx user ID and group ID as we had on the webservers, so we created a user and group.

# groupadd -g 994 nginx
# useradd -m -u 996 -g nginx nginx

Generating the SSL certificates

LES would serve clients through SSL, so we had a self-signed certificate set up for testing purposes, to be switched for a trusted cert in production.

# su - nginx

nginx$ mkdir -p /home/nginx/ssl/les/2018-selfsigned
nginx$ cd /home/nginx/ssl/les/2018-selfsigned
nginx$ openssl req -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.pem -days 365
Generating a 4096 bit RSA private key
writing new private key to 'server.key'
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [XX]:IT
State or Province Name (full name) []:Parma
Locality Name (eg, city) [Default City]:Parma
Organization Name (eg, company) [Default Company Ltd]
Organizational Unit Name (eg, section) []:LES
Common Name (eg, your name or your server\'s hostname) []
Email Address []

nginx$ cd /home/nginx/ssl/les/
nginx$ ln -s 2018-selfsigned/server.key ./
nginx$ ln -s 2018-selfsigned/server.pem ./

Installing LES and PM2

We finally get to install LES; we’ll also install pm2 to handle the startup and restart of LES.

The official website recommends Supervisor, but in our experience pm2 works a lot better for NodeJS.

# su - nginx

nginx$ npm config set prefix /home/nginx
nginx$ npm install pm2 sqlite3 laravel-echo-server

Configuring LES

LES needs a configuration file that you can generate with the init command.

# su - nginx

nginx$ /home/nginx/node_modules/laravel-echo-server/bin/server.js init
? Do you want to run this server in development mode? No
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. http://localhost
? Will you be serving on http or https? https
? Enter the path to your SSL cert file. /home/nginx/ssl/les/server.pem
? Enter the path to your SSL key file. /home/nginx/ssl/les/server.key
? Do you want to generate a client ID/Key for HTTP API? Yes
? Do you want to setup cross domain access to the API? Yes
? Specify the URI that may access the API: *
? Enter the HTTP methods that are allowed for CORS: GET, POST
? Enter the HTTP headers that are allowed for CORS: Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id

This will create a laravel-echo-server.json file in our nginx home.

Unfortunately, even if the init command asks about the database, it doens’t create a valid configuration for Redis, you’ll have to manually add the host and port parameters, as shown below:

    "authHost": "http://localhost",
    "authEndpoint": "/broadcasting/auth",
    "clients": [
            "appId": "XXXX",
            "key": "YYYY"
    "database": "redis",
    "databaseConfig": {
        "redis": {
      "host": "vip-redis",
      "port": "6379"
        "sqlite": {
            "databasePath": "/database/laravel-echo-server.sqlite"
    "devMode": false,
    "host": null,
    "port": "6001",
    "protocol": "https",
    "socketio": {},
    "sslCertPath": "/home/nginx/ssl/les/server.pem",
    "sslKeyPath": "/home/nginx/ssl/les/server.key",
    "sslCertChainPath": "",
    "sslPassphrase": "",
    "apiOriginAllow": {
        "allowCors": true,
        "allowOrigin": "*",
        "allowMethods": "GET, POST",
        "allowHeaders": "Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id"

Configuring PM2

Once LES is set up, we can configure pm2 by creating an echo-server.json:

  "name": "echo",
  "script": "/home/nginx/node_modules/laravel-echo-server/bin/server.js",
  "args": "start"

And we can start it and verify that it works correctly:

# su - nginx

nginx$ pm2 start echo-server.json

[PM2][WARN] Applications echo not running, starting...
[PM2] App [echo] launched (1 instances)
│ App name │ id │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user  │ watching │
│ echo     │ 0  │ fork │ 2193 │ online │ 0       │ 0s     │ 4%  │ 11.0 MB   │ nginx │ disabled │
 Use `pm2 show ` to get more details about an app

nginx$ pm2 log
PM2        | [2018-03-26 11:27:19] PM2 log: Starting execution sequence in -fork mode- for app name:echo id:0
PM2        | [2018-03-26 11:27:19] PM2 log: App name:echo id:0 online
0|echo     | L A R A V E L  E C H O  S E R V E R
0|echo     |
0|echo     | version 1.3.6
0|echo     |
0|echo     | Starting server...
0|echo     |
0|echo     | ✔  Running at localhost on port 6001
0|echo     | ✔  Channels are ready.
0|echo     | ✔  Listening for http events...
0|echo     | ✔  Listening for redis events...
0|echo     |
0|echo     | Server ready!

Since everything is ok, we save the setup so pm2 will be able to restart everything once invoked with the resurrect command.

nginx$ pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/nginx/.pm2/dump.pm2

Creating a systemd service for PM2

The last step in our setup is to create a systemd service to start pm2.

We created a pm2-nginx.service file in /etc/systemd/system:

Description=PM2 process manager


ExecStart=/home/nginx/node_modules/pm2/bin/pm2 resurrect
ExecReload=/home/nginx/node_modules/pm2/bin/pm2 reload all
ExecStop=/home/nginx/node_modules/pm2/bin/pm2 kill


Then we made sure to enable the service at boot time:

# systemctl daemon-reload
# systemctl enable pm2-nginx

Then we checked to make sure everything started up after a reboot:

# service pm2-nginx status
Redirecting to /bin/systemctl status pm2-nginx.service
● pm2-nginx.service - PM2 process manager
   Loaded: loaded (/etc/systemd/system/pm2-nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since lun 2018-03-26 12:15:24 CEST; 4min 46s ago
  Process: 1128 ExecStart=/home/nginx/node_modules/pm2/bin/pm2 resurrect (code=exited, status=0/SUCCESS)
 Main PID: 1246 (laravel-echo-se)
   CGroup: /system.slice/pm2-nginx.service
           ├─1236 PM2 v2.10.1: God Daemon (/home/nginx/.pm2)
           └─1246 laravel-echo-server

mar 26 12:15:18 nodejs01 systemd[1]: Starting PM2 process manager...
mar 26 12:15:23 nodejs01 pm2[1128]: [PM2] Spawning PM2 daemon with pm2_home=/home/nginx/.pm2
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] PM2 Successfully daemonized
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] Resurrecting
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] Restoring processes located in /home/nginx/.pm2/dump.pm2
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] Process /home/nginx/node_modules/laravel-echo-server/bin/server.js restored
mar 26 12:15:24 nodejs01 systemd[1]: pm2-nginx.service: Supervising process 1246 which is not our child. We\'ll most likely not notice when it exits.
mar 26 12:15:24 nodejs01 systemd[1]: Started PM2 process manager.