Tools: How to Configure Laravel Reverb with Nginx in Production (2026)

Tools: How to Configure Laravel Reverb with Nginx in Production (2026)

Internal server config (Reverb listens on this port)

Redirect HTTP to HTTPS 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. Nginx handles all public traffic (HTTP/HTTPS), and Reverb runs quietly on an internal port — never exposed directly to the internet. Laravel 10+ application already running on Nginx

PHP 8.2+Node.js & npmSSL certificate (Let's Encrypt recommended)Supervisor installed (sudo apt-get install supervisor -y) Step 1: Install Laravel Reverbphp 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 .envThis is where most people get it wrong. Here's what matters:For HTTPS (production with domain) `BROADCAST_CONNECTION=reverbQUEUE_CONNECTION=sync REVERB_APP_ID=your-app-idREVERB_APP_KEY=your-app-keyREVERB_APP_SECRET=your-app-secretREVERB_HOST="yourdomain.com"REVERB_PORT=443REVERB_SCHEME=https REVERB_SERVER_HOST=127.0.0.1REVERB_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=reverbQUEUE_CONNECTION=sync REVERB_APP_ID=your-app-idREVERB_APP_KEY=your-app-keyREVERB_APP_SECRET=your-app-secretREVERB_HOST="your-server-ip"REVERB_PORT=80REVERB_SCHEME=http REVERB_SERVER_HOST=127.0.0.1REVERB_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 NginxAdd 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; server { listen 80; server_name yourdomain.com; return 301 https://$host$request_uri;}` Step 4: Keep Reverb Running with SupervisorWithout Supervisor, Reverb dies the moment your SSH session closes.sudo nano /etc/supervisor/conf.d/reverb.conf [program:reverb]process_name=%(program_name)scommand=php /var/www/your-app/artisan reverb:start --host=127.0.0.1 --port=8080 --no-interactionautostart=trueautorestart=trueuser=www-dataredirect_stderr=truestdout_logfile=/var/www/your-app/storage/logs/reverb.logstopwaitsecs=3600 Step 5: Clear Cache & Rebuild Frontend Step 6: Verify Everything WorksCheck Reverb is listening internally: Test the WebSocket connection using piehost.com/websocket-tester: URL: wss://yourdomain.com/app/your-app-keyClick Connect → should say Connection established Subscribe to a channel: Fire a test broadcast from tinker: php artisan make:event TestBroadcast Edit app/Events/TestBroadcast.php: You should see the message arrive in the WebSocket tester instantly. ✅ SummaryThe key things that make Reverb work behind Nginx: Nginx proxies /app and /apps paths to Reverb's internal portREVERB_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! 🚀 Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

Browser (wss://yourdomain.com/app/...) ↓ Nginx :443 ← handles SSL termination ↓ Reverb :8080 ← internal only, managed by Supervisor Browser (wss://yourdomain.com/app/...) ↓ Nginx :443 ← handles SSL termination ↓ Reverb :8080 ← internal only, managed by Supervisor Browser (wss://yourdomain.com/app/...) ↓ Nginx :443 ← handles SSL termination ↓ Reverb :8080 ← internal only, managed by Supervisor 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; } 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; } 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; } sudo nginx -t sudo systemctl reload nginx # OR for Bitnami: sudo /opt/bitnami/nginx/sbin/nginx -t sudo /opt/bitnami/ctlscript.sh restart nginx sudo nginx -t sudo systemctl reload nginx # OR for Bitnami: sudo /opt/bitnami/nginx/sbin/nginx -t sudo /opt/bitnami/ctlscript.sh restart nginx sudo nginx -t sudo systemctl reload nginx # OR for Bitnami: sudo /opt/bitnami/nginx/sbin/nginx -t sudo /opt/bitnami/ctlscript.sh restart nginx sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start reverb sudo supervisorctl status sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start reverb sudo supervisorctl status sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start reverb sudo supervisorctl status reverb RUNNING pid 12345, uptime 0:00:05 reverb RUNNING pid 12345, uptime 0:00:05 reverb RUNNING pid 12345, uptime 0:00:05 cd /var/www/your-app php artisan config:clear php artisan cache:clear npm run build cd /var/www/your-app php artisan config:clear php artisan cache:clear npm run build cd /var/www/your-app php artisan config:clear php artisan cache:clear npm run build sudo ss -tlnp | grep 8080 # Should show: LISTEN 0 ... 127.0.0.1:8080 sudo ss -tlnp | grep 8080 # Should show: LISTEN 0 ... 127.0.0.1:8080 sudo ss -tlnp | grep 8080 # Should show: LISTEN 0 ... 127.0.0.1:8080 { "event": "pusher:subscribe", "data": { "channel": "my-channel" } } { "event": "pusher:subscribe", "data": { "channel": "my-channel" } } { "event": "pusher:subscribe", "data": { "channel": "my-channel" } } php artisan make:event TestBroadcast php artisan make:event TestBroadcast php artisan make:event TestBroadcast <?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 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 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()); php artisan tinker >>> event(new \App\Events\TestBroadcast()); php artisan tinker >>> event(new \App\Events\TestBroadcast());