
August 14, 2026
11 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Implementing GraphQL Subscriptions with Laravel Lighthouse transforms a standard request-response API into a reactive system where clients receive instant updates without polling. While Lighthouse provides the schema directives and subscription engine, the actual real-time delivery depends entirely on your Laravel broadcasting infrastructure and WebSocket server configuration. This guide covers the complete production setup, moving beyond basic tutorials to address the Redis, queue, and deployment realities required for stable subscriptions in 2026.
Before diving into subscription specifics, ensure your foundation is solid. Real-time features add operational complexity that compounds existing architectural issues. If you are still structuring your core API, review Laravel API best practices first to avoid building subscriptions on top of an unscalable base. Subscriptions amplify both good and bad design decisions; they work reliably only when your underlying event system and queue infrastructure are already production-grade.
How do GraphQL Subscriptions with Laravel Lighthouse actually work?
Understanding the internal mechanism prevents the most common debugging failures. GraphQL Subscriptions with Laravel Lighthouse do not maintain a direct PHP process per client. Instead, they rely on a pub/sub pattern decoupled from the HTTP lifecycle.
The sequence operates as follows:
- Initial Handshake: The client sends a GraphQL subscription query over a WebSocket connection. Lighthouse validates the query, resolves any initial arguments, and registers the subscription in storage (typically Redis).
- Event Trigger: A mutation or background job in your Laravel application fires an event. This is usually done via
Broadcast::event()or by returning a subscription trigger from a mutation resolver. - Pub/Sub Distribution: Laravel serializes the event payload and publishes it to a Redis channel. This step is non-blocking and handled by the queue worker if configured correctly.
- Delivery: The WebSocket server subscribes to the Redis channel. When a message arrives, it matches the event against active subscription registrations and pushes the updated GraphQL payload to all matching connected clients.
This architecture means your PHP application never holds a socket open. The WebSocket server (Reverb or Laravel WebSockets) handles persistence, while Laravel remains stateless. Misconfiguring any link in this chain—Redis connectivity, queue workers, or channel naming—silently breaks subscriptions without throwing HTTP errors.
How do you configure broadcasting for GraphQL Subscriptions with Laravel Lighthouse?
Lighthouse does not replace Laravel’s broadcasting system; it consumes it. Your broadcasting configuration must be correct before Lighthouse subscriptions will function. In 2026, with Laravel 12.x and PHP 8.4, the recommended stack uses Redis 7.x as the broadcast driver and either Laravel Reverb (first-party) or Laravel WebSockets (community-maintained) as the socket server.
Redis Configuration
Redis serves dual purposes: caching/broadcasting and subscription storage. Ensure your config/database.php Redis configuration includes a dedicated connection for broadcasting if your workload is heavy:
<?php
// config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'broadcast' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => 1, // Isolate broadcast traffic
],
], In .env, set BROADCAST_CONNECTION=redis. Verify connectivity with php artisan tinker followed by Redis::connection('broadcast')->ping(). A common mistake in Nepal-hosted environments is firewall rules blocking Redis port 6379 between the application server and Redis instance when they are on separate VMs.
WebSocket Server Selection
| Feature | Laravel Reverb | Laravel WebSockets (beyondcode) |
|---|---|---|
| Maintenance Status | First-party (Laravel team) | Community (archived but stable) |
| Laravel 12 Support | Native | Requires fork or compatibility patch |
| Horizontal Scaling | Built-in Redis pub/sub | Requires manual Redis configuration |
| Dashboard | Minimal | Full debug dashboard |
| Production Ready 2026 | Yes | Legacy projects only |
For new projects in 2026, use Laravel Reverb. Install via composer require laravel/reverb and run php artisan reverb:install. For existing projects on Laravel WebSockets that cannot migrate immediately, ensure you are running version 2.x with PHP 8.2+ compatibility patches applied.
How do you implement a subscription class in Lighthouse?
With broadcasting configured, define your subscription in the Lighthouse schema and create the corresponding PHP class. This example assumes a legal-tech portal where clients need real-time updates when their case status changes—a pattern I have implemented across multiple Nepal law firm portals.
Schema Definition
# schema.graphql
type Subscription {
caseStatusUpdated(caseId: ID!): CaseStatusUpdate!
@subscription(class: "Subscriptions\\CaseStatusUpdated")
}
type CaseStatusUpdate {
caseId: ID!
status: String!
updatedAt: DateTime!
note: String
} Subscription Class Implementation
Create app/GraphQL/Subscriptions/CaseStatusUpdated.php:
<?php
namespace App\GraphQL\Subscriptions;
use Nuwave\Lighthouse\Subscriptions\Subscription;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Illuminate\Support\Facades\Auth;
class CaseStatusUpdated extends Subscription
{
/
* Filter which subscribers should receive the update.
*/
public function authorize($root, array $args, GraphQLContext $context): bool
{
// Only allow users who own this case or are assigned attorneys
return Auth::check() &&
($root->user_id === Auth::id() ||
$root->assigned_attorney_id === Auth::id());
}
/
* Resolve the subscription payload.
*/
public function resolve($root, array $args): array
{
return [
'caseId' => $root->id,
'status' => $root->status,
'updatedAt' => $root->updated_at,
'note' => $root->latest_note,
];
}
/**
* Define the channel name for filtering.
*/
public static function topicName(array $args): string
{
return 'case.' . $args['caseId'];
}
} The authorize method is critical. Without it, every subscriber to caseStatusUpdated receives every case update, creating a data leak. Lighthouse calls authorize both at subscription registration and at delivery time. Always validate ownership or permissions here, never assume the client filtered correctly.
Triggering the Subscription
Subscriptions can be triggered from mutations or event listeners. The mutation approach is explicit and traceable:
<?php
namespace App\GraphQL\Mutations;
use App\Models\CaseFile;
use Nuwave\Lighthouse\Subscriptions\BroadcastSubscription;
class UpdateCaseStatus
{
public function __invoke($root, array $args): CaseFile
{
$case = CaseFile::findOrFail($args['id']);
$case->update(['status' => $args['status']]);
// Trigger subscription with the updated model
BroadcastSubscription::trigger(
'caseStatusUpdated',
['caseId' => $case->id],
$case
);
return $case;
}
} Alternatively, use Laravel events for decoupled triggers. Create CaseStatusChanged event implementing ShouldBroadcastNow, then call Broadcast::event(new CaseStatusChanged($case)) from your service layer. Lighthouse automatically maps broadcast events to subscriptions when channel names match.
What are the production deployment requirements for GraphQL Subscriptions with Laravel Lighthouse?
Development setups mask production realities. On a real client project serving legal professionals across Nepal, subscriptions failed silently after deployment because the WebSocket server was not supervised and Redis connections were not persisted. Address these requirements before going live.
Process Supervision
The WebSocket server must run as a persistent daemon. Use systemd on Ubuntu 22/24:
# /etc/systemd/system/reverb.service
[Unit]
Description=Laravel Reverb WebSocket Server
After=network.target redis.service
[Service]
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/bin/php artisan reverb:start --host=0.0.0.0 --port=8080
Restart=always
RestartSec=5
Environment="APP_ENV=production"
[Install]
WantedBy=multi-user.target Enable with sudo systemctl enable --now reverb.service. Never run the WebSocket server inside tmux or screen in production. Process crashes without supervision cause silent subscription failures that are difficult to diagnose.
Queue Workers
Subscription broadcasting should be queued to avoid blocking HTTP responses. Configure a dedicated queue for broadcasts in config/queue.php and run workers with --queue=broadcasts,default. Monitor queue depth; broadcast jobs piling up indicate Redis contention or insufficient workers. For high-traffic systems, I typically allocate 2–4 dedicated broadcast workers separate from general application queues.
Nginx Reverse Proxy
Proxy WebSocket connections through Nginx to handle SSL termination and load balancing:
server {
listen 443 ssl http2;
server_name api.example.com;
location /graphql {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # Keep WS alive 24h
proxy_send_timeout 86400s;
}
} The proxy_read_timeout value is critical. Default Nginx timeouts (60s) kill idle WebSocket connections. Set this to match your expected maximum idle period. For legal-tech portals where attorneys may leave dashboards open during court sessions, 24-hour timeouts prevent unnecessary reconnections.
How do you debug failing GraphQL Subscriptions with Laravel Lighthouse?
Subscription failures are notoriously silent. The client receives no error; updates simply stop arriving. Follow this diagnostic sequence when subscriptions work locally but fail in staging or production.
- Verify WebSocket connectivity: Open browser DevTools → Network → WS tab. Confirm the connection establishes and stays open. A 404 indicates misconfigured Nginx proxy paths. A 101 Switching Protocols response confirms successful upgrade.
- Check Redis pub/sub: Run
redis-cli MONITORon the server while triggering an event. You should seePUBLISHcommands with your channel name. Absence indicates the broadcast job never executed or Redis connection failed. - Inspect queue failures: Check
failed_jobstable and Laravel logs forBroadcastException. Common causes include serialization errors in the event payload or missing authentication context in queued jobs. - Validate authorization: Add temporary logging in your subscription’s
authorizemethod. Ifauthorizereturns false, the subscription silently drops. This is the most frequent cause of "subscriptions work for admin but not regular users" issues. - Test with Lighthouse debug mode: Enable
LIGHTHOUSE_DEBUG=truein development. Lighthouse adds subscription debug information to responses, including registration IDs and filter criteria. Never enable this in production.
For complex debugging scenarios involving multiple services, refer to building real-time features in Laravel using WebSockets and Redis for deeper coverage of the underlying broadcasting mechanics that Lighthouse abstracts.
When should you avoid GraphQL Subscriptions with Laravel Lighthouse?
Subscriptions are not universally appropriate. They introduce persistent connections, stateful infrastructure, and operational overhead that simpler alternatives avoid. Consider these trade-offs before committing:
- High-frequency updates (>10/sec per client): WebSocket throughput becomes the bottleneck. Use Server-Sent Events (SSE) or short-polling instead. GraphQL subscriptions excel at low-to-medium frequency state changes, not streaming telemetry.
- Public anonymous feeds: Without authentication, subscription channels become attack vectors for resource exhaustion. Implement rate limiting and connection caps at the WebSocket server level. For public content like blog updates, RSS or SSE with CDN caching is more scalable.
- Simple notification-only use cases: If clients only need "something changed" signals without payload data, use Pusher/Ably native notifications or Firebase Cloud Messaging. Reserve GraphQL subscriptions for cases where the client needs the full updated GraphQL object graph.
- Teams without DevOps capacity: Subscriptions require managing persistent processes, monitoring connection counts, and handling Redis scaling. If your team cannot commit to this operational burden, evaluate managed services like Ably or Pusher that integrate with Lighthouse via custom broadcast drivers.
For teams evaluating whether to invest in real-time infrastructure at all, hiring a web developer in Nepal with proven Lighthouse and WebSocket experience can bridge the gap between prototype and production reliability without building internal expertise from scratch.
Deploying GraphQL Subscriptions with Laravel Lighthouse Reliably
GraphQL Subscriptions with Laravel Lighthouse deliver genuine real-time value when the full stack is configured correctly: Redis broadcasting, supervised WebSocket servers, queued event processing, and proper authorization filtering. The implementation details matter more than the schema elegance. Test your subscription pipeline end-to-end in a staging environment that mirrors production infrastructure before shipping to clients. Monitor connection counts, queue depths, and Redis memory usage as primary health indicators.
If you are implementing real-time features for a Laravel application and need hands-on guidance or production-ready setup, reach out to discuss your specific requirements. I have deployed Lighthouse subscriptions across legal-tech platforms, booking systems, and eCommerce dashboards in Nepal and internationally, and can help you avoid the pitfalls that only surface under real user load.

