Pusher is excellent but costs add up. Laravel Echo Server provides self-hosted WebSocket infrastructure compatible with Laravel broadcasting. At ZIRA Software, Echo Server powers our real-time features, saving thousands monthly while maintaining full control.
Why Echo Server?
Pusher problems:
- Monthly costs ($49-$499+)
- Connection limits
- Message limits
- External dependency
- Privacy concerns
Echo Server benefits:
- Free and open source
- Unlimited connections
- Unlimited messages
- Full control
- On-premises hosting
- Compatible with Pusher API
Installation
Install globally:
npm install -g laravel-echo-server
Initialize:
laravel-echo-server init
Configuration prompts:
- Development mode? Yes
- Port? 6001
- Database? Redis
- Auth host? http://localhost
Generates laravel-echo-server.json:
{
"authHost": "http://localhost",
"authEndpoint": "/broadcasting/auth",
"clients": [
{
"appId": "your-app-id",
"key": "your-app-key"
}
],
"database": "redis",
"databaseConfig": {
"redis": {
"port": "6379",
"host": "127.0.0.1"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"sslCertPath": "",
"sslKeyPath": ""
}
Laravel Configuration
.env:
BROADCAST_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
config/broadcasting.php:
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
],
Frontend Configuration
resources/js/bootstrap.js:
import Echo from 'laravel-echo';
import io from 'socket.io-client';
window.io = io;
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001',
auth: {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('auth_token'),
},
},
});
Install socket.io-client:
npm install --save socket.io-client
Start Echo Server
laravel-echo-server start
Output:
L A R A V E L E C H O S E R V E R
version 1.6.2
⚠ Starting server in DEV mode...
✔ Running at localhost on port 6001
✔ Channels are ready.
✔ Listening for http events...
✔ Listening for redis events...
Server ready!
Production Deployment
Install as service:
# Install PM2
npm install -g pm2
# Start Echo Server with PM2
pm2 start laravel-echo-server -- start
# Save process list
pm2 save
# Setup startup script
pm2 startup
Systemd service:
# /etc/systemd/system/laravel-echo-server.service
[Unit]
Description=Laravel Echo Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/bin/laravel-echo-server start
Restart=on-failure
[Install]
WantedBy=multi-user.target
Enable service:
sudo systemctl enable laravel-echo-server
sudo systemctl start laravel-echo-server
SSL Configuration
Generate certificates:
sudo certbot certonly --standalone -d echo.yourdomain.com
laravel-echo-server.json:
{
"protocol": "https",
"sslCertPath": "/etc/letsencrypt/live/echo.yourdomain.com/fullchain.pem",
"sslKeyPath": "/etc/letsencrypt/live/echo.yourdomain.com/privkey.pem",
"port": "6001"
}
Frontend:
window.Echo = new Echo({
broadcaster: 'socket.io',
host: 'echo.yourdomain.com:6001',
encrypted: true,
});
Nginx Reverse Proxy
Better: Proxy through Nginx:
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# WebSocket endpoint
location /socket.io {
proxy_pass http://127.0.0.1:6001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 $scheme;
}
}
Frontend:
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname,
encrypted: true,
path: '/socket.io',
});
Redis Configuration
config/database.php:
'redis' => [
'client' => 'predis',
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'options' => [
'prefix' => env('REDIS_PREFIX', 'laravel_'),
],
],
Authentication
API token:
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname,
auth: {
headers: {
Authorization: 'Bearer ' + document.querySelector('meta[name="api-token"]').content,
},
},
});
Session:
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname,
withCredentials: true,
});
Broadcasting Events
Same as Pusher:
broadcast(new OrderShipped($order));
Works identically:
Echo.channel('orders')
.listen('OrderShipped', (e) => {
console.log(e.order);
});
Presence Channels
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
return ['id' => $user->id, 'name' => $user->name];
});
Echo.join('chat.1')
.here((users) => {
console.log('Users:', users);
})
.joining((user) => {
console.log(user.name + ' joined');
})
.leaving((user) => {
console.log(user.name + ' left');
});
Private Channels
Broadcast::channel('user.{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
Echo.private('user.1')
.listen('NotificationSent', (e) => {
console.log(e.notification);
});
Performance Tuning
laravel-echo-server.json:
{
"devMode": false,
"socketio": {
"transports": ["websocket", "polling"],
"pingInterval": 25000,
"pingTimeout": 60000,
"cookie": false
}
}
Redis optimization:
# redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru
Monitoring
Stats endpoint:
http://localhost:6001/apps/your-app-id/status
PM2 monitoring:
pm2 monit
pm2 logs laravel-echo-server
Custom monitoring:
// Add to laravel-echo-server
io.on('connection', (socket) => {
console.log('Connection:', socket.id);
socket.on('disconnect', () => {
console.log('Disconnect:', socket.id);
});
});
Scaling
Multiple Echo Server instances:
# Server 1
PORT=6001 laravel-echo-server start
# Server 2
PORT=6002 laravel-echo-server start
Load balancer:
upstream echo {
server 127.0.0.1:6001;
server 127.0.0.1:6002;
}
server {
location /socket.io {
proxy_pass http://echo;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Redis pub/sub ensures messages reach all instances.
Debugging
Enable debug mode:
{
"devMode": true
}
Client-side debugging:
window.Echo.connector.socket.on('connect', () => {
console.log('Connected');
});
window.Echo.connector.socket.on('error', (error) => {
console.error('Error:', error);
});
Testing
Test connection:
curl http://localhost:6001/apps/your-app-id/status
Test broadcasting:
Route::get('/test-broadcast', function () {
broadcast(new \App\Events\TestEvent('Hello World'));
return 'Event sent!';
});
Cost Comparison
Pusher:
- 100 concurrent: $49/mo
- 500 concurrent: $99/mo
- 1000 concurrent: $199/mo
Echo Server:
- Unlimited concurrent: $0
- VPS cost: $5-20/mo
- Total savings: Thousands annually
Migration from Pusher
- Install Echo Server
- Update frontend config (broadcaster: 'socket.io')
- Change BROADCAST_DRIVER to redis
- Test thoroughly
- Switch DNS
Zero code changes to events or channels!
Best Practices
- Use PM2 - Process management
- Enable SSL - Security
- Monitor connections - Capacity planning
- Scale horizontally - Multiple instances
- Use Redis persistence - Reliability
- Set up logging - Debugging
- Regular updates - Security patches
Troubleshooting
Connection refused:
- Check firewall
- Verify Echo Server running
- Check port availability
Authentication fails:
- Verify authHost correct
- Check CORS headers
- Validate tokens
Messages not received:
- Check Redis connection
- Verify channel names
- Check broadcast driver
Conclusion
Laravel Echo Server provides self-hosted WebSocket infrastructure that's Pusher-compatible. From real-time notifications to chat applications, Echo Server delivers without monthly fees. At ZIRA Software, Echo Server powers production applications handling thousands of concurrent connections reliably.
Deploy Echo Server, configure Laravel broadcasting, and enjoy cost-effective real-time features.
Need help deploying self-hosted WebSocket infrastructure? Contact ZIRA Software to discuss Echo Server deployment, scaling strategies, and real-time architecture.