skip to Main Content

I’m trying to implement Laravel Websockets with multiple servers.

I have an App server and a Queue Worker server running. I tried to broadcast my notifications from the Queue Worker server but I’m getting

lluminateBroadcastingBroadcastException: Pusher error: . in /home/forge/my-app/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php:128

In the App Server, in Network tab I can confirm it connects to the websocket. I’ve used alex bouma’s post and setup reverse proxy. If I broadcast within the App Server, it works:

server {
    listen 6002 ssl http2;
    listen [::]:6002 ssl http2;
    server_name example.com;
    index.php

    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;
    }
}

App Server & Queue Worker are in private networks and can connect to each other. However when I try to broadcast from the Queue Worker server, it doesn’t work.

I’m having difficulty understanding how to make it work.

  • Do I have to run php artisan websockets:serve also on the queue worker? If so, do I need to give --host={private-ip} flag?

  • In my broadcasting.php, I have added App Server’s private ip as PUSHER_ENDPOINT_HOST for Queue Worker’s env. Is this correct?

'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' => in_array(config('app.env'), ['production', 'staging']),
         'host' => env('PUSHER_ENDPOINT_HOST', '127.0.0.1'),
         'port' => 6001,
         'scheme' => 'http'
      ],
],

Update: I have made changes as @KamleshPaul suggested. Here is how all my code looks like below.

I have these configs in both app server and worker server. I’m running php artisan websockets:serve on both, and App Server successfully connects to the socket. However, the queue worker doesn’t send the notification.

Both php artisan websockets:serve shows: "Starting the WebSocket server on port 6001…"

But still it doesn’t seem to work. (By the way, I’m using Laravel Forge and allowed ports are:

  • App: 6001, 6002, 433, 22

  • Worker: 6001, 6002, 22

.env

PUSHER_APP_ID=aaa
PUSHER_APP_KEY=bbb
PUSHER_APP_SECRET=ccc
PUSHER_APP_CLUSTER=mt1
PUSHER_HOST=socket.my_domain.com
PUSHER_PORT=433
PUSHER_SCHEME=https

VITE_PUSHER_APP_KEY=${PUSHER_APP_KEY}
VITE_PUSHER_APP_CLUSTER=${PUSHER_APP_CLUSTER}
VITE_PUSHER_HOST=${PUSHER_HOST}
VITE_PUSHER_SCHEME={PUSHER_SCHEME}
VITE_PUSHER_PORT={PUSHER_PORT}

echo.js

window.Echo = new Echo({
      broadcaster: 'pusher',
      key: import.meta.env.VITE_PUSHER_APP_KEY,
      cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
      wsHost: import.meta.env.VITE_PUSHER_HOST,
      wsPort: import.meta.env.VITE_PUSHER_PORT || 443,
      forceTLS: true,
      disableStats: true,
      scheme: import.meta.env.VITE_PUSHER_SCHEME,
      enabledTransports: ["ws", "wss"],
}

broadcasting.php

'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' => env('PUSHER_HOST'),
        'port' => env('PUSHER_PORT'),
        'scheme' => env('PUSHER_SCHEME')
    ],
],

