Teknoloji

WebSocket Hosting: Laravel Reverb ve Socket.IO

WebSocket Hosting: Laravel Reverb ve Socket.IO

Gerçek zamanlı bildirimler, canlı destek pencereleri, anlık skor veya fiyat panelleri, çok oyunculu oyunlar… Bunların ortak noktası, verilerin istemcilere düşük gecikmeyle ulaştırılması ihtiyacıdır. Kalıcı ve çift yönlü bir bağlantı bunu sağlamanın yollarından biridir. Bu da bizi WebSocket protokolüne, Node.js tabanlı sunuculara, Laravel Echo ve Socket.io gibi kütüphanelere götürüyor.

WebSocket, klasik PHP tabanlı request–response işleyişinden farklıdır: Tek bir isteği yanıtlamak yerine, açık bağlantı üzerinden mesaj alışverişini sürdürür; bağlantılar dakikalarca, hatta saatlerce açık kalabilir. Bu da hosting tarafında farklı kaynak planlaması, farklı güvenlik kuralları ve farklı mimari kararları gerektiriyor.

Bu yazıda, özellikle Node.js, Laravel Echo ve Socket.io kullanan WebSocket uygulamalarını paylaşımlı hosting ve VPS seçenekleri açısından karşılaştıracağız; harici servislerin veya dedicated altyapının hangi ihtiyaçlarda değerlendirilebileceğini de ele alacağız.

Örnekler Laravel 13 / PHP 8.3+, Reverb 1.12 ve Echo 2.6 ile bağımsız Node.js 22 / Socket.IO 4 akışlarını kapsar. Laravel uygulaması için çalışan oturum açma sistemi, veritabanı, Composer ve Vite derleme ortamı ön koşuldur. Kurulum komutları uygulamanın kendi dizininde çalıştırılır; mevcut projenin bağımlılıkları ve migration değişiklikleri önce staging ortamında incelenir.

Eğer kafanızda şu sorular varsa, yazının sonuna geldiğinizde çok daha net bir yol haritanız olacak:

  • “Paylaşımlı hostingte WebSocket çalıştırabilir miyim, yoksa doğrudan VPS’e mi geçmeliyim?”
  • “Laravel Echo için kendi WebSocket sunucumu mu kurmalıyım, Pusher/benzeri servis mi kullanmalıyım?”
  • “Node.js + Socket.io sunucusu için kaç vCPU, kaç GB RAM ayırmalıyım?”
  • “Nginx reverse proxy, Cloudflare, SSL ve WebSocket bir araya geldiğinde mimariyi nasıl kurmalıyım?”

WebSocket ve Gerçek Zamanlı İletişim: HTTP’den Farkı Ne?

Klasik HTTP request–response modelinde tarayıcı istek gönderir, sunucu yanıtlar; alttaki bağlantı sonraki istekler için açık tutulabilir. WebSocket’te ise:

  • İstemci, HTTP/1.1 üzerinden kurulan bağlantıda bir upgrade isteği gönderir (Handshake).
  • Sunucu kabul ederse bağlantı WebSocket’e yükseltilir.
  • Bu bağlantı, taraflardan biri kapatana veya ağ kopana kadar açık kalabilir.
  • Sunucu ve istemci bu tek bağlantı üzerinden iki yönlü mesaj alışverişi yapmaya devam eder.

Socket.IO, ham WebSocket değildir: bağlantı, oda ve olay mesajları için kendi protokolünü kullanır; sunucu ile socket.io-client eşleşir. Laravel Echo ise tarayıcı istemcisidir. Aşağıdaki Laravel örneğinde Echo, Reverb sunucusunun Pusher protokolüne pusher-js üzerinden bağlanır. Ayrı Socket.IO örneği bu akışa kendiliğinden bağlanmaz; Redis adresini paylaşmak da bir protokol köprüsü oluşturmaz.

Eski laravel-echo-server deposu arşivlenmiştir ve 1.6.3 paketi Socket.IO 2.x bağımlılığı taşır. BeyondCode Laravel WebSockets de artık bakım almadığını belirtip Reverb önerir. Bu araçları Laravel 13 için yeni kurulum yolu olarak kullanmayın; mevcut eski yığın için sürüm ve geçiş uyumluluğunu ayrıca inceleyin.

Ancak kalıcı bağlantıların şu sonuçları var:

  • Her açık bağlantı uygulamada durum, bellek ve dosya tanıtıcısı; işletim sisteminde socket buffer gibi kaynaklar tutar.
  • Bağlantı sayısı yanında mesaj boyutu, sıklığı ve bir mesajın kaç aboneye gönderildiği kaynak ihtiyacını belirler.
  • Paylaşımlı hostingte uzun ömürlü süreç, proxy ve bağlantı limitleri sağlayıcıya ve pakete göre doğrulanmalıdır.

PHP uygulaması ile gerçek zamanlı bağlantı katmanı aynı sunucuda olmak zorunda değildir. Kararı süreç izni, ölçülmüş kapasite, operasyon sorumluluğu ve toplam maliyet üzerinden verin.

Paylaşımlı Hosting Üzerinde WebSocket, Node.js ve Laravel Echo Mümkün mü?

Paylaşımlı hostingte sistem yönetimi sağlayıcıdadır. PHP desteğinin yanında Node.js veya başka uzun süreli süreç desteği sunulabilir; panelin adı tek başına yeterli bilgi değildir. Şu sınırları kontrol edin:

  • Kaynaklar (CPU, RAM, IO), sağlayıcının kullandığı altyapıya göre CloudLinux veya benzeri mekanizmalarla sınırlandırılabilir.
  • Sürekli çalışan Node.js süreçlerinin desteklenip desteklenmediği ve hangi koşullarda sonlandırıldığı plana göre doğrulanmalıdır.
  • Özel port açma, firewall kuralı değiştirme veya kernel ayarı yapma yetkisi genellikle verilmez.

