Real-time WebSocket communication in Laravel has never been easier thanks to Laravel Reverb — a first-party WebSocket server that integrates seamlessly with Laravel's broadcasting system. In this guide, I'll walk you through everything needed to get Reverb running behind Nginx in production, including the gotchas that cost me hours of debugging.
What We're Building
Browser (wss://yourdomain.com/app/...)
↓
Nginx :443 ← handles SSL termination
↓
Reverb :8080 ← internal only, managed by Supervisor
Nginx handles all public traffic (HTTP/HTTPS), and Reverb runs quietly on an internal port — never exposed directly to the internet.
Prerequisites
Laravel 10+ application already running on Nginx
PHP 8.2+
Node.js & npm
SSL certificate (Let's Encrypt recommended)
Supervisor installed (sudo apt-get install supervisor -y)
Step 1: Install Laravel Reverb
php artisan install:broadcasting
Answer yes to all prompts. This installs the Reverb PHP package along with Laravel Echo and pusher-js on the frontend.
Step 2: Configure Your .env
This is where most people get it wrong. Here's what matters:
For HTTPS (production with domain)
`BROADCAST_CONNECTION=reverb
QUEUE_CONNECTION=sync
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="yourdomain.com"
REVERB_PORT=443
REVERB_SCHEME=https
Internal server config (Reverb listens on this port)
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8080
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"`
For HTTP (development / IP-based server)
`BROADCAST_CONNECTION=reverb
QUEUE_CONNECTION=sync
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="your-server-ip"
REVERB_PORT=80
REVERB_SCHEME=http
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8080
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"`
⚠️ Common mistake: Don't forget QUEUE_CONNECTION=sync. Without it, broadcast events may never fire even though everything else looks correct.
Step 3: Configure Nginx
Add the WebSocket proxy blocks to your Nginx server block before location `server {
listen 443 ssl;
server_name yourdomain.com;
root /var/www/your-app/public;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# WebSocket proxy for Reverb
location /app {
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;
proxy_pass http://127.0.0.1:8080;
}
location /apps {
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;
proxy_pass http://127.0.0.1:8080;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}`
Test and reload:
sudo nginx -t
sudo systemctl reload nginx
# OR for Bitnami:
sudo /opt/bitnami/nginx/sbin/nginx -t
sudo /opt/bitnami/ctlscript.sh restart nginx
Step 4: Keep Reverb Running with Supervisor
Without Supervisor, Reverb dies the moment your SSH session closes.
sudo nano /etc/supervisor/conf.d/reverb.conf
[program:reverb]
process_name=%(program_name)s
command=php /var/www/your-app/artisan reverb:start --host=127.0.0.1 --port=8080 --no-interaction
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/reverb.log
stopwaitsecs=3600
Activate:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start reverb
sudo supervisorctl status
You should see:
reverb RUNNING pid 12345, uptime 0:00:05
Step 5: Clear Cache & Rebuild Frontend
cd /var/www/your-app
php artisan config:clear
php artisan cache:clear
npm run build
Step 6: Verify Everything Works
Check Reverb is listening internally:
sudo ss -tlnp | grep 8080
# Should show: LISTEN 0 ... 127.0.0.1:8080
Test the WebSocket connection using piehost.com/websocket-tester:
URL: wss://yourdomain.com/app/your-app-key
Click Connect → should say Connection established
Subscribe to a channel:
{
"event": "pusher:subscribe",
"data": {
"channel": "my-channel"
}
}
Fire a test broadcast from tinker:
php artisan make:event TestBroadcast
php artisan make:event TestBroadcast
Edit app/Events/TestBroadcast.php:
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class TestBroadcast implements ShouldBroadcast
{
public function broadcastOn() {
return new Channel('my-channel');
}
public function broadcastAs() {
return 'test.event';
}
public function broadcastWith() {
return ['message' => 'Hello from Reverb!'];
}
}
php artisan tinker
>>> event(new \App\Events\TestBroadcast());
You should see the message arrive in the WebSocket tester instantly. ✅
Summary
The key things that make Reverb work behind Nginx:
Nginx proxies /app and /apps paths to Reverb's internal port
REVERB_HOST = your public domain (what the browser uses)
REVERB_SERVER_PORT = internal port Reverb listens on (8080)
QUEUE_CONNECTION=sync = ensures broadcasts fire immediately
Supervisor = keeps Reverb alive permanently
Happy broadcasting! 🚀
Top comments (0)