Software:
In websockets.php (complete file) I have my local_cert
and local_pk
setup with my certificates. If I leave this option blank I cannot even connect. I also have set verify_peer
to false
, because if I don't I cannot connect either.
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'),
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'https',
'curl_options' => [
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
]
],
],
If I get rid of the curl options I get an empty Broadcast exception like described here.
bootstrap.js:
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: '7d23096ae0ab2d02d220',
wsHost: window.location.hostname,
wsPort: 6001,
wssPort: 6001,
encrypted: true,
disableStats: true,
auth: {
headers: {
'X-CSRF-TOKEN': window.App.csrfToken,
},
},
})
This is all I get from the logs after running php artisan websockets:serve
:
New connection opened for app key 7d23096ae0ab2d02d220.
Connection id 49092664.114416323 sending message {"event":"pusher:connection_established","data":"{\"socket_id\":\"49092664.114416323\",\"activity_timeout\":30}"}
What I should get is messages about listening / joining channels and sending messages etc. But all of that does not work at the moment. I have things like:
Echo.private('notifications.' + this.user.id)
.listen('UserNotificationSent', (e) => {
console.log(e)
})
Events: UserNotificationSent.php for example.
Of course internally I have everything else setup as well: channels with auth, etc. Everything worked locally on my machine on a lower Laravel version (5.4). But I recently updated to 5.8 and deployed to a server and now I struggle with this.
I also opened an issue on github.
IMPORTANT UPDATE
This is actually not due to the deployment, I have the same problem on my local setup. What is interesting is that listening to channels via Echo.channel()
works, however, .private()
is not working. On Github (link above) I came across a guy who has the exact same problem. We did not find a solution yet.
It happens because of the port 6001 is reserved in nginx on live server (explanation at the bottom). I needed to use reverse-proxy on nginx to make it work - and used port 6002 for websockets in live server.
In nginx (upon request, I added the full nginx code):
server {
#The nginx domain configurations
root /var/www/laravel/public;
index index.html index.htm index.php index.nginx-debian.html;
server_name example.com www.example.com;
#WHAT YOU NEED IS FROM HERE...
location / {
try_files $uri $uri/ /index.php?$query_string;
# "But why port 6000, 6002 and 433? Scroll at the bottom"
proxy_pass http://127.0.0.1:6001;
proxy_set_header Host $host;
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 https;
proxy_set_header X-VerifiedViaNginx yes;
proxy_read_timeout 60;
proxy_connect_timeout 60;
proxy_redirect off;
# Specific for websockets: force the use of HTTP/1.1 and set the Upgrade header
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;
}
#..UNTIL HERE - The rest are classic nginx config and certbot
#The default Laravel nginx config
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
#SSL by certbot
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl on;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
ssl_session_cache shared:SSL:30m;
ssl_protocols TLSv1.1 TLSv1.2;
# Diffie Hellmann performance improvements
ssl_ecdh_curve secp384r1;
}
Everything that connects to your domain over TLS will be proxied to a local service on port 6001, in plain text. This offloads all the TLS (and certificate management) to Nginx, keeping your websocket server configuration as clean and simple as possible.
This also makes automation via Let’s Encrypt a lot easier, as there are already implementations that will manage the certificate configuration in your Nginx and reload them when needed. - Source - Mattias Geniar
Echo setup:
let isProduction = process.env.MIX_WS_CONNECT_PRODUCTION === 'true';
Vue.prototype.Echo = new LaravelEcho({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
wssHost: window.location.hostname,
wssPort: isProduction ? 6002 : 6001,
wsHost: window.location.hostname,
wsPort: isProduction ? 6002 : 6001,
disableStats: false,
encrypted: isProduction,
enabledTransports: ['ws', 'wss'],
disabledTransports: ['sockjs', 'xhr_polling', 'xhr_streaming']
});
In websockets.php
'apps' => [
[
'id' => env('MIX_PUSHER_APP_ID'),
'name' => env('APP_NAME'),
'key' => env('MIX_PUSHER_APP_KEY'),
'secret' => env('MIX_PUSHER_APP_SECRET'),
'enable_client_messages' => false,
'enable_statistics' => true,
],
],
// I kept them null but I use LetsEncrypt for SSL certs too.
'ssl' => [
'local_cert' => null,
'local_pk' => null,
'passphrase' => null,
]
And broadcasting.php
'pusher' => [
'driver' => 'pusher',
'key' => env('MIX_PUSHER_APP_KEY'),
'secret' => env('MIX_PUSHER_APP_SECRET'),
'app_id' => env('MIX_PUSHER_APP_ID'),
'options' => [
'cluster' => env('MIX_PUSHER_APP_CLUSTER'),
'encrypted' => env('MIX_WS_CONNECT_PRODUCTION'),
'host' => '127.0.0.1',
'port' => env('MIX_WS_CONNECT_PRODUCTION') ? 6002 : 6001,
'scheme' => 'http'
],
],
This was my full cycle that made it work. Hope it helps.
Quoting from Alex Bouma's explanation:
"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).