cPanel veya başka bir panelde Node.js uygulaması oluşturabilmek, WebSocket bağlantısının proxy üzerinden kesintisiz iletileceğini garanti etmez. Sağlayıcıdan süreç yeniden başlatma politikasını, WebSocket upgrade desteğini, timeout ve kaynak limitlerini doğrulayın. Uygunluk değerlendirmesinde:

  • MVP ve iç araçlarda dahi oturum açma, bağlantı kopması ve beklenen mesaj trafiğini test edin.
  • Üretim için yalnız kullanıcı sayısını değil, paket koşullarını ve temsilî yük altında gecikme/hata oranını değerlendirin.

Paylaşımlı Hostingte Laravel Echo Kullanımı

Laravel projelerinde, gerçek zamanlı bildirim için iki temel model var:

  1. Yönetilen yayın servisi: Laravel desteklenen HTTP API’siyle yayınlar, tarayıcı servisle uyumlu istemciyi kullanır.
  2. Kendi sunucunuz: Laravel Reverb ile yayın, özel kanal yetkilendirmesi ve Echo istemcisi aynı desteklenen protokolü kullanır. Reverb PHP sürecidir; Node.js bağlantı sunucusu gerektirmez.

İkinci seçenek için paketin aşağıdaki süreç ve bağlantı gereksinimlerini karşılaması gerekir:

  • Reverb gibi seçilen WebSocket sunucusu sürekli çalışmalıdır; bağımsız Socket.IO seçerseniz bu bir Node.js sürecidir.
  • ShouldBroadcast olayları kuyrukta işlendiği için queue worker gerekir. Harici yayın servisi seçmek bu ihtiyacı kaldırmaz.
  • Redis zorunlu değildir; seçilen queue/cache veya yatay ölçeklendirme tasarımı kullanıyorsa desteklenen, erişilebilir bir Redis servisi gerekir.

O yüzden, paylaşımlı hostingte Laravel kullanıyorsanız ve küçük bir projeyseniz, harici WebSocket/broadcast servisi daha mantıklı bir başlangıç olabilir. Ancak trafik ve maliyet arttıkça Laravel ve diğer PHP framework’ler için paylaşımlı hosting mi VPS mi sorusunu yeniden değerlendirmek yararlı olabilir.

“Resource Limit Reached” Gerçeği

Paylaşımlı hostingte WebSocket sunucusu ayağa kaldırabildiğinizi varsayalım. Eşzamanlı bağlantılar ve mesaj trafiği planın kaynak sınırlarını aştığında:

  • Kaynak sınırlama mekanizmasına göre CPU kullanımı kısıtlanabilir; bellek sınırında işlemler başarısız olabilir veya süreçler sonlandırılabilir.
  • Panelde “Resource Limit Reached” tarzı uyarılar görebilirsiniz.
  • Sunucunuzun PHP tarafı da bundan etkilenip siteniz yavaşlayabilir.

Kaynak sınırlarının etkilerini ayrıntılı incelemek için “Resource Limit Reached” hatasını önleme rehberini kullanabilirsiniz.

Özetle: Kendi WebSocket sunucunuzu çalıştırmak için paylaşımlı hostingin süreç, proxy ve kaynak sınırları belirleyicidir. Bu sınırlar yetersizse VPS’e geçiş veya harici bir gerçek zamanlı iletişim servisi değerlendirilebilir; uygulamanın PHP tarafı uygun bir paylaşımlı hosting planında kalabilir.

VPS Üzerinde WebSocket Sunucusu Kurmanın Avantajları

VPS (Sanal Özel Sunucu), kendi gerçek zamanlı uygulama altyapısını yönetmek isteyenler için esnek bir seçenektir. Root erişimi bulunan ve ilgili ayarlara izin veren bir VPS’te:

  • Kendi Node.js, PHP, Redis, Nginx versiyonlarınızı seçebilirsiniz.
  • Arka plan servisleri (systemd, PM2, Supervisor) özgürce kullanabilirsiniz.
  • Firewall, kernel ayarları, TCP parametreleri gibi düşük seviye ayarlara erişiminiz olur.
  • Kaynak tahsisi ve kullanım sınırları VPS planına bağlıdır; vCPU sayısı fiziksel CPU çekirdeklerinin yalnız size ayrıldığı anlamına gelmez.

Bu konuyu özellikle Node.js bakış açısından ele aldığımız Node.js uygulamalarını nerede host etmeli? yazısında detaylı inceledik. WebSocket sunucusunu kendiniz yönetecekseniz, VPS süreç ve ağ ayarlarında daha fazla kontrol sağlayabilir.

Kapasite Planlama: Kaç WebSocket Bağlantısı Taşıyabilirim?

Güvenilir bir bağlantı sayısı vermek için uygulamanın gerçek kaynak tüketimini ölçmek gerekir:

  • Bir WebSocket bağlantısının RAM tüketimi uygulamaya ve tutulan bağlantı durumuna bağlıdır; kapasite hesabında kendi uygulamanızdan ölçülen değer kullanılmalıdır.
  • Mesaj işleme azaldığında CPU ihtiyacı düşebilir; heartbeat ve bağlantı açma yükü sürer. Bellek ve socket sayısını CPU ile birlikte izleyin.
  • Çok konuşan (chat, oyun, canlı veri akışı) odalarda CPU da ciddi devreye girer.

Yalnızca bellek hesabını göstermek için, 2 GB RAM’li bir VPS üzerinde şu varsayımları kullanalım:

  • Örnekte işletim sistemi ve uygulamalara 0,8 GB ayrıldığını, bağlantılar için 1,2 GB kaldığını varsayalım.
  • Bağlantı başına 30 KB yalnız hesap varsayımı olsun; bu değer bir ürün ölçümü veya tipik tüketim değildir.

