Skip to content

Instantly share code, notes, and snippets.

@adamhut
Last active June 16, 2020 03:13
Show Gist options
  • Save adamhut/d147a99c3155224fc20a960bac9af1b5 to your computer and use it in GitHub Desktop.
Save adamhut/d147a99c3155224fc20a960bac9af1b5 to your computer and use it in GitHub Desktop.
Laravel Websockets on Forge Note Using SSL

Step by Step to create a stand along Laravel Websocket Server

I can make the laravel-websocket works local and on forge without SSL. I want to make it work when it with SSL on Forge as well. I read some articles and the officeal documnet to make it work. The follwoing is my sernario.This take me a long to make it work. Thank Alex Bouma's article finaly make my case work.

Sernario

  • One stand alone Weksocket Server
  • One Backend Server
  • One Client System Server
  • Website Servers

All the examples I saw online are all make all the service stay on one sever. So it confunse me. and it turns out with some litte tweek I can make it work.

Steps

Step1 - setup a websocket Server

  • composer create-project laravel/laravel laravel-websocket-server Create a Laravel Project
  • composer require beyondcode/laravel-websockets Install the Laravel Websocket packeage and following the documents to publish the necessary file and setting.
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
  • Update the .env file. You can whatever you want if you are not going back to use Pusher . It been used in websockets.php and broadcasting.php
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=[YOUR APP ID]
PUSHER_APP_KEY=[YOU APP KEY]
PUSHER_APP_SECRET=[YOUR APP SECRET]
  • Edit the websocket.php for the stand alone websocket sever. broadcasting.php won't matters. edit the following section to limit the connection origins and since we are using NGINX to help us to do the SSL heavy work for us. We can leave the SSL section unchange
    /*
     * This array contains the hosts of which you want to allow incoming requests.
     * Leave this empty if you want to accept requests from all hosts.
     */
   'allowed_origins' => [
        'test-website.com'
   ],
   'ssl' => [
        /*
         * Path to local certificate file on filesystem. It must be a PEM encoded file which
         * contains your certificate and private key. It can optionally contain the
         * certificate chain of issuers. The private key also may be contained
         * in a separate file specified by local_pk.
         */
        'local_cert' => null,//'/etc/nginx/ssl/ws.thebcr.com/527710/server.crt',

        /*
         * Path to local private key file on filesystem in case of separate files for
         * certificate (local_cert) and private key.
         */
        'local_pk' =>  null,//'/etc/nginx/ssl/ws.thebcr.com/527710/server.key',

        /*
         * Passphrase for your local_cert file.
         */
        'passphrase' => null,
        // 'verify_peer' =>false,
    ],

Step2 On the forge

I am going to use separate domain. The websocket will stay on it own domain and own server. After deployed your server to Forge then added a Let's Encrypts cettificate. All can done on Forge interface and you good to go(Forge is awesome)

Since I am using normal 443 port to moniter the traffic provide by the package. You just to need to copy and paste the follow NGINX config after the to make it work. replace laravel-websockets.example.com to your own domain name. and make sure the ssl_certificate ssl_certificate_key are matched with the port 443 part section The content will be different case by case /etc/nginx/ssl/laravel-websockets.example.com/473558/server.crt /etc/nginx/ssl/laravel-websockets.example.com/473558/server.key please update it according to your case.

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/laravel-websockets.example.com/before/*;

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name laravel-websockets.example.com;
    root /home/forge/laravel-websockets.example.com/public;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/laravel-websockets.example.com/473558/server.crt;
    ssl_certificate_key /etc/nginx/ssl/laravel-websockets.example.com/473558/server.key;

    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    # FORGE CONFIG (DO NOT REMOVE!)
    include forge-conf/laravel-websockets-example.alexbouma.me/server/*;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/laravel-websockets-example.alexbouma.me-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

server {
    listen 6002 ssl;
    listen [::]:6002 ssl;
    server_name laravel-websockets.example.com;
    root /home/forge/laravel-websockets.example.com/public;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/laravel-websockets-example.alexbouma.me/473558/server.crt;
    ssl_certificate_key /etc/nginx/ssl/laravel-websockets-example.alexbouma.me/473558/server.key;

    ssl_protocols TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    # FORGE CONFIG (DO NOT REMOVE!)
    include forge-conf/laravel-websockets-example.alexbouma.me/server/*;

    location / {
        proxy_pass             http://127.0.0.1:6001;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;
        
        # Allow the use of websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/laravel-websockets-example.alexbouma.me-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Plase be note that we are using port 6002 you can pick what everyever you want, just make sure you open that port on you forge server. after that make sure your server reload or runing php artisan websockets:serve

Extra

On December 2019, I migrated my web socket server to another region, and it didn't work. I can not find out why at beginning. By the end I figure it out. I was using Clouldflare to do proxy , so it can not work at begining. You just simply switch the DNS setting from Proxy to DNS ONLY should resolve the issue.

The following is the orignal Text from Alex Bouma article talk about port. I think it is important

But why port 6000, 6002 and 433, what a mess!

I hear ya! Let me explain a bit, it will hopefully all make sense afterwards.

Here is the thing, opening an port on your server can only be done by only one application at a time (technically that is not true, but let's keep it simple here) . So if we would let NGINX listen on port 6001 we cannot start our websockets server also on port 6001 since it will conflict with NGINX and the other way around, therefore we let NGINX listen on port 6002 and let it proxy (NGINX is a reverse proxy after all) all that traffic to port 6001 (the websockets server) over plain http. Stripping away the SSL so the websockets server has no need to know how to handle SSL.

So NGINX will handle all the SSL magic and forward the traffic in plain http to port 6001 on your server where the websockets server is listening for requests.

The reason we are not configuring any SSL in the websockets.php config and we define the scheme in our broadcasting.php as http and use port 6001 is to bypass NGINX and directly communicate with the websockets server locally without needing SSL which faster (and easier to configure and maintain).

A note about ports

Websockets are not allowed to connect on any port you can think of... as Stack Overflow found out a lot of them are blocked (by browsers) and while testing I used port 6000, which also seems blocked, that why I used port 6002 which works just fine.

I had a hard time figuring out what was going on since no visible errors are thrown when you use a port that is blocked by the browser :( But looking at the pusher events I saw the error which mumbled about the port I choose not being allowed for a websockets connection.

You can see the events of the Pusher client by running window.Echo.connector.pusher.connection.timeline.events in the developer console and inspecting the entries.

On the Backend Server.

  • install the require package to make it work composer require pusher/pusher-php-server "~3.0"
  • BROADCAST_DRIVER=pusher update the .env file
'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => true,
        'host' => 'laravel-websockets.example.com',
        'port' => 6002,
        'scheme' => 'https'
    ],
],
  • that is all you need to do here

On you Client Side

  • install Laravel Echo and pusher npm install --save laravel-echo pusher-js
  • update app.js and then npm run production
window.Echo = new Echo({
     broadcaster: "pusher",
     key: "b12fcbcf3175a9c80082",
     cluster: "mt1",
     encrypted: true,
     wsHost: "laravel-websockets.example.com",
     wsPort: 6002,
     wssPort: 6002,
     disableStats: true,
     enabledTransports: ['ws', 'wss'],
 });

Credits

@emotality
Copy link

Dude you saved my life 😒 Thanks! After 12 hours of struggle..

6002 -> 6001 proxy <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment