Le débat « Reverb dans Laravel » vs « serveur WebSocket standalone » a l’air trivial. Sur un projet qui démarre, tu veux juste du temps réel qui marche. En prod, c’est un autre sport : l’endroit où tu fais tourner tes WebSockets décide de ton blast radius, de ton rollout, de ton debug et du genre d’incident qui va te réveiller.
Je ne vais pas te vendre une religion. Je vais te décrire ce qui se passe quand ça chauffe, ce qui casse en premier, et comment je choisis quand je n’ai pas le droit de me tromper.
Reverb « dans Laravel » : tu gagnes de la simplicité, tu perds de l’isolation
Le gros avantage de Reverb, c’est l’intégration. Tu restes dans l’écosystème Laravel, tu parles le langage « broadcasting », et tu déploies un truc que ton équipe sait déjà opérer. La DX est réelle : config, auth, événements, channels privés, tout est cohérent avec le reste.
Le piège, c’est que tu colles des connexions longues et bavardes dans le même périmètre que ton app. Et une connexion WebSocket, ce n’est pas une requête HTTP qui passe et s’en va. Ça vit longtemps, ça occupe des file descriptors, ça mange de la RAM par connexion, ça peut provoquer des pics CPU bizarres quand tu pushes fort, et ça change complètement la façon dont tes déploiements doivent être pensés.
Quand Reverb est co-déployé « avec Laravel », beaucoup d’équipes finissent par le faire tourner sur les mêmes machines, le même cluster, parfois le même type de process manager. Ça marche très bien… jusqu’au moment où ton scaling HTTP (stateless, bursty) n’a plus rien à voir avec ton scaling WS (connexions persistantes, plus prévisible, plus sensible aux coupures). Si tu scales les deux ensemble, tu vas forcément faire des compromis.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
Serveur WebSocket standalone : moins de couplage, plus de responsabilité
Un serveur WebSocket dédié (binaire Go, Node, autre) te donne un truc très simple mentalement : « ceci ne fait que du WS ». Tu gagnes en isolation de ressources et en lisibilité d’incident. Quand ça part en vrille, tu sais qui tuer, quoi scaler, et comment contenir le problème sans flinguer le reste.
Tu peux aussi choisir une techno plus adaptée à beaucoup de connexions simultanées, avec une empreinte mémoire maîtrisée et un event loop qui encaisse. Et surtout, tu peux faire évoluer ton infra en deux axes indépendants : ton API Laravel d’un côté, ton temps réel de l’autre. En pratique, ça évite des situations absurdes où tu ajoutes des pods Laravel juste pour tenir 20 000 sockets ouvertes.
Le coût, c’est l’ops. Tu rajoutes un service de plus, un pipeline de déploiement, une surface de config réseau, des règles de sécurité, des dashboards, des alertes, du capacity planning. Et tu dois être carré sur la compatibilité protocolaire si tu veux rester « drop-in » avec tes libs et ton code existant.
Le premier incident n’est pas « ça marche pas » : c’est « ça marche… puis ça dégrade tout »
Le scénario classique, ce n’est pas un crash net. C’est une dégradation progressive. Un pic de connexions, un backlog de messages, une latence qui monte, des clients qui reconnectent en boucle, et là tu as un effet boule de neige. Le temps réel a un talent particulier pour transformer un petit problème en gros bordel parce que la reconnexion automatique des clients amplifie la charge.
Le point que beaucoup sous-estiment : les connexions longues changent la façon dont tes load balancers et reverse proxies doivent être réglés. Si ton ALB, ton Nginx ou ton ingress a des timeouts trop agressifs, tu vas avoir des déconnexions régulières. Et une déconnexion régulière, ce n’est pas « juste une déconnexion ». C’est souvent une tempête de reconnect, donc un spike CPU, donc des latences, donc encore plus de reconnect. L’incident se nourrit tout seul.
Ensuite, il y a le côté « ressources ». La RAM n’explose pas parce que tu fais des grosses allocations, elle grimpe parce que tu as beaucoup de connexions vivantes, chacune avec son état. Les file descriptors montent vite. Les limites systèmes que personne ne regarde (ulimit, ephemeral ports côté client, etc.) deviennent soudain très concrètes.
Compatibilité « Pusher drop-in » : utile, mais ce n’est pas un contrat magique
Beaucoup de solutions, Reverb compris selon les setups, se positionnent comme compatibles avec le protocole Pusher. C’est précieux parce que côté Laravel, tu gardes le modèle « broadcaster » et souvent tes clients JS existants. Mais « drop-in » veut dire « suffisamment compatible pour les cas courants », pas « strictement identique en tout point ».
En prod, les surprises viennent rarement de « l’event n’arrive pas ». Elles viennent plutôt des détails périphériques : l’auth sur les channels privés, les subtilités de présence, le comportement sur les reconnects, la gestion des messages trop gros, les limites de rate, la façon dont sont gérés les pings/pongs, et ce que ton serveur fait quand il est sous pression. Deux implémentations peuvent être « compatibles » et pourtant te donner des comportements différents au moment où tu en as le moins envie.
Si tu comptes sur la compat Pusher pour pouvoir switcher rapidement (Reverb <-> standalone <-> managed), mon conseil terrain est simple : teste le switch avant l’incident. Pas en staging « gentil ». Avec des scripts qui ouvrent beaucoup de connexions, des reconnects, et des messages à un rythme réaliste. Sinon, le jour où tu veux basculer, tu découvres que tu as des dépendances implicites dans ton frontend ou dans ton infra.
Observabilité : sans métriques WS, tu vas diagnostiquer à l’aveugle
Le temps réel sans métriques, c’est de l’auto-sabotage. Le minimum vital, c’est de savoir combien tu as de connexions actives, ton taux de connexions entrantes, ton taux de déconnexions, et la répartition des codes de fermeture. Ça te dit immédiatement si tu as un problème réseau, un timeout de proxy, une saturation serveur, ou un bug client qui reconnecte n’importe comment.
Ensuite, tu veux une idée claire de ton débit message (messages/seconde), de la latence de publication, et d’un indicateur de backpressure. Si tu pousses plus vite que tu n’envoies, il te faut un signal. Pas forcément « un backlog » au sens queue persistée, mais au moins un symptôme mesurable (buffers qui grossissent, event loop qui ralentit, latence qui se dégrade). C’est là que l’architecture change beaucoup la vie : quand ton WS est dans le même service que Laravel, tu as tendance à mélanger les symptômes et à accuser le mauvais composant.
Dernier point très pratique : la corrélation de logs. Sur une archi co-déployée, tu as parfois un confort énorme pour suivre une requête HTTP (auth, action) puis l’émission d’un event puis sa livraison WS, avec un même request id. Sur un serveur standalone, c’est plus facile de séparer, mais tu dois être plus rigoureux sur la propagation d’identifiants et sur la centralisation des logs, sinon tu perds du temps.
Déploiement sans drama : le sujet, c’est le « draining » des connexions
Le grand classique en prod : tu déploies, et tu coupes tout le monde. Pas par méchanceté. Juste parce que ton orchestrateur tue le process, et qu’un WebSocket vivant n’aime pas ça. Les clients reconnectent, tu as un trou de quelques secondes, parfois plus, et ça se voit. Sur une app interne, ok. Sur un SaaS avec du temps réel au cœur du produit, ça devient vite un incident « visible client ».
Ce que tu veux, c’est un déploiement qui respecte les connexions. Ça passe par de la readiness qui arrête d’accepter de nouvelles connexions avant de shutdown, un délai de terminaison assez long, et un vrai comportement de « je finis ce que j’ai à finir ». Si tu co-déploies Reverb avec Laravel et que tu fais du rolling update agressif, tu vas te tirer une balle dans le pied.
Côté proxy, il faut aussi assumer que le WS n’est pas du HTTP normal. Exemple typique avec Nginx, où les timeouts trop courts font « tomber » des connexions parfaitement saines :
location /app {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# Timeouts adaptés aux connexions longues
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
Ça ne remplace pas une vraie stratégie de draining, mais ça évite le syndrome « toutes les 60 secondes, tout le monde reconnecte » parce que ton infra coupe trop tôt.
Quand Reverb co-déployé est un bon choix (et quand je l’éviterais)
Je trouve Reverb très pertinent quand ton temps réel est important mais pas encore critique, et que ton objectif principal est de livrer vite sans multiplier les moving parts. Typiquement : une app qui a quelques centaines ou milliers de connexions, un trafic assez stable, et une équipe qui veut rester dans Laravel sans ouvrir un chantier infra. Dans ce contexte, le coût mental d’un service en plus est souvent plus cher que le gain.
En revanche, je commence à pousser vers un serveur WS dédié dès que le temps réel devient un pilier produit, ou dès que les patterns de charge divergent. Exemple concret : un SaaS multi-tenant avec des pics à heures fixes, ou des « rooms » qui explosent lors d’événements, ou une app avec des clients mobiles qui reconnectent beaucoup (réseaux instables, background/foreground). Là, je veux isoler. Pas parce que Laravel serait « nul », mais parce que je veux pouvoir scaler, redémarrer, limiter, observer et déployer le WS sans embarquer l’API avec.
Le deuxième signal d’alarme, c’est quand tu commences à bricoler des contorsions pour compenser le couplage. Sticky sessions imposées « parce que sinon ça bug », rollouts ralentis à cause des connexions, workers PHP qui se mettent à souffrir parce que le nœud est saturé en RAM par les sockets, ou au contraire un besoin de surprovisionner l’API juste pour tenir les connexions. À ce stade, tu as déjà la réponse : tu as dépassé le modèle co-déployé.
Ce que je mettrais en place tout de suite, quel que soit ton choix
Peu importe Reverb ou standalone, je veux deux choses dès le départ : des limites claires et une procédure de rollback. Les limites, c’est la capacité max par instance (connexions, débit), et ce qui se passe quand tu dépasses. Le rollback, c’est la capacité de revenir à une version N-1 sans faire reconnecter tout le monde dix fois. Ça peut vouloir dire un déploiement blue/green, ou juste un rollout qui respecte le draining.
Et je veux une config « boring » côté Laravel, où je sais exactement quel driver de broadcast est utilisé, et où je peux basculer un environnement sans toucher au code applicatif. Un exemple minimal (l’idée, pas la recette universelle) :
// config/broadcasting.php (extrait)
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'host' => env('PUSHER_HOST', '127.0.0.1'),
'port' => env('PUSHER_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'http'),
'useTLS' => env('PUSHER_SCHEME', 'http') === 'https',
],
],
],
Le but n’est pas de « faire du Pusher ». Le but, c’est de conserver un point de bascule simple : aujourd’hui Reverb, demain un serveur dédié, et si tu es en feu, un provider managed temporaire. Ce switch-là, s’il est douloureux, tu vas le payer au pire moment.
Conclusion : la vraie question, c’est ton blast radius
Le choix n’est pas « Laravel vs pas Laravel ». Le choix, c’est où tu veux mettre le risque. Reverb dans la même zone que ton app, c’est confortable et rapide, mais tu acceptes qu’un problème WS puisse dégrader ton API (et l’inverse). Un serveur WS standalone, c’est plus propre en prod, mais tu payes en ops et en discipline.
Si tu n’as pas encore vécu un incident temps réel, tu vas naturellement sous-estimer le sujet. C’est normal. Mon conseil, c’est de décider en pensant au jour où tu seras on-call, pas au jour où tu pushes ta première notif. Le temps réel, c’est fun. Jusqu’à la première tempête de reconnect.