Ondalık birimlerle 1,2 GB ≈ 1.200.000 KB; 1.200.000 / 30 ≈ 40.000 çıkar. Bu yalnızca varsayımsal bellek bölümüdür; sunucunun taşıyabileceği bağlantı sayısı değildir.

  • TCP buffer’lar, kernel overhead, loglar, monitoring agent’ları vs. burada yok.
  • CPU yükü, GC (garbage collector), ani trafik patlamaları hesaba katılmıyor.

Aynı VPS’te Laravel, veritabanı, Redis veya başka hizmetler varsa paylarını ve ani yük payını ayrıca ölçün. 2 GB RAM tek başına güvenilir bir bağlantı sınırı vermez. k6, JMeter ve Locust ile yük testi rehberindeki ölçüm yaklaşımını kullanabilirsiniz; Socket.IO testinde gerçek Socket.IO istemcisi veya sürümü doğrulanmış protokol desteği gerekir.

Güvenlik, Firewall ve Ağ Katmanı

VPS üzerinde WebSocket sunucusu kurarken şu ilkeleri benimseyin:

  • Doğrudan 6001, 3000 vb. portları dış dünyaya açmak yerine, Nginx reverse proxy üzerinden web trafiğini 80/443 portlarında karşılayın. Firewall değişikliklerinde gerekli yönetim erişimini de koruyun.
  • WebSocket backend’inizi 127.0.0.1 üzerinde dinletin; dış dünyadan doğrudan erişilemesin.
  • Firewall (ufw, iptables, nftables vb.) ile gereksiz tüm portları kapatın.
  • SSL/TLS sonlandırmasını Nginx’te yaparak, WebSocket bağlantılarını wss:// üzerinden güvenli hale getirin.

Nginx reverse proxy kurulumuna yabancıysanız, başlangıç noktası olarak Nginx reverse proxy ve basit load balancer rehberini incelemeniz iyi olur. WebSocket özelinde, upgrade ve connection başlıklarını düzgün iletmek kritik.

Cloudflare kullanıyorsanız WebSocket desteğini, TLS ayarlarını ve origin erişim politikasını ayrıca doğrulayın. Cloudflare WAF ilk HTTP el sıkışmasını denetler; sonraki WebSocket mesajlarını taramaz. Mesaj yetkisi ve hız sınırları uygulamada kalır. Ağ veya edge güncellemeleri bağlantıları kesebilir; Cloudflare, WebSocket ve gRPC rehberi bağlantı katmanını ayrıntılandırır.

Laravel Reverb ve Echo: Çalışan Yayın Akışı

Bu örnekte Laravel, Reverb ve queue worker aynı VPS’tedir. Tarayıcı https://app.example.com üzerinde oturum açar; WebSocket bağlantısı ws.example.com üzerinden kurulur. Yayın yolu şöyledir: yetkili HTTP isteği → database queue → worker → Reverb → yetkili Echo abonesi.

  • Laravel / PHP-FPM: oturum, CSRF, veri doğrulama ve özel kanal yetkisini yönetir.
  • Database queue ve worker: yayın işini sıraya alır ve Reverb’e gönderir; jobs tablosu ve worker çalışır durumda olmalıdır.
  • Reverb: 127.0.0.1:8080 üzerinde dinleyen PHP WebSocket sunucusudur.
  • Nginx: tarayıcı TLS bağlantısını sonlandırıp Reverb’e iletir. Echo/Pusher istemcisi Socket.IO istemcisiyle değiştirilmez.

1) Kurulum, Ortam Değişkenleri ve Kanal Yetkisi

Laravel 13 projenizde Reverb kurulumunu çalıştırın. Kurulumun oluşturduğu REVERB_APP_ID, REVERB_APP_KEY ve REVERB_APP_SECRET değerlerini koruyun; secret tarayıcıya gitmez. Örnek Reverb 1.12.0, Echo 2.6.3 ve Pusher JS 8.6.0 sürüm kümesini kullanır. php artisan migrate öncesinde mevcut migration dosyalarını inceleyin; database queue için jobs tablosunun bulunduğunu doğrulayın.

php artisan install:broadcasting --reverb
composer require laravel/reverb:1.12.0
npm install --save-dev laravel-echo@2.6.3 pusher-js@8.6.0
php artisan migrate
php artisan channel:list
BROADCAST_CONNECTION=reverb
QUEUE_CONNECTION=database
DB_QUEUE_RETRY_AFTER=90

REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=8080
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST=ws.example.com
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https

REVERB_SERVER_HOST/PORT dinleme adresidir; REVERB_HOST/PORT/SCHEME Laravel’in yayın hedefidir. Burada ikisi de loopback’tir. VITE_REVERB_* ise tarayıcının herkese açık TLS adresidir. config/reverb.php içindeki ilgili uygulamada allowed_origins değerini ['app.example.com'] olarak daraltın; bu kanal yetkilendirmesinin yerine geçmez. Kurulumun routes/channels.php dosyasını ve /broadcasting/auth rotasını kaydettiğini doğrulayın; gerekirse bootstrap/app.php içindeki withRouting çağrısına channels: __DIR__.'/../routes/channels.php' ekleyin.

Aşağıdaki sınıf yalnız alıcıya ait kanala yayın yapar ve tarayıcıya yalnız message alanını gönderir. Public özelliklerin tamamını istemeden yayınlamamak için broadcastWith açıkça tanımlıdır.

<?php
// app/Events/NoticeCreated.php
namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class NoticeCreated implements ShouldBroadcast
{
    public function __construct(
        public string $userId,
        public string $message,
    ) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel('users.'.$this->userId)];
    }

    public function broadcastAs(): string
    {
        return 'notice.created';
    }

    public function broadcastWith(): array
    {
        return ['message' => $this->message];
    }
}
<?php
// routes/channels.php: retain any other application channels.
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('users.{userId}', function (User $user, string $userId) {
    return (string) $user->getAuthIdentifier() === $userId;
});

2) Yetkili Yayın Rotası ve Echo Abonesi