nginx of socket.my_domain.com

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/socket.my_domain.com/before/*;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name socket.my_domain.com;
    server_tokens off;
    root /home/forge/socket.my_domain.com/public;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/socket.my_domain.com/1262458/server.crt;
    ssl_certificate_key /etc/nginx/ssl/socket.my_domain.com/1262458/server.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    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/socket.my_domain.com/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/socket.my_domain.com-error.log error;

    error_page 404 /index.php;

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

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

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/socket.my_domain.com/after/*;

2

Answers


  1. Chosen as BEST ANSWER

    I finally figured it out. These settings below worked for me. Hopefully it saves you some time :)

    I decided to use reverse proxy on subdomain approach like described in the docs.

    For the subdomain's nginx, the only different thing from a default Laravel nginx config is the location / {}. Just copy the nginx config of your Laravel app, change the server_name and replace the location / {} and you are good to go.

    window.Echo = new Echo({
         broadcaster: 'pusher',
         key: import.meta.env.VITE_PUSHER_APP_KEY,
         cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
         wsHost: import.meta.env.VITE_PUSHER_HOST,
         wsPort: import.meta.env.VITE_PUSHER_PORT || 443,
         forceTLS: true,
         disableStats: true,
         scheme: import.meta.env.VITE_PUSHER_SCHEME,
         enabledTransports: ["ws", "wss"],
    });
    

    // broadcasting.php
    
    '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' => env('PUSHER_HOST'),
            'port' => env('PUSHER_PORT'),
            'scheme' => env('PUSHER_SCHEME'),
        ],
    ],
    

    // websockets.php
    
    'apps' => [
        [
            'id' => env('PUSHER_APP_ID'),
            'name' => env('APP_NAME'),
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'host' => env('PUSHER_HOST'),
    //        'capacity' => null,
            'enable_client_messages' => false,
            'enable_statistics' => false,
        ],
    ],
    

    // .env file
    
    PUSHER_APP_ID=HIDDEN_ID
    PUSHER_APP_KEY=HIDDEN_KEY
    PUSHER_APP_SECRET=HIDDEN_SECRET
    PUSHER_APP_CLUSTER=mt1
    PUSHER_HOST=socket.example.com;
    PUSHER_PORT=6001
    PUSHER_SCHEME=http
    
    VITE_PUSHER_APP_KEY=${PUSHER_APP_KEY}
    VITE_PUSHER_APP_CLUSTER=${PUSHER_APP_CLUSTER}
    VITE_PUSHER_HOST=${PUSHER_HOST}
    VITE_PUSHER_SCHEME=https
    VITE_PUSHER_PORT=433
    
    • Make sure you run php artisan websockets:serve in both instances: in my case - App & Worker server.

    • Make sure you allow port 6001 on your app server

    In my case, if I ssh into the server and write php artisan websockets:serve, it doesn't show the upcoming messages or connections like it do in local, so be aware of it.

    • In App Server, for connecting the Laravel Websockets dashboard just use empty port:

    enter image description here


    Some resources:


  2. as per laravel websockets doc

    create a subdomain socket.yourapp.tld then create a nginx config like this

    server {
      listen        443 ssl;
      listen        [::]:443 ssl;
      server_name   socket.yourapp.tld;
    
      # Start the SSL configurations
      ssl                  on;
      ssl_certificate      /etc/letsencrypt/live/socket.yourapp.tld/fullchain.pem;
      ssl_certificate_key  /etc/letsencrypt/live/socket.yourapp.tld/privkey.pem;
    
      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;
      }
    }
    

    then in broadcasting.php you need to change these

    'host' => socket.yourapp.tld,
    'port' => 433,
    'scheme' => 'https'
    

    same goes for javascript

    export default new Echo({
      broadcaster: "pusher",
      key: "key",
      wsHost: process.env.REACT_APP_WS_HOST,
      wsPort: process.env.REACT_APP_WS_PORT || 443,
      forceTLS: process.env.REACT_APP_WS_PORT === 433,
      disableStats: true,
      enabledTransports: ["ws", "wss"],
      authorizer: (channel, options) => {
        return {
          authorize: (socketId, callback) => {
            axios
              .post(
                `${process.env.REACT_APP_MARKETPLACE_URL}broadcasting/auth`,
                {
                  socket_id: socketId,
                  channel_name: channel.name,
                },
                {
                  headers: {
                    Authorization: `Bearer ${token}`,
                  },
                }
              )
              .then((response) => {
                callback(false, response.data);
              })
              .catch((error) => {
                callback(true, error);
              });
          },
        };
      },
    

    ref link https://beyondco.de/docs/laravel-websockets/basic-usage/ssl#usage-with-a-reverse-proxy-like-nginx

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search