Çalışan Laravel oturum açma akışınızı kullanın. Aşağıdaki rotaları routes/web.php dosyasına mevcut importları yinelemeden ekleyin. Alıcı kimliği istemciden alınmaz; oturumdaki kullanıcıdan türetilir. web middleware CSRF kontrolünü, auth oturumu, throttle ise yayın isteği hızını sınırlar.

// Add to routes/web.php, below its existing PHP opening tag.
use App\Events\NoticeCreated;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::view('/realtime-demo', 'realtime-demo')->middleware('auth');

Route::post('/realtime-demo/notice', function (Request $request) {
    $data = $request->validate(['message' => ['required', 'string', 'max:200']]);
    event(new NoticeCreated(
        (string) $request->user()->getAuthIdentifier(),
        $data['message'],
    ));
    return response()->json(['queued' => true], 202);
})->middleware(['auth', 'throttle:20,1']);

resources/views/realtime-demo.blade.php dosyasını ve JavaScript girişini oluşturun. vite.config.js içindeki Laravel eklentisinin input listesine resources/js/realtime-demo.js ekleyin; mevcut girişleri koruyun. Ayrı sayfa yalnız bu Echo örneğini başlatır.

<!-- resources/views/realtime-demo.blade.php -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <meta name="user-id" content="{{ auth()->id() }}">
    <title>Realtime demo</title>
    @vite('resources/js/realtime-demo.js')
</head>
<body>
    <form id="notice-form">
        <label>Message <input name="message" maxlength="200" required></label>
        <button id="send" disabled>Send</button>
    </form>
    <p id="status" role="status">Connecting</p>
    <p id="notice"></p>
</body>
</html>
// resources/js/realtime-demo.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;
const csrf = document.querySelector('meta[name="csrf-token"]').content;
const userId = document.querySelector('meta[name="user-id"]').content;
const status = document.querySelector('#status');
const send = document.querySelector('#send');
let deliverySeen = false;
const echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: Number(import.meta.env.VITE_REVERB_PORT),
    wssPort: Number(import.meta.env.VITE_REVERB_PORT),
    forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
    enabledTransports: ['ws', 'wss'],
    authEndpoint: '/broadcasting/auth',
    auth: { headers: { 'X-CSRF-TOKEN': csrf } },
});

echo.private(`users.${userId}`)
    .subscribed(() => { send.disabled = false; status.textContent = 'Subscribed'; })
    .error(() => { send.disabled = true; status.textContent = 'Subscription failed'; })
    .listen('.notice.created', event => {
        deliverySeen = true;
        document.querySelector('#notice').textContent = event.message;
        status.textContent = 'Event received';
    });

echo.connector.pusher.connection.bind('state_change', ({ current }) => {
    if (current !== 'connected') send.disabled = true;
});

document.querySelector('#notice-form').addEventListener('submit', async event => {
    event.preventDefault();
    deliverySeen = false;
    status.textContent = 'Submitting';
    try {
        const response = await fetch('/realtime-demo/notice', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'X-CSRF-TOKEN': csrf,
            },
            body: JSON.stringify({ message: event.target.elements.message.value }),
        });
        if (!response.ok) throw new Error('Request failed');
        if (!deliverySeen) status.textContent = 'Queued; waiting for delivery';
    } catch {
        status.textContent = 'Request failed; check session or validation';
    }
});

Özel kanal users.{userId}, yalnız oturumdaki kullanıcıya açılır. Özel yayın adı notice.created olduğu için Echo dinleyicisinde baştaki nokta gereklidir: .notice.created. Sayfadaki Send düğmesi abonelik tamamlanınca açılır. HTTP 202 yalnız sıraya alma anlamına gelir; olayı görmek için worker ve Reverb de çalışmalıdır. Örnek anlık bildirimdir; teslim kaydı veya çevrimdışı mesaj geçmişi sağlamaz.

Laravel kanal yetkisi abonelik anında değerlendirilir; açık abonelikleri sürekli yeniden denetlemez. Oturum veya hesap iptalinde bağlı kullanıcıyı ayıracak uygulama mekanizmasını ayrıca kurun.

3) Nginx ile WebSocket Reverse Proxy

Geçerli TLS sertifikası hazırlanmış ws.example.com sanal hostu için aşağıdaki yapılandırma kullanılabilir. map HTTP bağlamındadır. Reverb /app/ yolu ile, sonraki bağımsız Socket.IO örneği /socket.io/ ile çalışır; yalnız kullandığınız location bloğunu etkinleştirebilirsiniz.

# map belongs in the http context, outside server blocks.
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 443 ssl;
    server_name ws.example.com;
    ssl_certificate     /etc/letsencrypt/live/ws.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ws.example.com/privkey.pem;

    # Reverb browser subscriptions; Laravel publishes over loopback :8080.
    location /app/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_read_timeout 120s;
        proxy_send_timeout 120s;
    }

    # Separate, optional Socket.IO example.
    location /socket.io/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_read_timeout 120s;
        proxy_send_timeout 120s;
    }

    location / { return 404; }
}

Burada proxy_pass sonunda URI bulunmadığı için yol korunur. proxy_read_timeout toplam bağlantı ömrü değildir; upstream’den iki okuma arasındaki sessizlik süresini sınırlar. 120 saniye bu örnekte Socket.IO’nun varsayılan 25 saniye ping aralığı + 20 saniye ping timeout toplamından uzundur; kendi heartbeat ayarlarınızla yeniden kontrol edin. Yapılandırmayı önce nginx -t ile doğrulayın. Reverb’ün yayın API’si /apps yolundadır; burada Laravel doğrudan loopback’e yayınladığı için bu yol internete açılmaz. Laravel ayrı hosta taşınırsa yayın için erişilebilir HTTPS /apps yolu veya korumalı özel ağ bağlantısı ayrıca kurulmalıdır.

4) Reverb, Queue Worker ve Node.js Süreç Yönetimi

İlk doğrulamayı ayrı terminal süreçleriyle yapabilirsiniz. config:cache sonrasında ortam değişiklikleri için cache’i yeniden üretin ve uzun ömürlü süreçleri yeniden başlatın. Vite değerleri derleme anında alınır; tarayıcı ayarını değiştirince tekrar derleyin.

php artisan config:cache
npm run build
php artisan reverb:start
# In a separate process:
php artisan queue:work database --queue=default --sleep=1 --tries=3 --timeout=60

--timeout=60, DB_QUEUE_RETRY_AFTER=90 değerinden küçüktür. Worker timeout’u için CLI PHP’de PCNTL gerekir; dış I/O süreleri 60 saniyelik iş bütçesinden kısa olmalıdır. İş uzunsa süreleri birlikte yeniden hesaplayın. --tries=3 ilk denemeyi içerir. İş gerçekten tamamlanmadan yalnız sürecin açık olması yayın sağlığı sayılmaz.

  • Supervisor veya systemd: Reverb ve queue worker için ayrı süreç tanımları, uygulama kullanıcısı, doğru PHP yolu, otomatik başlangıç ve log saklama ayarlayın. 60 saniyelik worker için örneğin 75 saniyelik durdurma payı tanıyın; mevcut uzun işler için bütçeyi yeniden hesaplayın.
  • Node.js: aşağıdaki Socket.IO sürecini systemd veya PM2’den biri yönetsin. PM2’de startup kaydı ve kaydedilmiş süreç listesi gerekir; log rotasyonu ayrıca ayarlanır.
  • Dağıtım: Reverb yeniden başlatılınca bağlantılar kapanır. İstemci yeniden bağlanmalı ve yetkisini tekrar doğrulatmalıdır; reload komutu tek başına kesintisiz bağlantı garantisi değildir.

VPS üzerinde Node.js uygulamalarını canlıya alırken PM2/systemd + Nginx + SSL kombinasyonunu nasıl kuracağınızı, adım adım Node.js’i canlıya alma rehberinde detaylandırdık. WebSocket sunucuları için de aynı prensipler geçerli.

Bağımsız Node.js ve Socket.IO: Yetkili Oda Örneği

Bu ikinci örnek Node.js 22.23.4, Socket.IO 4.8.4 ve jose 6.2.15 kullanır. Laravel/Reverb ile bağlantı kurmaz. Amaç, tek süreçte çalışan küçük bir test uygulamasında JWT doğrulaması, sunucu tarafı oda yetkisi, olay yayını ve token süresini göstermektir. Alice yalnız room:red, Bob yalnız room:blue odasına erişir.

Yeni bir test dizininde aşağıdaki paketleri kurun. key.mjs, token.mjs ve server.mjs dosyalarını sonraki örneklerden oluşturduktan sonra son üç satırı aynı güvenilir terminalde çalıştırın. Üretilen anahtar yalnız sunucu tarafındadır; token beş dakika geçerlidir. Üretimde demo token komutunu ve sabit ACL’yi gerçek kimlik sağlayıcınızın doğrulaması ve güncel izin kayıtlarıyla değiştirin.

npm init -y
npm install --save-exact socket.io@4.8.4 socket.io-client@4.8.4 jose@6.2.15
export DEMO_JWT_KEY_HEX="$(openssl rand -hex 32)"
node token.mjs alice
node server.mjs
// key.mjs: server-side only.
const hex = process.env.DEMO_JWT_KEY_HEX ?? '';
if (!/^[a-f0-9]{64}$/i.test(hex)) throw new Error('Set a 32-byte demo key');
export const key = Buffer.from(hex, 'hex');
// token.mjs: run only in the trusted demo server terminal.
import { SignJWT } from 'jose';
import { key } from './key.mjs';
const user = process.argv[2];
if (!['alice', 'bob'].includes(user)) throw new Error('Use alice or bob');
console.log(await new SignJWT({})
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuer('realtime-demo').setAudience('realtime-demo')
    .setSubject(user).setIssuedAt().setExpirationTime('5m')
    .sign(key));

Sunucu, token’ın imzası yanında issuer, audience, algoritma ve süresini denetler. Odaya katılma ve yayınlama ayrı ayrı yetkilendirilir. Origin filtresi tarayıcı kaynaklarını sınırlar; JWT ve ACL’nin yerine geçmez. CORS tek başına WebSocket erişim kontrolü değildir.

// server.mjs
import { createServer } from 'node:http';
import { Server } from 'socket.io';
import { jwtVerify } from 'jose';
import { key } from './key.mjs';

const origin = 'https://app.example.com';
const acl = new Map([
    ['alice', new Set(['room:red'])],
    ['bob', new Set(['room:blue'])],
]);
const http = createServer((request, response) => {
    response.writeHead(request.url === '/health' ? 200 : 404);
    response.end(request.url === '/health' ? 'ok' : 'not found');
});
const io = new Server(http, {
    cors: { origin, methods: ['GET', 'POST'] },
    allowRequest: (request, done) => done(null, request.headers.origin === origin),
    maxHttpBufferSize: 16_384,
});

io.use(async (socket, next) => {
    try {
        const { payload } = await jwtVerify(socket.handshake.auth.token, key, {
            algorithms: ['HS256'], issuer: 'realtime-demo', audience: 'realtime-demo',
            requiredClaims: ['sub', 'iat', 'exp'], maxTokenAge: '5m',
        });
        if (!acl.has(payload.sub) || payload.exp - payload.iat > 300) {
            throw new Error('Invalid identity or lifetime');
        }
        socket.data.user = payload.sub;
        socket.data.expires = payload.exp * 1000;
        next();
    } catch {
        next(new Error('Unauthorized'));
    }
});

io.on('connection', socket => {
    const timer = setTimeout(() => socket.disconnect(true),
        Math.max(0, socket.data.expires - Date.now()));
    socket.on('disconnect', () => clearTimeout(timer));
    let windowStart = Date.now(), count = 0;
    socket.use((packet, next) => {
        if (Date.now() >= socket.data.expires) return socket.disconnect(true);
        if (Date.now() - windowStart >= 10_000) { windowStart = Date.now(); count = 0; }
        if (++count > 20) return socket.disconnect(true);
        next();
    });
    socket.on('join', async (room, callback) => {
        const reply = typeof callback === 'function' ? callback : () => {};
        if (!acl.get(socket.data.user)?.has(room)) return reply({ ok: false });
        await socket.join(room);
        reply({ ok: true });
    });
    socket.on('publish', (data, callback) => {
        const reply = typeof callback === 'function' ? callback : () => {};
        if (!data || !acl.get(socket.data.user)?.has(data.room)
            || !socket.rooms.has(data.room) || typeof data.text !== 'string'
            || !data.text.trim() || data.text.length > 200) return reply({ ok: false });
        io.to(data.room).emit('notice', { text: data.text, sender: socket.data.user });
        reply({ ok: true });
    });
});

http.listen(3000, '127.0.0.1');
for (const signal of ['SIGINT', 'SIGTERM']) {
    process.once(signal, () => io.close(() => process.exit(0)));
}

İstemciyi https://app.example.com sayfasındaki JavaScript paketine ekleyin; terminalde üretilen kısa süreli Alice token’ını test penceresine yapıştırın. Token sorgu URL’sine konulmaz. Bu demo bağlanınca odaya katılır ve Hello mesajı yayınlar; ağdan tekrar bağlanınca aynı işlemi yineleyeceği için iş sonucunu temsil eden mükerrer işlemler için uygun değildir.

// Bundle into an HTTPS page at https://app.example.com.
import { io } from 'socket.io-client';

const token = window.prompt('Paste the short-lived alice demo token');
const socket = io('https://ws.example.com', { auth: { token }, autoConnect: false });
socket.on('notice', event => { console.log('Notice:', event.text); });
socket.on('connect_error', () => { console.log('Connection or authorization failed'); });
socket.on('disconnect', reason => {
    if (reason === 'io server disconnect') console.log('Obtain a fresh token before reconnecting');
});
socket.on('connect', () => {
    socket.timeout(5000).emit('join', 'room:red', (error, joined) => {
        if (error || !joined?.ok) return console.log('Join failed');
        socket.timeout(5000).emit('publish', { room: 'room:red', text: 'Hello' },
            (error, result) => console.log(error || !result?.ok ? 'Publish failed' : 'Accepted'));
    });
});
socket.connect();

20 olay / 10 saniye sınırı yalnız bu bağlantı içindir; yeniden bağlanma bunu sıfırlar. Üretimde kullanıcı ve IP bazlı bağlantı sınırları, ortak hız limiti, güncel izin/iptal denetimi ve gerektiğinde bağlı kullanıcıları ayırma mekanizması ekleyin. Token süresi dolunca sunucu bağlantıyı keser; yeni token edinmeden zorla tekrar bağlanmayın. ACK, uygulamanın kabulünü bildirir; kalıcı kayıt veya tüm alıcıların teslim aldığını kanıtlamaz.

Tek VPS’te Node.js uygulamalarını nasıl konumlandırabileceğinizi, Nginx ile nasıl öne çıkaracağınızı ve SSL’i nasıl yöneteceğinizi anlamak için, mutlaka Node.js hosting karşılaştırması yazısını da arka planda okumanızı öneririm.

Örneğin systemd 252 için /etc/systemd/system/realtime-demo.service tanımı:

[Unit]
Description=Socket.IO demo
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
User=realtime
WorkingDirectory=/srv/realtime
EnvironmentFile=/etc/realtime-demo.env
ExecStart=/opt/node22/bin/node /srv/realtime/server.mjs
Restart=always
RestartSec=5
TimeoutStopSec=30
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Önce realtime kullanıcısını, /srv/realtime uygulama dizinini ve gerçek Node 22 binary yolunu hazırlayın. /etc/realtime-demo.env dosyası yalnız sunucu yöneticisinin okuyabildiği izinlerle DEMO_JWT_KEY_HEX=<64-hex-character-key> içermelidir; shell komutu yazılmaz. Anahtar demo token’larını üreten anahtarla aynı olmalıdır. Unit dosyasını systemd-analyze verify ile kontrol edip ardından yetkili yönetici hesabıyla daemon-reload ve enable –now uygulayın. Restart sınırı devreye girdiğinde alarm gereklidir; journal için saklama sınırı belirleyin.

Paylaşımlı Hosting’ten VPS’e WebSocket Uygulamasını Taşımak

Örnek bir geçiş senaryosunda ilk sürüm paylaşımlı hosting + harici broadcast servisiyle yayına alınabilir. Kullanım büyüdüğünde maliyet veya performans gereksinimleri değişirse ekip VPS’e geçiş seçeneğini değerlendirebilir; bu sorunlar her projede kaçınılmaz değildir.

Bu geçişi daha sakin yapmak için şu adımları takip edebilirsiniz:

1) DNS ve Alan Adı Planı

  • Laravel/PHP siteniz hâlâ paylaşımlı hostingte kalacaksa, WebSocket subdomain’ini (örn. ws.example.com) yeni VPS’e yönlendirin. DNS değişikliği tek başına yeterli değildir: Laravel ile VPS arasındaki olay aktarımını ve yetkilendirme erişimini de planlayın. Önceki tek sunuculu örneğin yerel bağlantı varsayımları bu ayrık kurulum için yeterli değildir.
  • Tam geçiş yapacaksanız, example.com, api.example.com ve ws.example.com gibi tüm kritik kayıtları yeni sunucuya planlı olarak taşıyın.
  • DNS geçişlerinde kesintiyi en aza indirmek için, TTL stratejileriyle zero-downtime taşıma rehberindeki önerileri uygulayın.

2) Uygulama ve Konfigürasyon Taşıma

  • Laravel ve gerekiyorsa ayrı Socket.IO uygulamasını, kilit dosyaları ve hedef runtime sürümleriyle birlikte aktarın.
  • Veritabanı, queue, cache ve yayın bağlantısını ayrı ayrı yapılandırın; Vite adreslerini yeniden derleyin.
  • İki hosta ayrılıyorsa loopback adresi diğer makineyi göstermez. Laravel→Reverb yayın bağlantısı için HTTPS veya korumalı özel ağ; tarayıcı→Laravel yetkilendirmesi için doğru oturum/origin düzeni kurun.
  • Reverb, queue worker ve varsa Node sürecini ayrı yönetip Nginx/TLS yolunu doğrulayın.

Paylaşımlı hostingten VPS’e geçişi genel anlamda nasıl planlamanız gerektiğini, sadece WebSocket değil tüm bileşenler için paylaşımlı hosting’den VPS’e geçiş rehberinde detaylı anlattık.

3) Test, Load Test ve Aşamalı Geçiş

  • Önce staging’de oturum açma, izinli/izinsiz kanal, izinli/izinsiz yayın, geçersiz/süresi dolan token ve gerçek olay teslimini sınayın.
  • Laravel için Echo/Pusher protokolüyle, Socket.IO için socket.io-client ile bağlanın. Ham WebSocket açılması; Engine.IO/Socket.IO handshake, oda aboneliği veya Laravel kanal yetkisini doğrulamaz.
  • Ardından temsilî mesaj boyutu, fan-out, bağlantı açma/kapama, yeniden bağlanma dalgası ve worker gecikmesiyle yük testi yapın. Az istemcili işlev testleri kapasite ölçümü değildir.
  • DNS öncesinde TTL’yi düşürüp eski TTL süresini bekleyin. TTL trafik yüzdesi bölüştürmez ve açık bağlantıları taşımaz; aşamalı dağıtım için yük dengeleyici veya ağırlıklı DNS gerekir. DNS önbelleği nedeniyle tam kullanıcı yüzdesi beklemeyin; eski uçta kalan bağlantılar ve geri dönüş planı ayrıca yönetilir.

Operasyonel İpuçları: Loglama, İzleme, Yedekleme

Hatalı bir yayın veya yeniden bağlanma dalgası çok sayıda aboneyi etkileyebilir. Gözlem ve kurtarma yollarını bağlantı katmanıyla birlikte tasarlayın.

Loglama ve Gözlemlenebilirlik

  • Laravel, Reverb, Node.js ve Nginx loglarında istek/olay korelasyonu, yetki retleri ve hata türlerini izleyin; token, session secret veya özel mesaj gövdelerini loglamayın.
  • Logları merkezi ELK veya Loki gibi bir sistemde, erişim ve saklama sınırlarıyla toplayabilirsiniz.
  • VPS merkezi log yönetimi rehberi mimari için yararlıdır. Promtail 2 Mart 2026’da destek ömrünü tamamladı; yeni kurulumda desteklenen bir collector, örneğin Grafana Alloy kullanın.

Kaynak İzleme ve Alarm

WebSocket bağlantıları arttıkça, CPU, RAM, disk IO ve ağ trafiğini yakından izlemeniz gerekir. Özellikle:

  • Reverb/Node bellek tüketimi, CPU ve event-loop gecikmesi; PHP-FPM ve queue worker baskısı.
  • Açık bağlantı/dosya tanıtıcısı, bağlantı açma hızı, TCP durumları ve proxy timeout’ları.
  • Yayın kuyruğu yaşı, teslim gecikmesi, hata/yeniden deneme oranı ve yeniden bağlanma dalgaları.
  • Bir /health HTTP yanıtına ek olarak yetkili abonelik ve olay teslimini doğrulayan temsilî kontrol.

Bu metrikleri toplamak ve dashboard’lar oluşturmak için, Prometheus + Grafana ile VPS izleme rehberindeki yapıyı WebSocket sunucunuza da uygulayabilirsiniz.

Yedekleme Stratejisi

Açık bağlantılar, oda üyelikleri ve bekleyen mesajlar bellekte durum tutar; sunucu yeniden başlayınca bunlar korunmuş sayılmaz. Kalıcı iş verisini bağlantı sürecinden ayırın:

  • Uygulama kodu, kilit dosyaları ve güvenli biçimde korunan yapılandırma/anahtarlar.
  • Veritabanı, gerekli olay geçmişi, son işlenen olay kimliği ve yeniden senkronizasyon yolu.
  • Redis kullanılıyorsa queue ve session verileri için kayıp toleransı, persistence ve eviction politikası; cache ile aynı dayanıklılık varsayımı yapılmaz.
  • Socket.IO’nun varsayılan teslimi en fazla bir kezdir; bağlantı kopukken kaçan sunucu olayları kendiliğinden tekrar oynatılmaz. Kayıp kabul edilmiyorsa kalıcı geçmiş, offset ve idempotency tasarlayın.

Uygun bir 3-2-1 yedekleme modeli kurmak için, 3-2-1 yedekleme stratejisi rehberindeki prensipleri VPS’iniz için de uygulayabilirsiniz. Yedekleri VPS’in diskinde tutmak yerine, Object Storage gibi harici ortamlara periyodik olarak aktarmak daha güvenlidir.

Yatay ölçeklemede Reverb’ün Redis scaling desteği veya Socket.IO’nun uygun adapter’ı süreçler arasında olay dağıtır. Bunlar birbirinin protokol köprüsü değildir. Socket.IO’da HTTP long-polling açıksa çoklu node için oturum yapışkanlığı da gerekir; Redis adapter bunu kaldırmaz ve kalıcı replay sağlamaz. Redis’i özel ağ, kimlik doğrulama/ACL ve gerekli bağlantılarda TLS ile koruyun; prefix veya mantıksal DB ayrı kaynak ve arıza alanı oluşturmaz.

DCHost Tarafında Hangi Senaryoda Hangi Ürün Mantıklı?

Ürün seçimini şu ihtiyaçlarla eşleştirebilirsiniz:

  • Yönetimi sağlayıcıda kalan uygulama: gerekli süreç/proxy özelliklerini karşılayan paylaşımlı plan veya harici gerçek zamanlı servis. Uzun vadeli uygunluk ölçüm ve sözleşme koşullarıyla belirlenir.
  • Süreç ve ağ kontrolü gereken uygulama: uygun VPS planı; Laravel/Reverb ve bağımsız Node.js bileşenleri gerektiği kadar kurulabilir. Her projeye Redis veya Node.js eklemek zorunlu değildir.
  • Yüksek erişilebilirlik veya özel kaynak ihtiyacı: birden fazla VPS, dedicated sunucu veya colocation değerlendirilebilir. Tek büyük sunucu yedeklilik sağlamaz; arıza alanları ve bakım sorumluluğu ayrıca tasarlanır.

Teklif değerlendirirken hedef eşzamanlı bağlantı sayısını, mesaj boyutunu/sıklığını, fan-out’u, gecikme hedefini ve aynı makinedeki diğer servisleri belirtin. Böylece CPU/RAM kadar trafik kotası, yönetim kapsamı ve ölçekleme maliyeti de karşılaştırılabilir.

Hosting Seçimini Protokol ve Ölçümle Yapın

WebSocket hosting seçimi, yalnız birkaç paket kurmaktan fazlasını gerektirir. Laravel Reverb/Echo ile Socket.IO’nun protokollerini doğru eşleştirin; yayın kaynağı, kanal yetkisi, kuyruk ve bağlantı süreçlerinin tamamını test edin.

Kararı verirken şu dört noktayı birlikte değerlendirin:

  • Paylaşımlı planın daemon, proxy ve kaynak desteğini doğrulayın; uygun değilse harici servis veya VPS seçeneğini ölçün.
  • VPS’te erişim kontrolü, güncelleme, servis başlangıcı, log saklama ve yedek sorumluluğunu açıkça atayın.
  • Nginx/TLS ve varsa Cloudflare’i uygulama yetkisinin tamamlayıcısı olarak kullanın; mesaj kontrolünü uygulamada sürdürün.
  • Temsilî yük, bağlantı kopması, yeniden bağlanma ve kalıcı durum kurtarmayı geçiş öncesinde deneyin.

Önce desteklenen protokolle çalışan küçük akışı kurun, sonra gerçek trafik biçimiyle ölçün. Gerekli kontrolü ve işletim kapasitesini sağlayan plan, yalnız kâğıt üzerindeki bağlantı sayısından daha anlamlı bir seçim ölçütüdür.

Birincil Teknik Kaynaklar

Sıkça Sorulan Sorular

Evet, plan uzun süreli uygulama sürecini, WebSocket proxy geçişini ve gereken runtime sürümünü destekliyorsa mümkün olabilir. Panelde Node.js seçeneği bulunması tek başına yeterli değildir. Süreç yeniden başlatma politikasını, timeout ve kaynak limitlerini doğrulayın; temsilî bağlantı ve mesaj yükünde ölçün. Evrensel kullanıcı sayısı eşiği yoktur. Gereksinimler karşılanmıyorsa harici gerçek zamanlı servis veya VPS değerlendirilebilir; Laravel’in HTTP tarafı uygun paylaşımlı planda kalabilir.

Seçimi bağlantı/olay hacmi, gecikme, servis kotası, işletim sorumluluğu ve toplam maliyetle yapın. Yönetilen servis uzun vadede de uygun olabilir; queued Laravel yayınları için worker ihtiyacı yine vardır. Kendi sunucunuzda bu yazının güncel Laravel yolu Reverb ve Echo/pusher-js kullanır. Reverb PHP sürecidir. Eski Laravel Echo Server veya BeyondCode Laravel WebSockets, Laravel 13 için doğrudan kurulum önerisi değildir. Socket.IO ise ayrı protokoldür; kendiliğinden Reverb/Echo köprüsü oluşturmaz.

Bağlantı sayısı, mesaj sıklığı/boyutu, fan-out ve aynı sunucudaki PHP, worker, veritabanı gibi hizmetleri birlikte ölçün. Belirli RAM veya vCPU miktarı evrensel bağlantı kapasitesi vermez; yazıdaki 30 KB hesabı yalnız varsayımsal bir aritmetik örnektir. Önce gerçek protokolle kimlik, abonelik ve mesaj teslimini doğrulayın; sonra temsilî yükte gecikme, hata, bellek, CPU ve kuyruk yaşını ölçün. Socket.IO testi için uyumlu Socket.IO istemcisi gerekir; ham WebSocket handshake tek başına yeterli değildir.

Desteklenen WebSocket yolu, TLS ve origin erişim politikası doğru kurulmalıdır. Cloudflare WAF başlangıçtaki HTTP el sıkışmasını denetler; bağlantı kurulduktan sonraki WebSocket mesajlarını incelemez. Oda/kanal yetkisi, payload doğrulaması ve olay hız sınırları uygulamada kalır. CORS kimlik doğrulama değildir. Nginx read timeout’unu heartbeat davranışıyla eşleştirin; edge veya ağ değişikliklerinde bağlantıların kapanabileceğini hesaba katın. CDN kullanmak origin’e doğrudan erişimi kendiliğinden kapatmaz.

Önce yalnız bağlantı servisini mi yoksa tüm uygulamayı mı taşıyacağınızı belirleyin. Yayın, queue, oturum ve kanal yetkilendirmesi yollarını birlikte kurun; iki hosta ayrılınca loopback adresleri yeterli değildir. HTTPS veya korumalı özel ağ üzerinden yayın erişimini ve yeni Vite istemci adresini doğrulayın. İzinli/izinsiz abonelik, olay teslimi, token süresi, yeniden bağlanma ve temsilî yükü staging’de test edin. TTL’yi önceden düşürmek önbellek süresini etkiler; trafik yüzdesi bölüştürmez veya açık bağlantıları taşımaz. Eski bağlantılar, geri dönüş ve durum kurtarma için ayrı plan yapın.
↑