<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Deploynix</title>
    <description>The latest articles on DEV Community by Deploynix (@deploynix).</description>
    <link>https://dev.to/deploynix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3800435%2Fa1f4de22-651f-46e8-adc8-a9da58944683.png</url>
      <title>DEV Community: Deploynix</title>
      <link>https://dev.to/deploynix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deploynix"/>
    <language>en</language>
    <item>
      <title>How Much Traffic Can a $5 Server Handle? Load Testing Laravel on Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sun, 26 Apr 2026 09:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/how-much-traffic-can-a-5-server-handle-load-testing-laravel-on-deploynix-1co5</link>
      <guid>https://dev.to/deploynix/how-much-traffic-can-a-5-server-handle-load-testing-laravel-on-deploynix-1co5</guid>
      <description>&lt;p&gt;Every developer launching a new project asks the same question: how much server do I actually need? The cloud providers offer servers starting at $4-6/month, but their marketing pages show theoretical bandwidth limits and CPU benchmarks that tell you nothing about how your Laravel application will perform under real-world load.&lt;/p&gt;

&lt;p&gt;So let's find out. In this post, we'll provision a $5 server on Deploynix, deploy a representative Laravel application, and progressively load test it until it breaks. Along the way, we'll apply optimizations and measure their impact. By the end, you'll know exactly what a budget server can handle — and when it's time to scale up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test Environment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Server
&lt;/h3&gt;

&lt;p&gt;We're using a typical $5/month cloud server — the kind you'd get from Hetzner, DigitalOcean, or Vultr at this price point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU: 1 shared vCPU&lt;/li&gt;
&lt;li&gt;RAM: 1 GB&lt;/li&gt;
&lt;li&gt;Disk: 25 GB SSD&lt;/li&gt;
&lt;li&gt;Network: 1 Gbps&lt;/li&gt;
&lt;li&gt;OS: Ubuntu 24.04&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the smallest server most providers offer. Deploynix provisions it as an App server — running Nginx, PHP 8.4 with FPM, MySQL, Valkey, and Supervisor all on the same machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Application
&lt;/h3&gt;

&lt;p&gt;Testing against a "Hello World" endpoint is meaningless. Real Laravel applications hit the database, render Blade views, check authentication, and process middleware. Our test application represents a typical content site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication middleware on all routes&lt;/li&gt;
&lt;li&gt;A dashboard page that queries 5 Eloquent models with relationships&lt;/li&gt;
&lt;li&gt;A listing page with pagination (20 items per page, eager-loaded relationships)&lt;/li&gt;
&lt;li&gt;An API endpoint returning JSON through an Eloquent Resource&lt;/li&gt;
&lt;li&gt;Active sessions stored in Valkey&lt;/li&gt;
&lt;li&gt;Config, route, and view caches enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives us a realistic baseline that reflects what most Laravel applications actually do.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Load Testing Tool
&lt;/h3&gt;

&lt;p&gt;We'll use &lt;code&gt;k6&lt;/code&gt;, a modern load testing tool that's developer-friendly and doesn't consume excessive resources on the machine running the tests. A basic k6 script looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Ramp to 10 users&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Stay at 10&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Ramp to 50&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Stay at 50&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// Ramp to 100&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// Stay at 100&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;    &lt;span class="c1"&gt;// Ramp down&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-app.deploynix.cloud/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response time &amp;lt; 500ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run tests from a separate machine — never from the server you're testing. Loading testing from the same server contaminates results because the test tool competes with the application for resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Baseline Test (No Optimization)
&lt;/h2&gt;

&lt;p&gt;First, let's deploy the application with default settings — no caching commands, no optimization. This represents what happens when a developer pushes code and forgets to run the optimization commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  10 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;At 10 concurrent users with a 1-second think time (each user makes a request, waits 1 second, repeats):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~9.5&lt;/li&gt;
&lt;li&gt;Median response time: 95ms&lt;/li&gt;
&lt;li&gt;P95 response time: 180ms&lt;/li&gt;
&lt;li&gt;P99 response time: 310ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~25%&lt;/li&gt;
&lt;li&gt;Memory usage: ~680MB (of 1GB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this level, the server handles everything comfortably. Response times are good, and there's plenty of headroom.&lt;/p&gt;

&lt;h3&gt;
  
  
  50 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;Ramping to 50 concurrent users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~42&lt;/li&gt;
&lt;li&gt;Median response time: 320ms&lt;/li&gt;
&lt;li&gt;P95 response time: 890ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,400ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~78%&lt;/li&gt;
&lt;li&gt;Memory usage: ~720MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Things are getting warm. The P95 response time approaching 1 second means one in twenty requests feels sluggish. CPU is climbing. Memory is tight — MySQL and Valkey are competing with PHP workers for that 1GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  100 Concurrent Users
&lt;/h3&gt;

&lt;p&gt;Pushing to 100:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~48 (barely increased from 50 users)&lt;/li&gt;
&lt;li&gt;Median response time: 1,800ms&lt;/li&gt;
&lt;li&gt;P95 response time: 4,200ms&lt;/li&gt;
&lt;li&gt;P99 response time: 8,500ms&lt;/li&gt;
&lt;li&gt;Error rate: 3.2% (timeouts and 502 errors)&lt;/li&gt;
&lt;li&gt;CPU usage: 98%&lt;/li&gt;
&lt;li&gt;Memory usage: ~950MB (OOM killer territory)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server hit its wall. Request throughput plateaued at ~48 req/s while response times exploded. Some requests are timing out entirely. The CPU is saturated, and memory is dangerously close to triggering the OOM killer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Baseline breaking point: ~50 concurrent users, ~42 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Apply Laravel Optimizations
&lt;/h2&gt;

&lt;p&gt;Now let's apply the standard production optimizations and retest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also ensure &lt;code&gt;APP_DEBUG=false&lt;/code&gt; and the OPcache extension is enabled (Deploynix enables this by default).&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 50 Concurrent Users (Optimized)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~48&lt;/li&gt;
&lt;li&gt;Median response time: 210ms&lt;/li&gt;
&lt;li&gt;P95 response time: 450ms&lt;/li&gt;
&lt;li&gt;P99 response time: 680ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~60%&lt;/li&gt;
&lt;li&gt;Memory usage: ~650MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A significant improvement. Caching configuration and routes eliminated the overhead of parsing those files on every request. Response times nearly halved, and CPU dropped from 78% to 60%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 100 Concurrent Users (Optimized)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~82&lt;/li&gt;
&lt;li&gt;Median response time: 480ms&lt;/li&gt;
&lt;li&gt;P95 response time: 1,100ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,800ms&lt;/li&gt;
&lt;li&gt;Error rate: 0.5%&lt;/li&gt;
&lt;li&gt;CPU usage: 92%&lt;/li&gt;
&lt;li&gt;Memory usage: ~780MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We nearly doubled the throughput before hitting the wall. The server can now handle 100 concurrent users with acceptable (though not great) response times. The few errors are sporadic timeouts during CPU spikes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimized breaking point: ~100 concurrent users, ~82 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: Switch to FrankenPHP (Octane)
&lt;/h2&gt;

&lt;p&gt;FrankenPHP keeps the Laravel application bootstrapped in memory between requests. Instead of loading the framework, service providers, and configuration on every request, the application boots once and handles subsequent requests from memory.&lt;/p&gt;

&lt;p&gt;Deploy with FrankenPHP through Deploynix's Octane driver selection, then retest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 100 Concurrent Users (FrankenPHP)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~145&lt;/li&gt;
&lt;li&gt;Median response time: 180ms&lt;/li&gt;
&lt;li&gt;P95 response time: 420ms&lt;/li&gt;
&lt;li&gt;P99 response time: 690ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: ~70%&lt;/li&gt;
&lt;li&gt;Memory usage: ~520MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a dramatic improvement. FrankenPHP eliminated the per-request framework bootstrap overhead, nearly doubling throughput again. Memory usage actually dropped because the application is loaded once instead of per-request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results at 200 Concurrent Users (FrankenPHP)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~180&lt;/li&gt;
&lt;li&gt;Median response time: 450ms&lt;/li&gt;
&lt;li&gt;P95 response time: 1,100ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,900ms&lt;/li&gt;
&lt;li&gt;Error rate: 0.8%&lt;/li&gt;
&lt;li&gt;CPU usage: 94%&lt;/li&gt;
&lt;li&gt;Memory usage: ~600MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 200 concurrent users, the single vCPU is the bottleneck. The application is still responsive but the CPU can't keep up. Throughput has started to plateau.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FrankenPHP breaking point: ~200 concurrent users, ~180 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4: Database Query Optimization
&lt;/h2&gt;

&lt;p&gt;Our test application had a few unoptimized queries — missing eager loading on the listing page and no indexes on commonly filtered columns. After fixing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before: N+1 problem&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// In view: $post-&amp;gt;author-&amp;gt;name (triggers N+1)&lt;/span&gt;

&lt;span class="c1"&gt;// After: Eager loaded&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And adding a composite index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'published_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results at 200 Concurrent Users (FrankenPHP + Query Optimization)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~210&lt;/li&gt;
&lt;li&gt;Median response time: 380ms&lt;/li&gt;
&lt;li&gt;P95 response time: 820ms&lt;/li&gt;
&lt;li&gt;P99 response time: 1,300ms&lt;/li&gt;
&lt;li&gt;Error rate: 0%&lt;/li&gt;
&lt;li&gt;CPU usage: 88%&lt;/li&gt;
&lt;li&gt;Memory usage: ~550MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Query optimization reduced database CPU time, which freed up CPU cycles for handling more requests. The errors at 200 users disappeared entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pushing to 300 Concurrent Users
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requests/second: ~225&lt;/li&gt;
&lt;li&gt;Median response time: 850ms&lt;/li&gt;
&lt;li&gt;P95 response time: 2,100ms&lt;/li&gt;
&lt;li&gt;P99 response time: 3,400ms&lt;/li&gt;
&lt;li&gt;Error rate: 1.5%&lt;/li&gt;
&lt;li&gt;CPU usage: 98%&lt;/li&gt;
&lt;li&gt;Memory usage: ~620MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CPU is fully saturated. We've found the ceiling for this $5 server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fully optimized breaking point: ~250 concurrent users, ~220 requests/second.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does This Mean in Real Traffic?
&lt;/h2&gt;

&lt;p&gt;Concurrent users is a load testing metric, not a real-world traffic metric. Real users don't send a request every second — they read pages, fill out forms, and navigate at human speed. The ratio depends on your application, but a common rule of thumb:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 concurrent user in load testing ≈ 10-30 real active users.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So our fully optimized $5 server handling 250 concurrent users translates to roughly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2,500 - 7,500 active users browsing simultaneously&lt;/li&gt;
&lt;li&gt;~220 requests/second sustained throughput&lt;/li&gt;
&lt;li&gt;~19 million requests/day at sustained peak&lt;/li&gt;
&lt;li&gt;~570 million requests/month at sustained peak&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most applications don't sustain peak traffic 24/7. If your peak is 4 hours/day, a $5 server can handle an application with 50,000-100,000 daily active users comfortably.&lt;/p&gt;

&lt;p&gt;That's a $5/month server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization Impact Summary
&lt;/h2&gt;

&lt;p&gt;Configuration&lt;/p&gt;

&lt;p&gt;Max RPS&lt;/p&gt;

&lt;p&gt;P95 at 50 Users&lt;/p&gt;

&lt;p&gt;Breaking Point&lt;/p&gt;

&lt;p&gt;Default (no caching)&lt;/p&gt;

&lt;p&gt;~42&lt;/p&gt;

&lt;p&gt;890ms&lt;/p&gt;

&lt;p&gt;~50 concurrent&lt;/p&gt;

&lt;p&gt;Laravel cache commands&lt;/p&gt;

&lt;p&gt;~82&lt;/p&gt;

&lt;p&gt;450ms&lt;/p&gt;

&lt;p&gt;~100 concurrent&lt;/p&gt;

&lt;p&gt;FrankenPHP (Octane)&lt;/p&gt;

&lt;p&gt;~180&lt;/p&gt;

&lt;p&gt;420ms&lt;/p&gt;

&lt;p&gt;~200 concurrent&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;~220&lt;/p&gt;

&lt;p&gt;320ms&lt;/p&gt;

&lt;p&gt;~250 concurrent&lt;/p&gt;

&lt;p&gt;Each optimization roughly doubled throughput. Combined, we went from 42 to 220 requests/second — a 5x improvement without spending an extra dollar on infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Scale Up
&lt;/h2&gt;

&lt;p&gt;Watch these signals on your Deploynix monitoring dashboard:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale up the server (vertical scaling) when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU consistently above 80% during normal traffic (not just spikes)&lt;/li&gt;
&lt;li&gt;Memory usage leaves less than 100MB free&lt;/li&gt;
&lt;li&gt;P95 response times exceed your acceptable threshold (usually 1-2 seconds)&lt;/li&gt;
&lt;li&gt;The OOM killer has been triggered (check &lt;code&gt;dmesg&lt;/code&gt; for killed processes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scale out to multiple servers (horizontal scaling) when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You've maxed out the largest single server your budget allows&lt;/li&gt;
&lt;li&gt;You need redundancy — a single server is a single point of failure&lt;/li&gt;
&lt;li&gt;Queue processing competes with web requests for CPU time&lt;/li&gt;
&lt;li&gt;Database operations need dedicated resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The typical scaling path on Deploynix:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a single App server ($5-12/month)&lt;/li&gt;
&lt;li&gt;Upgrade to a larger server when CPU/memory is consistently tight ($24-48/month)&lt;/li&gt;
&lt;li&gt;Separate the database to its own server when query performance suffers&lt;/li&gt;
&lt;li&gt;Add a dedicated worker server when queues can't keep up&lt;/li&gt;
&lt;li&gt;Add a load balancer with multiple web servers when a single web server can't handle peak traffic&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cost-Effective Optimization Checklist
&lt;/h2&gt;

&lt;p&gt;Before spending money on bigger servers, make sure you've applied these free optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;APP_DEBUG=false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Run &lt;code&gt;config:cache&lt;/code&gt;, &lt;code&gt;route:cache&lt;/code&gt;, &lt;code&gt;view:cache&lt;/code&gt;, &lt;code&gt;event:cache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] OPcache enabled with appropriate settings&lt;/li&gt;
&lt;li&gt;[ ] FrankenPHP, Swoole, or RoadRunner via Octane&lt;/li&gt;
&lt;li&gt;[ ] Eager loading on all relationship accesses (no N+1 queries)&lt;/li&gt;
&lt;li&gt;[ ] Database indexes on filtered and sorted columns&lt;/li&gt;
&lt;li&gt;[ ] Response caching for pages that don't change per-user&lt;/li&gt;
&lt;li&gt;[ ] Asset optimization (&lt;code&gt;npm run build&lt;/code&gt;, not &lt;code&gt;npm run dev&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] Queue heavy operations instead of processing inline&lt;/li&gt;
&lt;li&gt;[ ] Valkey for sessions, cache, and queues (not file/database drivers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is free and most take minutes to implement. Collectively, they can improve performance by 5-10x.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A $5 server can handle far more traffic than most developers expect. With Laravel's built-in optimization commands and an Octane driver like FrankenPHP, a single shared vCPU server sustains over 200 requests per second — enough for thousands of concurrent users.&lt;/p&gt;

&lt;p&gt;The key insight is that optimization should come before scaling. A $5 server with FrankenPHP and proper caching outperforms a $48 server running unoptimized PHP-FPM. Only when you've exhausted the free optimizations should you reach for the scaling lever.&lt;/p&gt;

&lt;p&gt;Deploynix makes both paths easy. Optimize your application and deploy it to a small server. Monitor the metrics. When the dashboard tells you it's time, scale up with a click. No premature infrastructure spending, no guessing — just data-driven decisions about when your application needs more resources.&lt;/p&gt;

&lt;p&gt;Start small. Optimize first. Scale when the numbers tell you to.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>loadtesting</category>
      <category>performance</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>The Definitive Guide to Laravel Deployment in 2026</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 21:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/the-definitive-guide-to-laravel-deployment-in-2026-307o</link>
      <guid>https://dev.to/deploynix/the-definitive-guide-to-laravel-deployment-in-2026-307o</guid>
      <description>&lt;p&gt;Deploying a Laravel application in 2026 looks nothing like it did five years ago. PHP-FPM is no longer the only game in town — FrankenPHP, Swoole, and RoadRunner have changed what's possible for performance. Server management platforms have matured to the point where a solo developer can run production infrastructure that would have required a DevOps team. And the Laravel ecosystem itself has introduced tools for monitoring, testing, and scaling that make the deployment story more complete than ever.&lt;/p&gt;

&lt;p&gt;This guide covers the full journey: from your local development environment to a production deployment that's monitored, backed up, and ready to scale. Whether you're deploying your first Laravel application or rearchitecting an existing one, this is your comprehensive reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Preparing Your Application for Production
&lt;/h2&gt;

&lt;p&gt;Before you touch a server, your application needs to be production-ready. This means more than "it works on my machine."&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Configuration
&lt;/h3&gt;

&lt;p&gt;Laravel uses &lt;code&gt;.env&lt;/code&gt; files to separate configuration from code. Your production environment needs different values from development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com

LOG_CHANNEL=stack
LOG_LEVEL=warning

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=your_app
DB_USERNAME=your_user
DB_PASSWORD=strong_random_password

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;APP_DEBUG=false&lt;/code&gt; prevents stack traces from leaking to users. Leaving this &lt;code&gt;true&lt;/code&gt; in production is a security vulnerability.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LOG_LEVEL=warning&lt;/code&gt; prevents your log files from filling the disk with debug information.&lt;/li&gt;
&lt;li&gt;Use Redis (or Valkey, which is Redis-compatible) for cache, sessions, and queues in production. The file and database drivers don't scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Optimizing for Production
&lt;/h3&gt;

&lt;p&gt;Laravel provides several Artisan commands that optimize performance by caching configuration, routes, and views:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands serialize your configuration, routes, views, and events into cached files that load faster than parsing the original sources on every request. Run them as part of your deployment process — never manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asset Compilation
&lt;/h3&gt;

&lt;p&gt;Build your frontend assets for production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs Vite in production mode, which minifies JavaScript and CSS, tree-shakes unused code, and generates versioned filenames for cache busting. Never run &lt;code&gt;npm run dev&lt;/code&gt; on a production server — development mode includes source maps and unminified code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Choosing Your Server Infrastructure
&lt;/h2&gt;

&lt;p&gt;Your infrastructure choices depend on your application's needs, traffic expectations, and budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Providers
&lt;/h3&gt;

&lt;p&gt;Deploynix supports provisioning servers on DigitalOcean, Vultr, Hetzner, Linode, AWS, and custom providers. Each has trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hetzner offers the best price-to-performance ratio for CPU and RAM. Ideal for budget-conscious deployments.&lt;/li&gt;
&lt;li&gt;DigitalOcean provides a polished experience with predictable pricing. Strong ecosystem of add-ons.&lt;/li&gt;
&lt;li&gt;Vultr offers competitive pricing with good global coverage.&lt;/li&gt;
&lt;li&gt;Linode (now Akamai) provides solid performance with straightforward pricing.&lt;/li&gt;
&lt;li&gt;AWS gives maximum flexibility and the broadest service catalog, but with higher complexity and cost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most Laravel applications starting out, a $5-12/month server on Hetzner or DigitalOcean is more than sufficient. You can always scale up later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Types
&lt;/h3&gt;

&lt;p&gt;A single App server handles everything for most applications: web serving, application processing, database, cache, and queue workers. As you grow, you split these responsibilities across specialized servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App Server:&lt;/strong&gt; Runs your Laravel application with Nginx, PHP, and queue workers. This is the all-in-one starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Web Server:&lt;/strong&gt; Handles HTTP requests and serves static files. In a scaled architecture, multiple web servers sit behind a load balancer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database Server:&lt;/strong&gt; Dedicated MySQL, MariaDB, or PostgreSQL instance. Isolating the database gives it dedicated CPU and RAM, improving query performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Server:&lt;/strong&gt; Dedicated Valkey (Redis-compatible) instance for caching, sessions, and queues. Separating cache from the application server prevents cache eviction during memory pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker Server:&lt;/strong&gt; Runs queue workers without competing with web requests for resources. Essential when you process heavy background jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load Balancer:&lt;/strong&gt; Distributes traffic across multiple web servers. Deploynix supports round robin, least connections, and IP hash load balancing methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing a PHP Runtime
&lt;/h3&gt;

&lt;p&gt;Traditional PHP-FPM remains reliable and well-understood, but modern runtimes offer significant performance improvements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FrankenPHP&lt;/strong&gt; is a modern PHP application server built on Caddy. It supports worker mode (keeping your application in memory between requests), HTTP/3, and Early Hints. It's becoming the default recommendation for new Laravel deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swoole&lt;/strong&gt; keeps your application bootstrapped in memory, eliminating the per-request overhead of loading the framework. It provides dramatic performance improvements but requires careful attention to memory leaks and static state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RoadRunner&lt;/strong&gt; is a Go-based application server that communicates with PHP workers over a binary protocol. It offers performance between FPM and Swoole with simpler debugging.&lt;/p&gt;

&lt;p&gt;Deploynix supports deploying with any of these Octane drivers. For most applications, FrankenPHP provides the best balance of performance and developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Provisioning and Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Provisioning with Deploynix
&lt;/h3&gt;

&lt;p&gt;Deploynix provisions servers by connecting to your cloud provider's API. You select a provider, region, server size, and type — Deploynix handles the rest: installing the OS, configuring the web server, setting up PHP, installing the database, configuring the firewall, and setting up SSL.&lt;/p&gt;

&lt;p&gt;The provisioning process installs everything your Laravel application needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx (configured for your chosen Octane driver or PHP-FPM)&lt;/li&gt;
&lt;li&gt;PHP 8.4 with essential extensions&lt;/li&gt;
&lt;li&gt;MySQL, MariaDB, or PostgreSQL&lt;/li&gt;
&lt;li&gt;Valkey for caching and queues&lt;/li&gt;
&lt;li&gt;Composer&lt;/li&gt;
&lt;li&gt;Node.js and npm for asset compilation&lt;/li&gt;
&lt;li&gt;Supervisor for queue workers and daemons&lt;/li&gt;
&lt;li&gt;UFW firewall with sensible defaults&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Connecting Your Git Repository
&lt;/h3&gt;

&lt;p&gt;Deploynix integrates with GitHub, GitLab, Bitbucket, and custom Git providers. Connect your repository, select a branch, and Deploynix configures the deployment pipeline.&lt;/p&gt;

&lt;p&gt;Your deployment workflow becomes: push to your branch, trigger a deploy (manually or automatically), and Deploynix handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSL Certificates
&lt;/h3&gt;

&lt;p&gt;Every production application needs HTTPS. Deploynix provisions SSL certificates automatically through Let's Encrypt when you add a domain to your site. For wildcard certificates, Deploynix supports DNS validation through Cloudflare, DigitalOcean, AWS Route 53, and Vultr DNS.&lt;/p&gt;

&lt;p&gt;Deploynix also provides vanity domains (&lt;code&gt;*.deploynix.cloud&lt;/code&gt;) with pre-configured wildcard SSL certificates — useful for staging environments and quick deployments before your custom domain is configured.&lt;/p&gt;

&lt;p&gt;Certificate renewal is handled automatically. Deploynix monitors certificate expiration and renews before they expire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: The Deployment Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Zero-Downtime Deployments
&lt;/h3&gt;

&lt;p&gt;Deploynix uses a release-based deployment strategy that eliminates downtime:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new release directory is created&lt;/li&gt;
&lt;li&gt;Your repository is cloned or updated&lt;/li&gt;
&lt;li&gt;Composer dependencies are installed&lt;/li&gt;
&lt;li&gt;npm dependencies are installed and assets are built&lt;/li&gt;
&lt;li&gt;Your deploy script runs (migrations, cache clearing, etc.)&lt;/li&gt;
&lt;li&gt;The symlink switches from the old release to the new one&lt;/li&gt;
&lt;li&gt;PHP-FPM/Octane workers are reloaded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The symlink switch is atomic — your application serves the old version until the exact moment it switches to the new one. There's no period where the application is partially deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Script
&lt;/h3&gt;

&lt;p&gt;Deploynix lets you define a custom deploy script that runs as part of the deployment process, inside the new release directory before the symlink swap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt; &lt;span class="nt"&gt;--optimize-autoloader&lt;/span&gt;
npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build
php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan queue:restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All steps complete before the new release goes live, ensuring users never see a partially prepared release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled Deployments
&lt;/h3&gt;

&lt;p&gt;Deploynix supports scheduling deployments for a future time. This is useful for coordinating releases with marketing launches or deploying during low-traffic windows. Scheduled deployments can be cancelled before their execution time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rollback
&lt;/h3&gt;

&lt;p&gt;When a deployment introduces a bug, Deploynix can roll back to any previous release instantly. The rollback switches the symlink back to a previous release directory — the same atomic operation as a forward deployment.&lt;/p&gt;

&lt;p&gt;Keep enough release directories to allow meaningful rollbacks. Deploynix retains configurable number of releases, automatically pruning older ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: Database Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Running Migrations
&lt;/h3&gt;

&lt;p&gt;Run migrations as part of your deploy script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--force&lt;/code&gt; flag is required in production. Without it, Artisan prompts for confirmation — which hangs in an automated deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Backups
&lt;/h3&gt;

&lt;p&gt;Deploynix supports automated database backups to AWS S3, DigitalOcean Spaces, Wasabi, and any S3-compatible storage. Configure backup frequency, retention period, and storage destination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backup strategy recommendations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hourly backups for applications with high write volume&lt;/li&gt;
&lt;li&gt;Daily backups for most applications&lt;/li&gt;
&lt;li&gt;Store backups in a different region than your primary server&lt;/li&gt;
&lt;li&gt;Test restoring from backups periodically — an untested backup is not a backup&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Optimization
&lt;/h3&gt;

&lt;p&gt;For production MySQL databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable the slow query log to identify performance problems&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; to 60-80% of available RAM on a dedicated database server&lt;/li&gt;
&lt;li&gt;Use connection pooling if your application creates many short-lived connections&lt;/li&gt;
&lt;li&gt;Monitor query performance through Deploynix's server metrics and Laravel Pulse&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 6: Queue Workers and Background Processing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Configuring Queue Workers
&lt;/h3&gt;

&lt;p&gt;Deploynix manages Supervisor configuration for your queue workers. Configure the number of worker processes, the queues they process, and restart policies.&lt;/p&gt;

&lt;p&gt;For most applications, start with 2-3 workers processing all queues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue: default,notifications,emails
Processes: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As your application grows, dedicate workers to specific queues based on priority:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker 1: payments (1 process, high priority)
Worker 2: default,notifications (3 processes)
Worker 3: exports,reports (2 processes, can be slow)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Daemon Processes
&lt;/h3&gt;

&lt;p&gt;Beyond queue workers, you might need long-running processes: WebSocket servers (Laravel Reverb), schedule runners, or custom daemons. Deploynix manages these through its daemon feature, which configures Supervisor to keep them running and restart them on failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 7: Monitoring and Alerting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server Monitoring
&lt;/h3&gt;

&lt;p&gt;Deploynix provides real-time monitoring for every managed server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU usage: Sustained high CPU indicates resource contention or runaway processes&lt;/li&gt;
&lt;li&gt;Memory usage: Track consumption trends to predict when you need to scale&lt;/li&gt;
&lt;li&gt;Disk usage: Running out of disk space causes cascading failures&lt;/li&gt;
&lt;li&gt;Network I/O: Unusual spikes might indicate a DDoS attack or a deployment pulling large dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Health Alerts
&lt;/h3&gt;

&lt;p&gt;Configure health alerts to notify you when metrics cross thresholds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU above 90% for more than 10 minutes&lt;/li&gt;
&lt;li&gt;Memory above 85%&lt;/li&gt;
&lt;li&gt;Disk usage above 80%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deploynix sends alerts through your configured notification channels, giving you time to investigate before users are affected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application-Level Monitoring
&lt;/h3&gt;

&lt;p&gt;Complement Deploynix's infrastructure monitoring with application-level tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Pulse for production performance trends, slow queries, and queue throughput&lt;/li&gt;
&lt;li&gt;Laravel Telescope (filtered) for capturing exceptions and failed jobs in production&lt;/li&gt;
&lt;li&gt;Custom health check endpoints that verify database, cache, queue, and external API connectivity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 8: Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Firewall Configuration
&lt;/h3&gt;

&lt;p&gt;Deploynix configures UFW (Uncomplicated Firewall) with sensible defaults: SSH (port 22), HTTP (port 80), and HTTPS (port 443) are open. Everything else is closed.&lt;/p&gt;

&lt;p&gt;Add custom rules for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database access from specific IP addresses (if you connect remotely)&lt;/li&gt;
&lt;li&gt;Application-specific ports (WebSockets on port 6001, etc.)&lt;/li&gt;
&lt;li&gt;Blocking known bad IP ranges&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SSH Security
&lt;/h3&gt;

&lt;p&gt;Deploynix provisions servers with key-based SSH authentication. Password authentication is disabled by default. Never re-enable it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Security Essentials
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keep PHP, Nginx, MySQL, and all system packages updated&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;composer audit&lt;/code&gt; regularly to check for vulnerable dependencies&lt;/li&gt;
&lt;li&gt;Use Sanctum for API authentication with granular token scopes&lt;/li&gt;
&lt;li&gt;Implement rate limiting on authentication and API endpoints&lt;/li&gt;
&lt;li&gt;Store sensitive values in environment variables, never in code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 9: Scaling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vertical Scaling (Scaling Up)
&lt;/h3&gt;

&lt;p&gt;The simplest scaling approach: give your server more resources. Deploynix makes this easy — resize your server through your cloud provider, and your application continues running with more CPU, RAM, and disk.&lt;/p&gt;

&lt;p&gt;Vertical scaling works until you hit the cloud provider's largest instance size or until cost becomes unreasonable. Most Laravel applications can serve thousands of concurrent users on a single well-configured server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Horizontal Scaling (Scaling Out)
&lt;/h3&gt;

&lt;p&gt;When a single server isn't enough, distribute the load across multiple servers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Separate your database onto a dedicated server. This is usually the first scaling step and provides the biggest impact.&lt;/li&gt;
&lt;li&gt;Separate your cache (Valkey) onto a dedicated server. This prevents cache eviction during application memory pressure.&lt;/li&gt;
&lt;li&gt;Add worker servers for queue processing. This keeps background jobs from competing with web requests for CPU.&lt;/li&gt;
&lt;li&gt;Add web servers behind a load balancer. Deploynix's load balancer supports round robin, least connections, and IP hash methods.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scaling Checklist
&lt;/h3&gt;

&lt;p&gt;Before scaling horizontally, ensure your application is stateless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sessions stored in Redis/Valkey (not file)&lt;/li&gt;
&lt;li&gt;Cache in Redis/Valkey (not file)&lt;/li&gt;
&lt;li&gt;File uploads on external storage (S3, not local disk)&lt;/li&gt;
&lt;li&gt;Queues using Redis/Valkey (not the database or sync driver)&lt;/li&gt;
&lt;li&gt;No local file writes that other servers need to access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your application writes anything to the local filesystem that other servers need to read, you'll have inconsistency across servers. Move shared state to external services before adding servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 10: The Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist for every new Laravel production deployment:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before First Deploy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;APP_DEBUG=false&lt;/code&gt; and &lt;code&gt;APP_ENV=production&lt;/code&gt; are set&lt;/li&gt;
&lt;li&gt;[ ] Database credentials use a strong, unique password&lt;/li&gt;
&lt;li&gt;[ ] Session, cache, and queue drivers are set to Redis/Valkey&lt;/li&gt;
&lt;li&gt;[ ] SSL certificate is provisioned&lt;/li&gt;
&lt;li&gt;[ ] Firewall rules are configured&lt;/li&gt;
&lt;li&gt;[ ] Backup schedule is configured and tested&lt;/li&gt;
&lt;li&gt;[ ] Health monitoring is enabled&lt;/li&gt;
&lt;li&gt;[ ] Queue workers are configured and running&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Every Deploy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Tests pass before deployment&lt;/li&gt;
&lt;li&gt;[ ] Migrations are tested against a copy of production data&lt;/li&gt;
&lt;li&gt;[ ] Assets are compiled for production&lt;/li&gt;
&lt;li&gt;[ ] Config, route, view, and event caches are refreshed&lt;/li&gt;
&lt;li&gt;[ ] Queue workers are restarted to pick up new code&lt;/li&gt;
&lt;li&gt;[ ] Deployment is verified by checking the application's health endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Post-Deploy Verification
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Application responds correctly to key user flows&lt;/li&gt;
&lt;li&gt;[ ] Queue workers are processing jobs&lt;/li&gt;
&lt;li&gt;[ ] No new errors appearing in logs&lt;/li&gt;
&lt;li&gt;[ ] Server metrics (CPU, memory, disk) are within normal ranges&lt;/li&gt;
&lt;li&gt;[ ] Scheduled tasks are running as expected&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Laravel deployment in 2026 is more powerful and more accessible than ever. Modern PHP runtimes like FrankenPHP deliver performance that would have been unthinkable a few years ago. Platforms like Deploynix eliminate the ops burden of server management, letting you focus on building your application.&lt;/p&gt;

&lt;p&gt;The key principles haven't changed: automate everything, monitor proactively, back up regularly, and scale intentionally. What has changed is the tooling — you no longer need a dedicated DevOps engineer to run production infrastructure. A solo developer with Deploynix can provision, deploy, monitor, and scale a Laravel application that serves millions of requests.&lt;/p&gt;

&lt;p&gt;Start simple. A single App server with zero-downtime deployments, automated backups, and health monitoring handles more traffic than most applications will ever see. Scale when the metrics tell you to, not when your anxiety does. And always, always test your backups.&lt;/p&gt;

&lt;p&gt;Your deployment pipeline is not a one-time setup — it's a living system that evolves with your application. Review it regularly, keep your dependencies updated, and invest in monitoring. The best deployment is the one you don't have to think about because everything is automated and every failure triggers an alert before your users notice.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>devops</category>
      <category>ssl</category>
      <category>scaling</category>
    </item>
    <item>
      <title>Finding and Fixing Slow Queries in Laravel Before They Hit Production</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 15:58:15 +0000</pubDate>
      <link>https://dev.to/deploynix/finding-and-fixing-slow-queries-in-laravel-before-they-hit-production-o72</link>
      <guid>https://dev.to/deploynix/finding-and-fixing-slow-queries-in-laravel-before-they-hit-production-o72</guid>
      <description>&lt;p&gt;Every Laravel application has slow queries. The question is whether you find them before your users do.&lt;/p&gt;

&lt;p&gt;A query that takes 50ms on your local machine with 100 rows might take 5 seconds in production with 500,000 rows. A polymorphic relationship that works fine in development becomes a full table scan when the &lt;code&gt;morphables&lt;/code&gt; table hits a million records. An Eloquent scope that chains three &lt;code&gt;whereHas&lt;/code&gt; calls generates a nested subquery monster that brings your database server to its knees during peak traffic.&lt;/p&gt;

&lt;p&gt;The good news: Laravel gives you excellent tools to find these queries before they cause outages. The better news: once you find them, the fixes are usually straightforward. This post walks through a complete workflow — from local detection with Debugbar and Telescope, through query analysis with EXPLAIN, to production monitoring with Deploynix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Detect Slow Queries in Development
&lt;/h2&gt;

&lt;p&gt;The first line of defense is catching slow queries during development. Two tools make this effortless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Debugbar
&lt;/h3&gt;

&lt;p&gt;Debugbar is the fastest way to spot query problems. Install it as a dev dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require barryvdh/laravel-debugbar &lt;span class="nt"&gt;--dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every page load shows a toolbar at the bottom of your browser with the number of queries executed and their total time. Click the queries tab to see each individual query with its execution time and the file/line that triggered it.&lt;/p&gt;

&lt;p&gt;The critical metric here isn't the total query time — it's the query count. If loading a dashboard page executes 47 queries, you have an N+1 problem regardless of how fast each individual query runs. Those 47 queries will become 4,700 queries when your data grows.&lt;/p&gt;

&lt;p&gt;Look for these red flags in Debugbar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than 10-15 queries per page load&lt;/li&gt;
&lt;li&gt;Duplicate queries (the same query executed multiple times with different IDs)&lt;/li&gt;
&lt;li&gt;Queries without a &lt;code&gt;WHERE&lt;/code&gt; clause on large tables&lt;/li&gt;
&lt;li&gt;Queries that don't appear in your controller or view code (often caused by lazy-loaded relationships in Blade templates)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Laravel Telescope
&lt;/h3&gt;

&lt;p&gt;Telescope records every query your application executes and flags slow ones automatically. After installation, visit &lt;code&gt;/telescope/queries&lt;/code&gt; to see queries sorted by duration.&lt;/p&gt;

&lt;p&gt;Telescope is particularly useful for catching slow queries in API endpoints and queued jobs — places where Debugbar can't reach.&lt;/p&gt;

&lt;p&gt;Configure the slow query threshold in your Telescope service provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Telescope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IncomingEntry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;EntryType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slow'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slow-query'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default slow query threshold is 100ms, which is reasonable for most applications. Any query that takes longer than that deserves investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Analyze with EXPLAIN
&lt;/h2&gt;

&lt;p&gt;Once you've identified a slow query, the next step is understanding why it's slow. MySQL's &lt;code&gt;EXPLAIN&lt;/code&gt; command tells you exactly how the database plans to execute a query.&lt;/p&gt;

&lt;p&gt;Take the raw SQL from Debugbar or Telescope and run it with &lt;code&gt;EXPLAIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output shows you the query execution plan. Here's what to look for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;type column:&lt;/strong&gt; This is the most important indicator.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ALL&lt;/code&gt; means a full table scan. On a large table, this is almost always the problem.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index&lt;/code&gt; means a full index scan — better than ALL, but still reads every row in the index.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;range&lt;/code&gt; means the query uses an index to select a range of rows. This is usually acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ref&lt;/code&gt; means the query uses an index to look up matching rows. This is good.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eq_ref&lt;/code&gt; means a unique index lookup. This is optimal.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;const&lt;/code&gt; means the query matches at most one row. This is the fastest possible lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;rows column:&lt;/strong&gt; The estimated number of rows MySQL will examine. If this number is close to the total row count of the table, you're doing a full scan even if the &lt;code&gt;type&lt;/code&gt; column says otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extra column:&lt;/strong&gt; Watch for "Using filesort" (MySQL has to sort results without an index) and "Using temporary" (MySQL creates a temporary table). Both indicate potential performance problems on large datasets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running EXPLAIN from Laravel
&lt;/h3&gt;

&lt;p&gt;You can use Eloquent's &lt;code&gt;toSql()&lt;/code&gt; method to get the raw SQL, then run EXPLAIN through a database client. Or use the query builder's &lt;code&gt;explain()&lt;/code&gt; method directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the EXPLAIN output as a collection you can inspect or log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Common Slow Query Patterns and Fixes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Missing Index on WHERE Columns
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index on &lt;code&gt;status&lt;/code&gt;, MySQL scans every row in the orders table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a migration&lt;/span&gt;
&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Composite WHERE Without a Composite Index
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with individual indexes on &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt;, MySQL can only use one index per table in a simple query. It picks the most selective one and scans the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a composite index that covers all three columns in the correct order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Column order matters. Put equality conditions (&lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;) before range conditions (&lt;code&gt;created_at&lt;/code&gt;) and sort columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Using &lt;code&gt;whereHas&lt;/code&gt; with Complex Conditions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whereHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rating'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;whereHas&lt;/code&gt; generates a correlated subquery that MySQL executes once per row in the outer table. On large tables, this is devastating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use a &lt;code&gt;JOIN&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.*'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'products.id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'reviews.product_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'reviews.rating'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or denormalize the data by adding a &lt;code&gt;reviews_avg_rating&lt;/code&gt; column to the products table and updating it through an observer or event listener.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 4: Sorting Without an Index
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderByDesc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'views_count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there's no index covering both the &lt;code&gt;WHERE&lt;/code&gt; and &lt;code&gt;ORDER BY&lt;/code&gt; columns, MySQL fetches all matching rows, then sorts them in memory. The "Using filesort" flag in EXPLAIN confirms this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'posts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'published'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'views_count'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 5: Selecting All Columns When You Need Few
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Then in a Blade view:&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endforeach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're loading every column (including potentially large text fields, JSON columns, and binary data) when you only need the name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces memory usage and network transfer between the database and application server. On tables with large &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;json&lt;/code&gt; columns, the difference can be dramatic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 6: Unbounded Queries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a user has 50,000 activity log entries, this loads all 50,000 into memory at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use pagination or chunking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For display&lt;/span&gt;
&lt;span class="nv"&gt;$logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For processing&lt;/span&gt;
&lt;span class="nc"&gt;ActivityLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chunkById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$logs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Process 500 at a time&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Prevent Regressions with Automated Detection
&lt;/h2&gt;

&lt;p&gt;Laravel 12 lets you prevent lazy loading application-wide, turning N+1 problems into exceptions during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bootstrap/app.php or a service provider&lt;/span&gt;
&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, you don't want exceptions crashing pages. Instead, log lazy loading violations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handleLazyLoadingViolationUsing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Lazy loading [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] on model [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$class&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a log trail of N+1 problems to fix without disrupting users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Monitor Queries in Production with Deploynix
&lt;/h2&gt;

&lt;p&gt;Development testing only goes so far. Your local database has a fraction of the data, the server has different resources, and traffic patterns create contention you can't simulate easily.&lt;/p&gt;

&lt;p&gt;Deploynix's server monitoring tracks your MySQL process's CPU and memory usage in real-time. When a slow query starts consuming resources, you'll see it reflected in the database server's metrics before it causes user-facing issues.&lt;/p&gt;

&lt;p&gt;Set up health alerts on your database server to notify you when MySQL CPU usage exceeds 80% for more than 5 minutes. This gives you a window to investigate before the query brings the server down.&lt;/p&gt;

&lt;p&gt;Combine Deploynix's infrastructure monitoring with Laravel Pulse's slow query tracking in production. Pulse tells you which queries are slow. Deploynix tells you whether those slow queries are actually impacting server resources. A query that takes 500ms but runs twice a day is a low priority fix. A query that takes 200ms but runs 10,000 times per hour is an emergency — and Deploynix's CPU graphs will make that obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Establish a Query Performance Budget
&lt;/h2&gt;

&lt;p&gt;Set concrete thresholds for your team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page load queries: Maximum 15 queries per page load&lt;/li&gt;
&lt;li&gt;API endpoint queries: Maximum 10 queries per request&lt;/li&gt;
&lt;li&gt;Individual query time: No single query over 100ms in development&lt;/li&gt;
&lt;li&gt;Total query time per request: Under 50ms for 95% of requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enforce these through automated testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'loads the dashboard in under 15 queries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertOk&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test fails when someone introduces an N+1 problem on the dashboard, catching it before code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Slow queries are inevitable as your application grows. What's not inevitable is letting them reach production undetected.&lt;/p&gt;

&lt;p&gt;Use Debugbar and Telescope to catch problems during development. Analyze suspicious queries with EXPLAIN to understand exactly why they're slow. Apply the common fixes — proper indexing, eager loading, query restructuring. Prevent regressions with &lt;code&gt;preventLazyLoading&lt;/code&gt; and query count tests. And monitor production with Deploynix to catch the queries that only become problems at scale.&lt;/p&gt;

&lt;p&gt;The developers who deploy confidently aren't the ones who write perfect queries. They're the ones who have systems in place to find and fix imperfect queries before users feel the pain.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>databasequeries</category>
      <category>laravel</category>
      <category>mysql</category>
    </item>
    <item>
      <title>N+1 Query Detection and Prevention in Laravel Production Apps</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 09:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/n1-query-detection-and-prevention-in-laravel-production-apps-2lg</link>
      <guid>https://dev.to/deploynix/n1-query-detection-and-prevention-in-laravel-production-apps-2lg</guid>
      <description>&lt;p&gt;The N+1 query problem is the most common performance issue in Laravel applications, and it's the easiest to introduce accidentally. A developer adds &lt;code&gt;$post-&amp;gt;author-&amp;gt;name&lt;/code&gt; in a Blade template, and suddenly a page that loaded 10 posts now executes 11 database queries instead of 2. Scale that to 100 posts and you have 101 queries. Scale to a thousand and your database server is on fire.&lt;/p&gt;

&lt;p&gt;What makes N+1 problems particularly insidious is that they're invisible during development. Your local database with 20 records responds instantly whether you execute 1 query or 100. The problem only becomes apparent in production where tables have millions of rows, query latency is higher, and database connections are shared across hundreds of concurrent requests.&lt;/p&gt;

&lt;p&gt;This post covers everything you need to detect N+1 queries, prevent them from being introduced, and monitor for them in production Laravel applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the N+1 Problem
&lt;/h2&gt;

&lt;p&gt;An N+1 query occurs when you load a collection of N models and then access a relationship on each model individually, triggering a separate query for each one.&lt;/p&gt;

&lt;p&gt;Here's the classic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Controller&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 1 query: SELECT * FROM posts&lt;/span&gt;

&lt;span class="c1"&gt;// Blade template&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="c1"&gt;// N queries: SELECT * FROM users WHERE id = ?&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endforeach&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first query loads all posts. Then, for each post, Laravel lazy-loads the &lt;code&gt;author&lt;/code&gt; relationship with a separate query. With 50 posts, that's 51 queries total.&lt;/p&gt;

&lt;p&gt;The fix is eager loading — tell Laravel to load all authors in a single query upfront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 2 queries total&lt;/span&gt;
&lt;span class="c1"&gt;// Query 1: SELECT * FROM posts&lt;/span&gt;
&lt;span class="c1"&gt;// Query 2: SELECT * FROM users WHERE id IN (1, 2, 3, ...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two queries instead of 51. The concept is simple. The challenge is applying it consistently across a growing codebase where relationships are accessed in controllers, views, components, API resources, and queued jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention: Laravel's preventLazyLoading
&lt;/h2&gt;

&lt;p&gt;Laravel provides a built-in mechanism to catch N+1 problems during development. When enabled, any lazy-loaded relationship throws an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In a service provider's boot method&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isProduction&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the &lt;code&gt;$post-&amp;gt;author-&amp;gt;name&lt;/code&gt; call without eager loading throws a &lt;code&gt;LazyLoadingViolationException&lt;/code&gt; instead of silently executing an extra query. This forces developers to explicitly eager-load every relationship they access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Violations in Production
&lt;/h3&gt;

&lt;p&gt;You don't want exceptions crashing pages in production. Instead, log violations so you can fix them without disrupting users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventLazyLoading&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handleLazyLoadingViolationUsing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"N+1 detected: lazy loading [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$relation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] on ["&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;get_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach gives you the best of both worlds: hard failures in development that force immediate fixes, and silent logging in production that reveals problems you missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  When preventLazyLoading Gets in the Way
&lt;/h3&gt;

&lt;p&gt;There are legitimate cases where lazy loading is acceptable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Artisan commands that process a single model and access one relationship&lt;/li&gt;
&lt;li&gt;Queue jobs that operate on a known small dataset&lt;/li&gt;
&lt;li&gt;Tinker sessions during debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can selectively allow lazy loading on specific models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On a specific model&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Setting&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Settings table is tiny; lazy loading is fine&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nv"&gt;$preventLazyLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or disable it temporarily in specific contexts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withoutPreventing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Lazy loading is allowed in this closure&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Eager Loading Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Eager Loading
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;with()&lt;/code&gt; method accepts a single relationship or an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nested Eager Loading
&lt;/h3&gt;

&lt;p&gt;Load relationships of relationships using dot notation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'comments.author'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'author.profile'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Loads: posts, comments, comment authors, post authors, author profiles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Constrained Eager Loading
&lt;/h3&gt;

&lt;p&gt;Filter or limit the eager-loaded relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'comments'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Laravel 12 supports &lt;code&gt;limit()&lt;/code&gt; on eager loads natively — no external packages needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional Eager Loading
&lt;/h3&gt;

&lt;p&gt;Load relationships only when certain conditions are met:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$includeComments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$includeAuthor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Eager Loading on Existing Collections
&lt;/h3&gt;

&lt;p&gt;If you already have a collection and need to load a relationship after the fact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Later, you realize you need authors&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or load only if not already loaded&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadMissing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;loadMissing()&lt;/code&gt; is particularly useful in deep call stacks where you're not sure if a relationship was already eager-loaded upstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Default Eager Loading on Models
&lt;/h3&gt;

&lt;p&gt;If a relationship is always needed when a model is loaded, define it as a default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$with&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this sparingly. It means every &lt;code&gt;Post::all()&lt;/code&gt;, &lt;code&gt;Post::find()&lt;/code&gt;, and &lt;code&gt;Post::where(...)&lt;/code&gt; call loads the author relationship, even when it's not needed. This can turn a simple count query into a heavy join operation.&lt;/p&gt;

&lt;p&gt;A better approach is to define scopes for common access patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scopeWithFeedRelations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Builder&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Builder&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tags'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'comments'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withFeedRelations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detecting N+1 in Existing Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query Counting in Tests
&lt;/h3&gt;

&lt;p&gt;Write tests that enforce query budgets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'loads the post index without N+1 queries'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;actingAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$author&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/posts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertOk&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If loading 20 posts requires more than 10 queries, something is lazy loading when it shouldn't be. This test catches regressions when someone adds a new relationship access in the view without updating the controller's eager loading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Laravel Debugbar
&lt;/h3&gt;

&lt;p&gt;Debugbar shows duplicate queries in its queries tab. If you see &lt;code&gt;SELECT * FROM users WHERE id = ?&lt;/code&gt; repeated 20 times with different IDs, that's an N+1 problem on the &lt;code&gt;author&lt;/code&gt; relationship.&lt;/p&gt;

&lt;p&gt;Debugbar also shows the file and line where each query was triggered. This makes it trivial to trace the lazy load back to the specific Blade template line or component that caused it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Telescope's Query Tab
&lt;/h3&gt;

&lt;p&gt;Telescope groups queries by request. Look for requests with high query counts — anything above 20-30 queries per request warrants investigation. Sort by query count to find the worst offenders first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common N+1 Traps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trap 1: Blade Components That Access Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/View/Components/PostCard.php&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'components.post-card'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// components/post-card.blade.php&lt;/span&gt;



&lt;span class="c1"&gt;## {{ $post-&amp;gt;title }}&lt;/span&gt;

    &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;  &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nc"&gt;N&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;eager&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component looks harmless. The N+1 is hidden because the relationship access happens in the view, far from the controller that loaded the posts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Ensure the parent view or controller eager-loads the relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trap 2: API Resources with Nested Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JsonResource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'author'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// N+1&lt;/span&gt;
            &lt;span class="s1"&gt;'comments_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// N+1 AND loads all comments&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Eager-load in the controller and use &lt;code&gt;withCount&lt;/code&gt; for counts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'author'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'comments'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// In the resource:&lt;/span&gt;
&lt;span class="s1"&gt;'comments_count'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;comments_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Uses the aggregate, no extra query&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trap 3: Accessors That Touch Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;totalWithTax&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Attribute&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taxRate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// Accessing $this-&amp;gt;taxRate triggers a lazy load&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessors are called during serialization, in Blade templates, and in API resources. Each call triggers the relationship query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Eager-load &lt;code&gt;taxRate&lt;/code&gt; wherever you use this accessor, or cache the relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;totalWithTax&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Attribute&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Attribute&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;relationLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'taxRate'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;taxRate&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$rate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trap 4: Queued Jobs Processing Collections
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendWeeklyDigest&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Each of these triggers a query&lt;/span&gt;
                &lt;span class="nv"&gt;$recentPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WeeklyDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recentPosts&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'subscriptions.posts'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;subWeek&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$recentPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;subscriptions&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WeeklyDigest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recentPosts&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring in Production
&lt;/h2&gt;

&lt;p&gt;Even with &lt;code&gt;preventLazyLoading&lt;/code&gt; and test coverage, N+1 problems can slip through. Monitor query counts in production using multiple layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Pulse
&lt;/h3&gt;

&lt;p&gt;Pulse tracks slow queries and high-frequency queries in production. A query that appears hundreds of times per minute with slight parameter variations is likely an N+1 problem. Check Pulse's slow queries dashboard regularly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploynix Server Monitoring
&lt;/h3&gt;

&lt;p&gt;On Deploynix, monitor your database server's CPU and connection count. A sudden increase in active connections often correlates with N+1 queries — each lazy-loaded query opens and closes a connection (or holds one from the pool). If your connection count spikes during peak traffic, investigate the most active endpoints for N+1 issues.&lt;/p&gt;

&lt;p&gt;Deploynix's real-time monitoring dashboard shows MySQL process CPU usage alongside your application server's metrics. When the database CPU spikes but the application CPU stays flat, the cause is almost always excessive queries — and N+1 is the most likely culprit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Middleware for Query Counting
&lt;/h3&gt;

&lt;p&gt;Add middleware that logs requests exceeding a query threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueryCountMiddleware&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"High query count: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$queryCount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; queries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fullUrl&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s1"&gt;'method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it globally in development or for specific route groups in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;N+1 queries are the single most impactful performance problem in Laravel applications, and they're entirely preventable. Enable &lt;code&gt;preventLazyLoading&lt;/code&gt; to catch them during development. Write query-count tests to prevent regressions. Use &lt;code&gt;with()&lt;/code&gt;, &lt;code&gt;loadMissing()&lt;/code&gt;, and &lt;code&gt;withCount()&lt;/code&gt; consistently.&lt;/p&gt;

&lt;p&gt;In production, monitor query patterns through Pulse and watch for database CPU spikes on your Deploynix dashboard. The combination of prevention at the code level and detection at the infrastructure level means N+1 problems get caught before they become outages.&lt;/p&gt;

&lt;p&gt;The goal isn't zero lazy loading — it's intentional lazy loading. Every relationship access should be a deliberate choice, not an accidental database round trip hiding in a Blade component three levels deep.&lt;/p&gt;

</description>
      <category>n1</category>
      <category>eloquent</category>
      <category>laravelperformance</category>
      <category>database</category>
    </item>
    <item>
      <title>Laravel Telescope vs. Pulse vs. Deploynix Monitoring: Which Do You Need?</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Sat, 25 Apr 2026 00:32:04 +0000</pubDate>
      <link>https://dev.to/deploynix/laravel-telescope-vs-pulse-vs-deploynix-monitoring-which-do-you-need-5dal</link>
      <guid>https://dev.to/deploynix/laravel-telescope-vs-pulse-vs-deploynix-monitoring-which-do-you-need-5dal</guid>
      <description>&lt;p&gt;Laravel developers in 2026 have three mature monitoring options, each built for a different layer of the stack. Telescope gives you a microscope into individual requests. Pulse gives you a dashboard for application-level trends. Deploynix monitoring gives you infrastructure-level visibility with health alerting.&lt;/p&gt;

&lt;p&gt;The question isn't which one is best. The question is which combination you need — because they're not competitors. They're complementary tools that, when used together, give you complete observability from the server hardware up through individual Eloquent queries.&lt;/p&gt;

&lt;p&gt;This post breaks down what each tool does, where they overlap, how they differ in performance overhead, and which setup makes sense based on your application's size and stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Tool Actually Does
&lt;/h2&gt;

&lt;p&gt;Before comparing features, it helps to understand the fundamental design philosophy behind each tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Telescope: The Development Debugger
&lt;/h3&gt;

&lt;p&gt;Telescope is a debugging assistant. It records every request, exception, database query, queued job, mail, notification, cache operation, and scheduled task that your application processes. You browse this data through a web dashboard that lets you inspect individual entries in detail.&lt;/p&gt;

&lt;p&gt;Telescope answers questions like: What exact SQL queries did this request execute? What was the payload of this queued job that failed? Why did this notification take 3 seconds to send?&lt;/p&gt;

&lt;p&gt;It shines during development and staging. In production, its recording overhead and storage requirements make it impractical for high-traffic applications unless heavily filtered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel Pulse: The Application Dashboard
&lt;/h3&gt;

&lt;p&gt;Pulse is an application performance monitoring tool. Instead of recording every individual event, it aggregates data into trends and surfaces the metrics that matter: slowest requests, most frequent queries, queue throughput, cache hit rates, and active user counts.&lt;/p&gt;

&lt;p&gt;Pulse answers questions like: Which endpoints are consistently slow this week? Are my queue workers keeping up with job volume? What's my cache hit ratio trending toward?&lt;/p&gt;

&lt;p&gt;It's designed for production use. Its aggregation approach keeps storage requirements manageable, and its sampling capabilities let you control the performance overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploynix Monitoring: The Infrastructure Layer
&lt;/h3&gt;

&lt;p&gt;Deploynix monitoring operates at the server and site level. It tracks CPU usage, memory consumption, disk I/O, network throughput, and disk space across all your managed servers. It provides real-time alerts when metrics cross thresholds you define.&lt;/p&gt;

&lt;p&gt;Deploynix answers questions like: Is the server running out of memory? Has CPU usage been sustained above 90% for the last hour? Is the application responding to HTTP requests? When did disk usage cross the warning threshold?&lt;/p&gt;

&lt;p&gt;It doesn't know about your Laravel application's internals — it doesn't see individual queries or jobs. But it sees everything happening at the operating system level, which is exactly the layer you need visibility into when your application monitoring tools report that "everything looks fine" but users are experiencing slowness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;p&gt;Here's a direct comparison of what each tool monitors:&lt;/p&gt;

&lt;p&gt;Capability&lt;/p&gt;

&lt;p&gt;Telescope&lt;/p&gt;

&lt;p&gt;Pulse&lt;/p&gt;

&lt;p&gt;Deploynix&lt;/p&gt;

&lt;p&gt;Individual request inspection&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Request performance trends&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Database query logging&lt;/p&gt;

&lt;p&gt;Yes (all)&lt;/p&gt;

&lt;p&gt;Yes (slow)&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Exception tracking&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Queue job inspection&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Cache operations&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;CPU/Memory monitoring&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Disk space alerts&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Server health monitoring&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Real-time server metrics&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;Mail/notification logging&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Model event tracking&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Gate/policy inspection&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Health check alerting&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;User activity tracking&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Performance percentiles&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;Yes&lt;/p&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;p&gt;The overlap is minimal. Telescope and Pulse both track queries and exceptions, but in fundamentally different ways. Telescope records every single query for later inspection. Pulse aggregates query performance into trends and highlights outliers. Deploynix doesn't touch queries at all — it watches the MySQL process's resource consumption from the OS level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Overhead
&lt;/h2&gt;

&lt;p&gt;This is where the differences matter most in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telescope's Overhead
&lt;/h3&gt;

&lt;p&gt;Telescope's default configuration records everything. Every request generates multiple database inserts for the request entry, query entries, model entries, and any other watchers you have enabled. On a high-traffic application, this creates significant database load.&lt;/p&gt;

&lt;p&gt;You can mitigate this with selective recording:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/Providers/TelescopeServiceProvider.php&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Telescope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IncomingEntry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isReportableException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isSlowQuery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
               &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isScheduledTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even with filtering, Telescope adds measurable overhead per request because the filtering logic itself runs on every request. For applications handling hundreds of requests per second, this matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; Enable Telescope fully in local and staging environments. In production, either disable it entirely or filter aggressively to only capture failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulse's Overhead
&lt;/h3&gt;

&lt;p&gt;Pulse was designed for production from day one. It uses an aggregation pipeline that batches metrics before writing them to the database, reducing write frequency dramatically compared to Telescope.&lt;/p&gt;

&lt;p&gt;Pulse also supports sampling — you can tell it to only record every Nth request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/pulse.php&lt;/span&gt;
&lt;span class="s1"&gt;'sample_rate'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Record 10% of requests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a 10% sample rate, Pulse gives you statistically meaningful trend data with a fraction of the overhead. The aggregated storage is also much smaller — Pulse automatically prunes old data based on configurable retention periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; Run Pulse in production with sampling enabled. A 10-25% sample rate provides excellent trend data for most applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploynix's Overhead
&lt;/h3&gt;

&lt;p&gt;Deploynix monitoring runs at the OS level, completely outside your Laravel application. It uses lightweight system agents that collect metrics from &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;sysstat&lt;/code&gt;, and similar system interfaces. Your application doesn't execute a single extra line of PHP.&lt;/p&gt;

&lt;p&gt;The HTTP health check adds one request every 30-60 seconds (configurable), which is negligible on any application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; Always enable Deploynix monitoring. There's no meaningful overhead to your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Setups by Application Size
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Solo Developer / Side Project
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring only.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you're launching an MVP or running a small project, you don't need application-level monitoring yet. Deploynix's built-in server monitoring and health alerts will tell you if something goes catastrophically wrong. Use Telescope locally during development to debug issues, but don't bother installing it in production.&lt;/p&gt;

&lt;p&gt;At this stage, your time is better spent building features than configuring monitoring dashboards. Deploynix handles the infrastructure concerns so you don't have to SSH into servers to check disk space or CPU usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Growing Application (1,000+ Daily Users)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring + Pulse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once your application serves real traffic consistently, you need trend data. Pulse shows you which endpoints are slowing down, whether your cache strategy is working, and if queue processing is keeping pace with demand.&lt;/p&gt;

&lt;p&gt;Install Pulse with a 25% sample rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'sample_rate'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you solid performance data without measurable overhead. Combined with Deploynix's server metrics, you can correlate application slowness (Pulse) with infrastructure bottlenecks (Deploynix).&lt;/p&gt;

&lt;p&gt;For example, Pulse might show that your &lt;code&gt;/api/reports&lt;/code&gt; endpoint's P95 response time jumped from 200ms to 1,200ms. Deploynix's server monitoring might simultaneously show that MySQL's CPU usage spiked to 95%. Now you know the problem is a database query issue on that endpoint, not a code regression or memory leak.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Application (10,000+ Daily Users)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring + Pulse + Telescope (filtered).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this scale, you need all three layers. Pulse gives you the trends. Deploynix gives you infrastructure alerting. And Telescope — heavily filtered to only capture failures and slow queries — gives you the detailed forensics you need when something goes wrong.&lt;/p&gt;

&lt;p&gt;Configure Telescope to only record problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Telescope&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IncomingEntry&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isReportableException&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
           &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
           &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isFailedJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
           &lt;span class="nv"&gt;$entry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;EntryType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SLOW_QUERY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Pulse's dashboard shows a spike in slow requests, switch to Telescope to inspect the individual queries and request payloads that caused it. When Deploynix alerts you to high memory usage, check Pulse to see if a particular endpoint is processing larger payloads than usual.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Traffic Application (100,000+ Daily Users)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use: Deploynix monitoring + Pulse (low sample rate) + dedicated APM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At very high traffic volumes, consider adding a dedicated APM service (New Relic, Datadog, or similar) alongside Pulse and Deploynix. Telescope's database storage model doesn't scale well at this level even with aggressive filtering.&lt;/p&gt;

&lt;p&gt;Run Pulse at a 5-10% sample rate for trend data. Use Deploynix for infrastructure monitoring and alerting. Let your APM handle the detailed request tracing that Telescope would otherwise provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Them Work Together
&lt;/h2&gt;

&lt;p&gt;The real power emerges when you use these tools as a unified observability stack rather than isolated dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Triage workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploynix alerts you that a server's CPU has been above 90% for 10 minutes.&lt;/li&gt;
&lt;li&gt;Open Pulse to check which endpoints or queues are consuming the most resources.&lt;/li&gt;
&lt;li&gt;If Pulse points to a specific endpoint, open Telescope to inspect recent requests to that endpoint and find the problematic query or operation.&lt;/li&gt;
&lt;li&gt;Fix the issue, deploy through Deploynix, and watch all three dashboards confirm the resolution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Performance investigation workflow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pulse shows P95 response time increasing over the past week.&lt;/li&gt;
&lt;li&gt;Drill into Pulse's slow queries tab to identify which queries are degrading.&lt;/li&gt;
&lt;li&gt;Check Deploynix's server metrics to see if the database server's disk I/O is saturated.&lt;/li&gt;
&lt;li&gt;If disk I/O is the bottleneck, use Deploynix to scale up the database server or add read replicas.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuration Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Don't Run Telescope and Pulse on the Same Database
&lt;/h3&gt;

&lt;p&gt;Both tools write monitoring data to your database. In production, configure them to use a separate database connection to avoid their writes competing with your application's queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/pulse.php&lt;/span&gt;
&lt;span class="s1"&gt;'storage'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'connection'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pulse'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/database.php&lt;/span&gt;
&lt;span class="s1"&gt;'pulse'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'host'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PULSE_DB_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'database'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PULSE_DB_DATABASE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pulse'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set Retention Policies
&lt;/h3&gt;

&lt;p&gt;Both Telescope and Pulse accumulate data. Configure pruning to prevent storage from growing unbounded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Telescope: prune entries older than 48 hours&lt;/span&gt;
&lt;span class="nv"&gt;$schedule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'telescope:prune --hours=48'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;daily&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Pulse: configure in config/pulse.php&lt;/span&gt;
&lt;span class="s1"&gt;'retain'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'entries'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 7 days in minutes&lt;/span&gt;
    &lt;span class="s1"&gt;'aggregates'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 90 days in minutes&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use Deploynix's Monitoring for Alerting
&lt;/h3&gt;

&lt;p&gt;Neither Telescope nor Pulse has built-in alerting. They're dashboards you check, not systems that notify you. Deploynix fills this gap with health alerts that trigger notifications when your servers or sites enter unhealthy states.&lt;/p&gt;

&lt;p&gt;Pair Deploynix's server-level health alerts with an external uptime monitoring service that polls your custom health check endpoint, and you get alerts that combine infrastructure metrics with application health data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Telescope, Pulse, and Deploynix monitoring aren't competing solutions — they're complementary layers of a complete observability stack. Telescope gives you the microscope for debugging individual issues. Pulse gives you the dashboard for tracking application performance trends. Deploynix gives you the infrastructure monitoring and alerting foundation that the other two are built on top of.&lt;/p&gt;

&lt;p&gt;Start with Deploynix monitoring on every server. Add Pulse when you need application-level trend data. Layer in Telescope when you need forensic debugging capabilities in production. Together, they give you visibility from the CPU cores up through individual database queries — and the confidence that you'll know about problems before your users do.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>monitoring</category>
      <category>telescope</category>
      <category>observability</category>
    </item>
    <item>
      <title>Custom Health Check Endpoints for Laravel: Beyond 200 OK</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Thu, 23 Apr 2026 10:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/custom-health-check-endpoints-for-laravel-beyond-200-ok-44n9</link>
      <guid>https://dev.to/deploynix/custom-health-check-endpoints-for-laravel-beyond-200-ok-44n9</guid>
      <description>&lt;p&gt;Every Laravel application ships with a default route that returns a 200 OK response. It tells you the web server is running. It tells you PHP is alive. And it tells you absolutely nothing about whether your application is actually healthy.&lt;/p&gt;

&lt;p&gt;A returning customer can't complete checkout if your Redis connection dropped. Your API consumers get cryptic 500 errors when the database connection pool is exhausted. Your queue workers silently stopped processing jobs three hours ago, and nobody noticed because your uptime monitor keeps reporting "all clear."&lt;/p&gt;

&lt;p&gt;Sound familiar? The problem is that most health checks are superficial. They verify the application process is running without confirming the application is actually working. In this post, we'll build a comprehensive health check endpoint that validates every critical dependency your Laravel application relies on — and integrate it with Deploynix's monitoring to get alerts before your users notice problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Simple 200 OK Isn't Enough
&lt;/h2&gt;

&lt;p&gt;A basic health check route like this is dangerously misleading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route will return 200 even when your database is down, your cache server is unreachable, your queue workers have crashed, your disk is 99% full, and your payment gateway API is timing out.&lt;/p&gt;

&lt;p&gt;A proper health check should verify the entire dependency chain your application needs to function correctly. When any critical component fails, your monitoring system should know about it immediately — not when a customer opens a support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing Your Health Check Architecture
&lt;/h2&gt;

&lt;p&gt;Before writing code, think about what "healthy" means for your specific application. Most Laravel applications depend on some combination of database connectivity, cache availability, queue processing, filesystem access, and external API reachability.&lt;/p&gt;

&lt;p&gt;We'll build a health check system with two tiers: a lightweight liveness probe that confirms the application process is running, and a comprehensive readiness probe that validates all dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// routes/api.php&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health/live'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HealthCheckController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'liveness'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/health/ready'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;HealthCheckController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'readiness'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The liveness endpoint stays simple and fast. Load balancers and container orchestrators use it to determine if the process should be restarted. The readiness endpoint does the heavy lifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Health Check Controller
&lt;/h2&gt;

&lt;p&gt;Start by generating a controller dedicated to health checks:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravelmonitoring</category>
      <category>laravelhealthchecks</category>
      <category>laraveldevops</category>
      <category>deploynix</category>
    </item>
    <item>
      <title>Laravel Deployment for Beginners: What Actually Happens When You Click Deploy</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Wed, 22 Apr 2026 22:00:08 +0000</pubDate>
      <link>https://dev.to/deploynix/laravel-deployment-for-beginners-what-actually-happens-when-you-click-deploy-1b0n</link>
      <guid>https://dev.to/deploynix/laravel-deployment-for-beginners-what-actually-happens-when-you-click-deploy-1b0n</guid>
      <description>&lt;p&gt;You have been building your Laravel application on your laptop for weeks. Everything works perfectly on &lt;code&gt;localhost&lt;/code&gt;. The features are done, the pages look great, and your tests pass. Now you want to put it on the internet so real people can use it. You click "Deploy" in Deploynix, a progress bar fills up, and 60 seconds later your application is live.&lt;/p&gt;

&lt;p&gt;But what actually happened during those 60 seconds? What did Deploynix do to take your code from a GitHub repository and turn it into a working website? Understanding the deployment process demystifies one of the most intimidating parts of web development. Once you know what is happening under the hood, deployments stop being scary and start being routine.&lt;/p&gt;

&lt;p&gt;This guide walks through every step of a Laravel deployment in plain language. No jargon, no assumptions about your experience level. If you can build a Laravel app, you can understand how it gets deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before Deployment: What Your Server Looks Like
&lt;/h2&gt;

&lt;p&gt;Before you ever deploy your application, Deploynix has already set up your server with everything Laravel needs to run. Think of your server as a computer in a data center that is always on, always connected to the internet, and always waiting to serve your website.&lt;/p&gt;

&lt;p&gt;When Deploynix provisions a server, it installs and configures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The operating system: Ubuntu Linux, which is the most common operating system for web servers.&lt;/li&gt;
&lt;li&gt;PHP: The programming language that Laravel is written in. Deploynix installs the exact version your application needs.&lt;/li&gt;
&lt;li&gt;A web server (Nginx): Software that listens for incoming web requests and routes them to your Laravel application. When someone types your domain name into their browser, Nginx is the first piece of software that responds.&lt;/li&gt;
&lt;li&gt;A database (MySQL, MariaDB, or PostgreSQL): Where your application stores its data, such as user accounts, posts, orders, and anything else you save to the database.&lt;/li&gt;
&lt;li&gt;Composer: The tool that manages your PHP dependencies, which are the packages and libraries your Laravel application uses.&lt;/li&gt;
&lt;li&gt;Node.js and npm: Tools for compiling your frontend assets, things like CSS and JavaScript that your browser needs to display your pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this setup happens once, when you first provision the server. After that, deploying your application is about getting your code onto this server and configuring it to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Getting Your Code from GitHub
&lt;/h2&gt;

&lt;p&gt;The first thing that happens when you click Deploy is that Deploynix copies your application's code from your Git repository (GitHub, GitLab, or Bitbucket) to your server.&lt;/p&gt;

&lt;p&gt;You have been pushing code to your repository every time you commit changes. Your repository contains everything that makes up your application: the PHP files, the Blade templates, the JavaScript, the configuration files, and the migration files. It is the single source of truth for what your application looks like at any given moment.&lt;/p&gt;

&lt;p&gt;Deploynix connects to your repository and downloads the latest version of your code to the server. This is similar to what happens when you run &lt;code&gt;git clone&lt;/code&gt; on your laptop, except Deploynix is doing it on your server.&lt;/p&gt;

&lt;p&gt;But here is an important detail: Deploynix does not just dump the code into the same folder every time. It creates a brand new folder for each deployment. If today is your fifth deployment, there are five separate folders on your server, one for each version of your code. We will explain why this matters in a later step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Installing PHP Dependencies
&lt;/h2&gt;

&lt;p&gt;Your Laravel application depends on dozens of packages written by other developers. Things like the Laravel framework itself, the database driver, the authentication system, and any other packages you have added with &lt;code&gt;composer require&lt;/code&gt;. These dependencies are listed in your &lt;code&gt;composer.json&lt;/code&gt; file but are not stored in your Git repository (the &lt;code&gt;vendor&lt;/code&gt; folder is in your &lt;code&gt;.gitignore&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;After downloading your code, Deploynix runs &lt;code&gt;composer install&lt;/code&gt; on the server. This reads your &lt;code&gt;composer.json&lt;/code&gt; and &lt;code&gt;composer.lock&lt;/code&gt; files, downloads every package your application needs, and puts them in the &lt;code&gt;vendor&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This step uses the &lt;code&gt;composer.lock&lt;/code&gt; file, not just &lt;code&gt;composer.json&lt;/code&gt;. The lock file records the exact version of every package that was installed on your development machine. This ensures that the server installs the same versions you tested with, not newer versions that might behave differently. Consistency between your development environment and production is critical.&lt;/p&gt;

&lt;p&gt;In production, Deploynix passes the &lt;code&gt;--no-dev&lt;/code&gt; flag to Composer, which tells it to skip development-only dependencies. Things like Pest (your testing framework) and Laravel Pint (your code formatter) are not needed on a production server, so skipping them makes the installation faster and the application lighter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Installing and Building Frontend Assets
&lt;/h2&gt;

&lt;p&gt;If your application uses Tailwind CSS, Alpine.js, or any other frontend tools managed through npm, the next step is installing those JavaScript dependencies and compiling your assets.&lt;/p&gt;

&lt;p&gt;Deploynix runs &lt;code&gt;npm install&lt;/code&gt; (or &lt;code&gt;npm ci&lt;/code&gt;) to download the JavaScript packages listed in your &lt;code&gt;package.json&lt;/code&gt; file. Then it runs &lt;code&gt;npm run build&lt;/code&gt; to compile everything into the final CSS and JavaScript files that browsers can understand.&lt;/p&gt;

&lt;p&gt;During development, you might have been running &lt;code&gt;npm run dev&lt;/code&gt;, which watches for file changes and recompiles on the fly. In production, &lt;code&gt;npm run build&lt;/code&gt; creates optimized, minified versions of your assets. Minification removes whitespace, shortens variable names, and compresses the files so they are as small as possible. Smaller files mean faster page loads for your users.&lt;/p&gt;

&lt;p&gt;The compiled files typically end up in your &lt;code&gt;public/build&lt;/code&gt; folder. These are the files that Nginx will serve directly to browsers when they request your CSS and JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Setting Up the Environment File
&lt;/h2&gt;

&lt;p&gt;Your Laravel application uses a &lt;code&gt;.env&lt;/code&gt; file to store configuration that varies between environments. On your laptop, the &lt;code&gt;.env&lt;/code&gt; file points to your local database, uses the &lt;code&gt;local&lt;/code&gt; app environment, and shows detailed error messages. In production, the configuration is different.&lt;/p&gt;

&lt;p&gt;Deploynix manages your production environment variables through its dashboard. You set values like your database credentials, your application key, your mail server settings, and any API keys your application needs. Deploynix stores these securely and makes them available to your application through the &lt;code&gt;.env&lt;/code&gt; file on the server.&lt;/p&gt;

&lt;p&gt;Some important differences between your local &lt;code&gt;.env&lt;/code&gt; and your production &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;APP_ENV&lt;/code&gt; is set to &lt;code&gt;production&lt;/code&gt; instead of &lt;code&gt;local&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_DEBUG&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; so that error details are not shown to users. In development, seeing a full stack trace is helpful. In production, it would expose sensitive information.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_URL&lt;/code&gt; points to your actual domain name instead of &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Database credentials point to your production database server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;APP_KEY&lt;/code&gt; is a unique encryption key that Laravel uses to encrypt cookies, sessions, and other sensitive data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file is never stored in your Git repository (it is in &lt;code&gt;.gitignore&lt;/code&gt;). This is intentional. You do not want production database passwords in your code repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Running Database Migrations
&lt;/h2&gt;

&lt;p&gt;Your application's database starts empty. The structure of your database (the tables, the columns, the indexes) is defined in migration files. Migrations are PHP files in your &lt;code&gt;database/migrations&lt;/code&gt; folder that describe each change to your database schema.&lt;/p&gt;

&lt;p&gt;When Deploynix deploys your application, it runs &lt;code&gt;php artisan migrate&lt;/code&gt; to apply any new migrations. This command looks at which migrations have already been run (Laravel tracks this in a special &lt;code&gt;migrations&lt;/code&gt; table in your database) and runs only the new ones.&lt;/p&gt;

&lt;p&gt;For example, if your previous deployment had 15 migrations and you have since added a 16th migration that adds a &lt;code&gt;phone_number&lt;/code&gt; column to the &lt;code&gt;users&lt;/code&gt; table, the migrate command will only run that 16th migration. The other 15 are already reflected in the database.&lt;/p&gt;

&lt;p&gt;Migrations are run in order, based on the timestamp in their filename. This ensures that the database structure evolves predictably and consistently, whether you are deploying for the first time or the hundredth time.&lt;/p&gt;

&lt;p&gt;This step is one of the few parts of deployment that can cause problems if something goes wrong. A migration that fails halfway through can leave your database in an inconsistent state. This is why it is important to test your migrations thoroughly before deploying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Caching Configuration, Routes, and Views
&lt;/h2&gt;

&lt;p&gt;Laravel is a feature-rich framework, and loading all of its configuration, routes, and views from scratch on every request takes time. To make your application faster in production, Deploynix runs several caching commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;php artisan config:cache&lt;/code&gt; compiles all of your configuration files into a single cached file. Instead of reading dozens of configuration files on every request, Laravel reads one file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;php artisan route:cache&lt;/code&gt; compiles your route definitions into a format that Laravel can load faster. If you have dozens or hundreds of routes, this makes a noticeable difference.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;php artisan view:cache&lt;/code&gt; pre-compiles your Blade templates into plain PHP. Normally, Laravel compiles Blade templates the first time they are requested. Caching them ahead of time means the first visitor to any page gets the same fast response as the thousandth visitor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These caches are rebuilt on every deployment so they always reflect your latest code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: The Symlink Swap (Zero-Downtime Magic)
&lt;/h2&gt;

&lt;p&gt;Remember how we said that Deploynix creates a new folder for each deployment? Here is why.&lt;/p&gt;

&lt;p&gt;Your web server (Nginx) is configured to serve your application from a specific path, something like &lt;code&gt;/home/deploynix/yoursite.com/current&lt;/code&gt;. But &lt;code&gt;current&lt;/code&gt; is not a regular folder. It is a symlink (short for symbolic link), which is essentially a shortcut that points to another folder.&lt;/p&gt;

&lt;p&gt;Right now, &lt;code&gt;current&lt;/code&gt; points to the folder containing your previous deployment's code. All of the steps we have discussed so far (downloading code, installing dependencies, running migrations) happened in a new folder. Your previous version of the application has been running the entire time, serving requests to your users without interruption.&lt;/p&gt;

&lt;p&gt;Once everything in the new folder is ready, Deploynix updates the &lt;code&gt;current&lt;/code&gt; symlink to point to the new folder. This change is atomic, meaning it happens in a single instant. One moment, Nginx is serving the old code. The next moment, it is serving the new code. There is no gap, no moment where the application is unavailable, no half-old-half-new state.&lt;/p&gt;

&lt;p&gt;This is what "zero-downtime deployment" means. Your users never see an error page or a loading spinner during deployment. They just seamlessly start seeing the new version of your application.&lt;/p&gt;

&lt;p&gt;The old deployment folders are kept around for a while in case you need to roll back. If something goes wrong with the new deployment, Deploynix can switch the symlink back to the previous folder in seconds, instantly reverting to the last working version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Restarting Workers and Services
&lt;/h2&gt;

&lt;p&gt;Some parts of your Laravel application run continuously in the background, not just when a user visits a page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Queue workers&lt;/strong&gt; process jobs that you have queued for background execution. Things like sending emails, processing uploaded images, or generating reports. These workers load your application code into memory when they start and keep running until they are told to stop.&lt;/p&gt;

&lt;p&gt;After a deployment, the queue workers are still running the old version of your code. Deploynix restarts them so they pick up the new code. This restart is done gracefully: the worker finishes processing its current job, then restarts with the new code. No jobs are lost or interrupted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron jobs&lt;/strong&gt; (scheduled tasks) are managed by Laravel's task scheduler. If you have scheduled commands defined in your application, they continue running on the server's schedule. The next time a scheduled task runs, it automatically uses the new code because it runs fresh through the &lt;code&gt;current&lt;/code&gt; symlink.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: Your Application Is Live
&lt;/h2&gt;

&lt;p&gt;After all of these steps complete, your application is live and serving the latest version of your code. The entire process typically takes between 30 and 90 seconds, depending on the size of your application and the number of dependencies.&lt;/p&gt;

&lt;p&gt;Here is a summary of what happened:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your code was downloaded from GitHub to a new folder on the server.&lt;/li&gt;
&lt;li&gt;PHP dependencies were installed with Composer.&lt;/li&gt;
&lt;li&gt;Frontend assets were compiled with npm.&lt;/li&gt;
&lt;li&gt;The environment configuration was applied.&lt;/li&gt;
&lt;li&gt;New database migrations were run.&lt;/li&gt;
&lt;li&gt;Configuration, routes, and views were cached.&lt;/li&gt;
&lt;li&gt;The symlink was swapped to point to the new code.&lt;/li&gt;
&lt;li&gt;Background workers were restarted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every subsequent deployment follows the same steps. The process is the same whether it is your second deployment or your two hundredth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When Something Goes Wrong
&lt;/h2&gt;

&lt;p&gt;Deployments can fail. A migration might have a bug. A Composer dependency might not install correctly. The build step might encounter an error. When this happens, Deploynix stops the deployment before the symlink swap.&lt;/p&gt;

&lt;p&gt;This is the beauty of the release-folder approach. If something fails during steps 1 through 6, the symlink never changes. Your previous deployment is still running, still serving users, completely unaffected. The failed deployment folder sits there harmlessly, and you can investigate what went wrong without any pressure because your application is still live.&lt;/p&gt;

&lt;p&gt;If a problem is discovered after the symlink swap (maybe a page throws an error that was not caught during testing), you can use Deploynix's rollback feature. Rolling back switches the symlink back to the previous deployment folder. It takes a few seconds, and your application is back to the last known good version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Updates
&lt;/h2&gt;

&lt;p&gt;After your first deployment, every subsequent deployment is the same process. You make changes to your code on your laptop, commit them to Git, push them to GitHub, and deploy through Deploynix. You can deploy manually by clicking the Deploy button in the dashboard, or you can set up automatic deployments that trigger whenever you push to a specific branch.&lt;/p&gt;

&lt;p&gt;Most teams set up automatic deployments for their main branch. The workflow becomes: write code, open a pull request, get it reviewed, merge it, and the deployment happens automatically. You can also schedule deployments for specific times using Deploynix's scheduled deployment feature, which is useful for changes that should go live during low-traffic hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;Deployment is the bridge between building your application and sharing it with the world. Understanding what happens during deployment makes you a more confident developer. When you know that the symlink swap is what prevents downtime, you can explain it to your team. When you know that migrations run in order, you can write them with confidence. When you know that the &lt;code&gt;.env&lt;/code&gt; file is separate from your code, you understand why it is safe to push your code to a public repository.&lt;/p&gt;

&lt;p&gt;Deploynix automates all of these steps so you can focus on building your application rather than managing servers. But automation works best when you understand what is being automated. Now you do. Go deploy something.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>deployment</category>
      <category>tutorial</category>
      <category>gettingstarted</category>
    </item>
    <item>
      <title>WebSockets 101 for Laravel Developers: From Concept to Production on Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Wed, 22 Apr 2026 10:00:05 +0000</pubDate>
      <link>https://dev.to/deploynix/websockets-101-for-laravel-developers-from-concept-to-production-on-deploynix-15oo</link>
      <guid>https://dev.to/deploynix/websockets-101-for-laravel-developers-from-concept-to-production-on-deploynix-15oo</guid>
      <description>&lt;p&gt;Most web applications are built on a simple model: the browser sends a request, the server processes it, the server sends a response, and the connection closes. This request-response cycle has powered the web for decades, and it works perfectly for the vast majority of interactions. But there are moments when it falls short.&lt;/p&gt;

&lt;p&gt;Imagine a project management app where multiple team members are viewing the same task board. Alice moves a task from "In Progress" to "Done." Bob, who is looking at the same board, does not see the change until he refreshes the page. In a traditional HTTP application, Bob's browser has no way to know that something changed on the server. It would have to poll repeatedly, asking the server "did anything change?" every few seconds, which is wasteful and introduces latency.&lt;/p&gt;

&lt;p&gt;WebSockets solve this problem elegantly. Instead of closing the connection after each request-response cycle, a WebSocket connection stays open. The server can push data to the browser the instant something happens, without the browser asking. Alice moves the task, the server broadcasts the change, and Bob's browser updates in real time.&lt;/p&gt;

&lt;p&gt;This guide takes you from zero WebSocket knowledge to a working real-time feature in your Laravel application, deployed and running on Deploynix.&lt;/p&gt;

&lt;h2&gt;
  
  
  How WebSockets Work
&lt;/h2&gt;

&lt;p&gt;A WebSocket connection starts as a regular HTTP request. The browser sends an HTTP request with a special &lt;code&gt;Upgrade&lt;/code&gt; header that says "I would like to switch to the WebSocket protocol." If the server supports WebSockets, it responds with a 101 status code (Switching Protocols), and the connection transitions from HTTP to WebSocket.&lt;/p&gt;

&lt;p&gt;Once the connection is upgraded, it becomes a full-duplex communication channel. Both the browser and the server can send messages at any time, independently of each other. The connection remains open until either side explicitly closes it, or a network interruption occurs.&lt;/p&gt;

&lt;p&gt;The key differences from regular HTTP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistent: The connection stays open for the duration of the session. No repeated handshakes, no connection overhead for each message.&lt;/li&gt;
&lt;li&gt;Bidirectional: The server can push data to the client without the client requesting it. The client can also send data to the server at any time.&lt;/li&gt;
&lt;li&gt;Low latency: Messages are delivered in milliseconds because there is no connection setup for each message.&lt;/li&gt;
&lt;li&gt;Low overhead: WebSocket frames have minimal headers compared to HTTP requests, so the per-message overhead is tiny.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebSockets are ideal for features like live notifications, real-time dashboards, chat applications, collaborative editing, live activity feeds, and any situation where users need to see changes as they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel Broadcasting: The Framework Layer
&lt;/h2&gt;

&lt;p&gt;Laravel does not ask you to work with raw WebSocket connections. Instead, it provides a Broadcasting system that abstracts the complexity into a clean, event-driven API. You define events in PHP, broadcast them, and listen for them in JavaScript. Laravel handles the plumbing.&lt;/p&gt;

&lt;p&gt;The broadcasting system has three components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events:&lt;/strong&gt; Standard Laravel events that implement the &lt;code&gt;ShouldBroadcast&lt;/code&gt; or &lt;code&gt;ShouldBroadcastNow&lt;/code&gt; interface. When these events are dispatched, Laravel serializes their public properties and sends them to the broadcasting driver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Channels:&lt;/strong&gt; Named channels that organize broadcasts. Laravel supports three types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public channels that anyone can listen to.&lt;/li&gt;
&lt;li&gt;Private channels that require authentication. The server verifies that the user is authorized to listen on the channel before allowing the connection.&lt;/li&gt;
&lt;li&gt;Presence channels that are private channels with the additional ability to see who else is listening. These are perfect for showing "who's online" indicators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Laravel Echo:&lt;/strong&gt; A JavaScript library that connects to the WebSocket server and provides a clean API for subscribing to channels and listening for events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverb vs. Third-Party Services
&lt;/h2&gt;

&lt;p&gt;Before Reverb, Laravel developers had two main options for WebSocket infrastructure: Pusher (a paid, hosted service) and laravel-websockets (a community package that ran a WebSocket server alongside your Laravel app). Each had trade-offs.&lt;/p&gt;

&lt;p&gt;Pusher is easy to set up but introduces a third-party dependency, ongoing costs, and the latency of routing messages through an external service. The laravel-websockets package kept everything in-house but was a community project with varying maintenance and could be tricky to run reliably in production.&lt;/p&gt;

&lt;p&gt;Laravel Reverb changed the equation. Reverb is a first-party Laravel package, maintained by the Laravel team, that provides a high-performance WebSocket server designed specifically for Laravel Broadcasting. It runs on your own servers, so there is no third-party dependency, no per-message pricing, and no added latency from routing through an external service.&lt;/p&gt;

&lt;p&gt;Reverb is built on ReactPHP and handles thousands of concurrent connections efficiently. It integrates seamlessly with Laravel's authentication system for private and presence channels, and it works out of the box with Laravel Echo.&lt;/p&gt;

&lt;p&gt;For most Laravel applications deploying on Deploynix, Reverb is the clear choice. You get full control over your WebSocket infrastructure, zero external dependencies, and first-party support from the Laravel team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Reverb in Your Laravel Application
&lt;/h2&gt;

&lt;p&gt;Let's walk through adding Reverb to an existing Laravel application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Install Reverb
&lt;/h3&gt;

&lt;p&gt;Install Reverb using the Artisan installer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan &lt;span class="nb"&gt;install&lt;/span&gt;:broadcasting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command installs the Reverb package, publishes the configuration file, adds the necessary environment variables to your &lt;code&gt;.env&lt;/code&gt; file, and installs the JavaScript dependencies (Laravel Echo and the Pusher JS client, which Echo uses as its WebSocket client library).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Configure Environment Variables
&lt;/h3&gt;

&lt;p&gt;After installation, your &lt;code&gt;.env&lt;/code&gt; file will have new Reverb-related variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BROADCAST_CONNECTION=reverb

REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=https
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In development, &lt;code&gt;localhost&lt;/code&gt; is fine. For production on Deploynix, you will configure these differently (we will cover that in the deployment section).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create a Broadcastable Event
&lt;/h3&gt;

&lt;p&gt;Create an event that implements &lt;code&gt;ShouldBroadcastNow&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>websockets</category>
      <category>reverb</category>
      <category>laravelecho</category>
    </item>
    <item>
      <title>Why Your Laravel App's Emails Land in Spam (and How to Fix It on Deploynix)</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Tue, 21 Apr 2026 22:00:06 +0000</pubDate>
      <link>https://dev.to/deploynix/why-your-laravel-apps-emails-land-in-spam-and-how-to-fix-it-on-deploynix-2cfk</link>
      <guid>https://dev.to/deploynix/why-your-laravel-apps-emails-land-in-spam-and-how-to-fix-it-on-deploynix-2cfk</guid>
      <description>&lt;p&gt;You spent an hour crafting the perfect welcome email. The copy is warm, the design is clean, and the call-to-action is compelling. A new user signs up for your Laravel application, your Mailable fires, the queue job processes successfully, and your logs confirm the email was sent. But the user never sees it. It is sitting in their spam folder, sandwiched between a Nigerian prince and a dubious pharmaceutical offer.&lt;/p&gt;

&lt;p&gt;Email deliverability is one of those problems that is invisible until it is catastrophic. Your Laravel application can send emails perfectly from a technical standpoint, the SMTP transaction completes, the mail server accepts the message, and everything looks green in your logs. But email providers like Gmail, Outlook, and Yahoo make the final decision about where your message lands, and they are ruthlessly aggressive about filtering anything that looks remotely suspicious.&lt;/p&gt;

&lt;p&gt;This guide explains why your emails are landing in spam and gives you concrete steps to fix it, with specific guidance for Laravel applications running on Deploynix-managed servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Pillars of Email Authentication
&lt;/h2&gt;

&lt;p&gt;Modern email deliverability rests on three authentication protocols that work together to prove you are who you say you are. If any of these are missing or misconfigured, email providers will treat your messages with suspicion. All three are DNS records, which means you need access to your domain's DNS management.&lt;/p&gt;

&lt;h3&gt;
  
  
  SPF (Sender Policy Framework)
&lt;/h3&gt;

&lt;p&gt;SPF tells receiving mail servers which servers are authorized to send email on behalf of your domain. When Gmail receives an email claiming to be from &lt;code&gt;notifications@yourapp.com&lt;/code&gt;, it checks the SPF record for &lt;code&gt;yourapp.com&lt;/code&gt; to see if the sending server's IP address is authorized.&lt;/p&gt;

&lt;p&gt;An SPF record is a TXT record on your domain that lists authorized senders. Here is what it looks like for common email providers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# If using Postmark
v=spf1 include:spf.mtasv.net ~all

# If using Mailgun
v=spf1 include:mailgun.org ~all

# If using Amazon SES
v=spf1 include:amazonses.com ~all

# If using multiple providers
v=spf1 include:spf.mtasv.net include:mailgun.org ~all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;~all&lt;/code&gt; at the end means "soft fail for anything not listed," which tells receivers to be suspicious of unauthorized senders but not outright reject them. Some guides recommend &lt;code&gt;-all&lt;/code&gt; (hard fail), but &lt;code&gt;~all&lt;/code&gt; is generally safer while you are setting things up to avoid accidentally blocking legitimate mail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; If you are sending email directly from your Deploynix server using &lt;code&gt;sendmail&lt;/code&gt; or a local SMTP server, your server's IP address needs to be in the SPF record. This is one of the many reasons we recommend using a dedicated email delivery service instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  DKIM (DomainKeys Identified Mail)
&lt;/h3&gt;

&lt;p&gt;DKIM adds a cryptographic signature to your outgoing emails that proves the message was actually sent by an authorized sender and was not modified in transit. The sending server signs the email with a private key, and the receiving server verifies the signature using a public key published in your DNS records.&lt;/p&gt;

&lt;p&gt;Your email delivery provider generates the DKIM keys and gives you the DNS record to publish. The record looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# DKIM record (type: TXT)
# Host: default._domainkey.yourapp.com
# Value: v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEB...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Laravel, you do not interact with DKIM directly in your application code. It is handled at the mail server level. When you configure your Laravel application to send through Postmark, Mailgun, SES, or another provider, that provider handles DKIM signing automatically. Your responsibility is publishing the correct DKIM DNS record that the provider gives you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; DKIM records are often long and can get truncated if your DNS provider has character limits on TXT records. Some providers require you to split the record across multiple strings. Verify the full record is published correctly using a tool like &lt;code&gt;dig TXT default._domainkey.yourapp.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  DMARC (Domain-based Message Authentication, Reporting, and Conformance)
&lt;/h3&gt;

&lt;p&gt;DMARC ties SPF and DKIM together and tells receiving servers what to do when authentication fails. It also provides a reporting mechanism so you can see who is sending email using your domain, whether legitimately or not.&lt;/p&gt;

&lt;p&gt;A basic DMARC record looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# DMARC record (type: TXT)
# Host: _dmarc.yourapp.com
# Value: v=DMARC1; p=none; rua=mailto:dmarc@yourapp.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;p=none&lt;/code&gt; policy means "monitor only." This is the right starting point. It tells receivers to deliver all messages regardless of authentication results but send you reports so you can see what is happening.&lt;/p&gt;

&lt;p&gt;Once you have verified that your legitimate emails all pass SPF and DKIM, tighten the policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;p=quarantine&lt;/code&gt; tells receivers to send failing messages to spam.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;p=reject&lt;/code&gt; tells receivers to outright reject failing messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving from &lt;code&gt;none&lt;/code&gt; to &lt;code&gt;quarantine&lt;/code&gt; to &lt;code&gt;reject&lt;/code&gt; should be a gradual process. Start with &lt;code&gt;none&lt;/code&gt;, analyze the reports for a few weeks, fix any authentication issues, then move to &lt;code&gt;quarantine&lt;/code&gt;, and eventually &lt;code&gt;reject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Setting &lt;code&gt;p=reject&lt;/code&gt; immediately without monitoring first. If there is a service you forgot about that sends email using your domain (a CRM, a helpdesk, a marketing automation tool), &lt;code&gt;p=reject&lt;/code&gt; will cause all of its emails to bounce.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse DNS (PTR Records)
&lt;/h2&gt;

&lt;p&gt;Reverse DNS maps an IP address back to a hostname. When a mail server receives a connection, it performs a reverse DNS lookup on the connecting IP to verify that the IP address resolves back to a legitimate hostname. If reverse DNS is not configured or resolves to a generic hostname like &lt;code&gt;123.456.789.0.generic.provider.com&lt;/code&gt;, that is a spam signal.&lt;/p&gt;

&lt;p&gt;For emails sent through a dedicated email delivery service (Postmark, Mailgun, SES), the provider handles reverse DNS for their own servers. You do not need to configure anything.&lt;/p&gt;

&lt;p&gt;However, if you are sending email directly from your Deploynix-managed server, reverse DNS matters. Deploynix provisions servers on cloud providers like DigitalOcean, Vultr, Hetzner, Linode, and AWS. Each provider has a different process for setting PTR records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DigitalOcean: PTR is automatically set to the Droplet's name. Name your server with your domain (e.g., &lt;code&gt;mail.yourapp.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Vultr: Configure PTR through the Vultr control panel under the server's settings.&lt;/li&gt;
&lt;li&gt;Hetzner: Configure PTR through the Hetzner Cloud Console or API.&lt;/li&gt;
&lt;li&gt;Linode: PTR is set through the Linode Manager's networking section.&lt;/li&gt;
&lt;li&gt;AWS: Request a PTR record through AWS support for EC2 instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here is the real advice: do not send email directly from your application server. Use a dedicated email delivery service. The rest of this guide assumes you will follow that advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Should Not Send Email from Your Server
&lt;/h2&gt;

&lt;p&gt;It is technically possible to configure Postfix or another MTA on your Deploynix-managed server and send email directly. Laravel supports this through the &lt;code&gt;sendmail&lt;/code&gt; mail driver. Do not do this unless you have a very specific reason and understand the implications.&lt;/p&gt;

&lt;p&gt;Sending email from a cloud server IP address is fighting an uphill battle. Cloud provider IP ranges are well-known to email providers, and many of those IPs have been used to send spam at some point. Even if your specific IP is clean, it may share a reputation with neighboring IPs in the same subnet.&lt;/p&gt;

&lt;p&gt;Dedicated email delivery services solve this problem by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maintaining IP pools with carefully managed reputations.&lt;/li&gt;
&lt;li&gt;Handling SPF, DKIM, and DMARC configuration for you.&lt;/li&gt;
&lt;li&gt;Providing deliverability analytics so you can monitor inbox placement.&lt;/li&gt;
&lt;li&gt;Managing bounce handling, complaint processing, and suppression lists.&lt;/li&gt;
&lt;li&gt;Offering dedicated IP addresses if your volume justifies it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Laravel applications, the best options are Postmark (excellent for transactional email, the highest deliverability rates in the industry), Mailgun (good for both transactional and bulk email), and Amazon SES (lowest cost at scale, more setup required).&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Laravel for Email Delivery
&lt;/h2&gt;

&lt;p&gt;In your Laravel application, email configuration lives in &lt;code&gt;config/mail.php&lt;/code&gt;. Set your default mailer to your chosen email provider and configure the credentials in your environment variables. Here is an example using Postmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .env on your Deploynix server&lt;/span&gt;
&lt;span class="no"&gt;MAIL_MAILER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;postmark&lt;/span&gt;
&lt;span class="no"&gt;POSTMARK_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;postmark&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;
&lt;span class="no"&gt;MAIL_FROM_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;yourapp&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;
&lt;span class="no"&gt;MAIL_FROM_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Your App Name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MAIL_FROM_ADDRESS&lt;/code&gt; should use your own domain, not a freemail address like Gmail. Sending application emails from a Gmail address is a strong spam signal and also means you cannot properly configure SPF, DKIM, and DMARC.&lt;/p&gt;

&lt;p&gt;On your Deploynix server, set these environment variables through the Deploynix dashboard. Environment variables are stored securely and injected into your application's &lt;code&gt;.env&lt;/code&gt; file during deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queue Your Emails
&lt;/h3&gt;

&lt;p&gt;Always send emails through a queue in production. Sending email synchronously blocks the HTTP request while waiting for the SMTP transaction to complete, which can take several seconds. More importantly, if the email provider is temporarily unavailable, the request fails entirely.&lt;/p&gt;

&lt;p&gt;Laravel makes this trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your controller or service&lt;/span&gt;
&lt;span class="nc"&gt;Mail&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Or implement ShouldQueue on the Mailable itself&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WelcomeEmail&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Mailable&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ShouldQueue&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Deploynix, your queued emails are processed by your dedicated worker servers. This means email sending does not consume resources on your app servers, and if the email provider has a momentary hiccup, the queue automatically retries the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content and Sending Practices
&lt;/h2&gt;

&lt;p&gt;Authentication is necessary but not sufficient. The content and patterns of your emails also affect deliverability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Spam Triggers
&lt;/h3&gt;

&lt;p&gt;Email providers use content analysis alongside authentication checks. Avoid these common triggers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ALL CAPS in subject lines. It looks like shouting.&lt;/li&gt;
&lt;li&gt;Excessive exclamation marks. One is fine. Three is spam.&lt;/li&gt;
&lt;li&gt;Spammy phrases. "Act now," "limited time," "free money." These are weighted negatively by spam filters.&lt;/li&gt;
&lt;li&gt;Image-only emails. Emails that are just one large image with no text are a classic spam pattern. Always include meaningful text content.&lt;/li&gt;
&lt;li&gt;Misleading subject lines. The subject should accurately describe the email content. "Re:" on a non-reply email is deceptive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Send Consistently
&lt;/h3&gt;

&lt;p&gt;Email providers track your sending patterns. If you normally send 100 emails per day and suddenly send 10,000, that spike looks suspicious. When launching a new feature that generates emails, ramp up gradually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Honor Unsubscribes Immediately
&lt;/h3&gt;

&lt;p&gt;When a user unsubscribes, stop sending them emails immediately. Do not wait for the next batch processing cycle. Do not send a "we are sorry to see you go" email. Just stop. Laravel's notification system supports checking notification preferences, so implement an &lt;code&gt;unsubscribed&lt;/code&gt; check in your notification classes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor Bounce Rates
&lt;/h3&gt;

&lt;p&gt;A high bounce rate (sending to invalid email addresses) damages your sender reputation rapidly. Implement email verification during registration, handle hard bounces by disabling the email address, and periodically clean your user list.&lt;/p&gt;

&lt;p&gt;Most email delivery services provide webhook callbacks for bounce events. Configure a webhook endpoint in your Laravel application that listens for bounce notifications and automatically marks the email address as invalid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Email Deliverability
&lt;/h2&gt;

&lt;p&gt;Before declaring victory, test your email setup thoroughly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mail Tester (mail-tester.com):&lt;/strong&gt; Send an email from your application to the unique address provided by Mail Tester. It will analyze your message and give you a score out of 10, with specific feedback on what to fix. Aim for a score of 9 or above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MXToolbox:&lt;/strong&gt; Check your domain's DNS records including SPF, DKIM, and DMARC. It identifies common misconfigurations and provides actionable recommendations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Postmaster Tools:&lt;/strong&gt; If a significant portion of your users are on Gmail, register your domain with Google Postmaster Tools. It provides data on your domain's reputation, spam rate, authentication success rate, and delivery errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send to Multiple Providers:&lt;/strong&gt; Test by sending emails to Gmail, Outlook, Yahoo, and iCloud accounts. Deliverability can vary between providers, so passing spam filters on one does not guarantee passing on another.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete DNS Checklist
&lt;/h2&gt;

&lt;p&gt;Here is a summary of every DNS record you need for reliable email deliverability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SPF Record: TXT record on your root domain listing all authorized senders.&lt;/li&gt;
&lt;li&gt;DKIM Record: TXT record (usually on a subdomain like &lt;code&gt;default._domainkey&lt;/code&gt;) containing your public key.&lt;/li&gt;
&lt;li&gt;DMARC Record: TXT record on &lt;code&gt;_dmarc.yourdomain.com&lt;/code&gt; specifying your policy and report address.&lt;/li&gt;
&lt;li&gt;MX Records: If you also receive email on your domain, proper MX records are essential.&lt;/li&gt;
&lt;li&gt;PTR Record: Only relevant if sending directly from your server (which you should not be doing).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify all records are published correctly, then test using the tools mentioned above. DNS changes can take up to 48 hours to propagate, but usually take effect within an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving Forward
&lt;/h2&gt;

&lt;p&gt;Email deliverability is not a set-it-and-forget-it task. Monitor your delivery rates, watch for reputation drops, respond to bounces and complaints promptly, and review your DMARC reports periodically.&lt;/p&gt;

&lt;p&gt;On Deploynix, your server infrastructure handles the operational side: environment variables for mail credentials, queue workers for reliable delivery, and the overall server performance that ensures emails are sent promptly. But the email authentication records and content practices are your responsibility.&lt;/p&gt;

&lt;p&gt;Get SPF, DKIM, and DMARC configured correctly. Use a dedicated email delivery service. Queue your emails. Monitor your metrics. Your carefully crafted welcome email deserves to land in the inbox, not the spam folder.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>email</category>
      <category>spam</category>
      <category>spf</category>
    </item>
    <item>
      <title>GDPR for Laravel Developers: Data Storage, Deletion, and Server Location on Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Tue, 21 Apr 2026 10:00:04 +0000</pubDate>
      <link>https://dev.to/deploynix/gdpr-for-laravel-developers-data-storage-deletion-and-server-location-on-deploynix-1n7c</link>
      <guid>https://dev.to/deploynix/gdpr-for-laravel-developers-data-storage-deletion-and-server-location-on-deploynix-1n7c</guid>
      <description>&lt;p&gt;GDPR compliance is not a server configuration toggle or a Laravel package you install. It is a set of obligations that touch every layer of your application, from the database schema to the server infrastructure to the user interface. As a Laravel developer, you are in a unique position: you control both the application code and, through Deploynix, the infrastructure it runs on.&lt;/p&gt;

&lt;p&gt;This guide takes a practical, code-aware approach to GDPR compliance. We will cover the rights your users have, how to implement them in Laravel, where your servers should live, and how Deploynix's infrastructure features support your compliance posture. No legal abstractions. Just concrete steps you can take this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Your Obligations
&lt;/h2&gt;

&lt;p&gt;GDPR grants individuals specific rights over their personal data. As the developer building the application that processes that data, these rights translate directly into features you need to build and infrastructure you need to configure.&lt;/p&gt;

&lt;p&gt;The rights most relevant to Laravel developers are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right to Access (Article 15):&lt;/strong&gt; Users can request a copy of all personal data you hold about them. You need a mechanism to gather data from across your application and provide it in a structured format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right to Erasure (Article 17):&lt;/strong&gt; Also called the "right to be forgotten." Users can request that you delete all their personal data, with some exceptions for legal obligations. You need a mechanism to find and delete every trace of a user's data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right to Data Portability (Article 20):&lt;/strong&gt; Users can request their data in a commonly used, machine-readable format. JSON is the standard choice for API-driven applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right to Rectification (Article 16):&lt;/strong&gt; Users can request corrections to inaccurate personal data. In most Laravel applications, this is satisfied by providing edit functionality for user profiles and related data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right to Restriction of Processing (Article 18):&lt;/strong&gt; Users can request that you stop processing their data while a complaint or correction request is being handled.&lt;/p&gt;

&lt;p&gt;Let's implement each of these practically in Laravel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Right to Data Export
&lt;/h2&gt;

&lt;p&gt;Data export is the foundation for both the right to access and the right to data portability. Your implementation needs to gather all personal data associated with a user, from every table where their data appears, and return it as a downloadable file.&lt;/p&gt;

&lt;p&gt;Start by auditing your database. Every table that contains a &lt;code&gt;user_id&lt;/code&gt; foreign key or stores personally identifiable information needs to be included in the export. In a typical Laravel application, this might include the users table, orders, addresses, support tickets, activity logs, uploaded files metadata, and notification preferences.&lt;/p&gt;

&lt;p&gt;Create a dedicated service class for data export. The service should query each relevant table for data belonging to the user and compile it into a structured JSON file. Consider the following approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDataExportService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'personal_information'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPersonalInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'orders'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'addresses'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAddresses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'support_tickets'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getSupportTickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'activity_log'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getActivityLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'preferences'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPreferences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'exported_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toIso8601String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each method should return an array of the user's data from that domain. Strip out internal identifiers, foreign keys, and system metadata that are not personal data. The user does not need to know their &lt;code&gt;stripe_customer_id&lt;/code&gt; or the internal &lt;code&gt;order_status_id&lt;/code&gt;. They need to see their name, email, order history, and the content they created.&lt;/p&gt;

&lt;p&gt;For large datasets, generate the export as a background job using Laravel's queue system. On Deploynix, queue jobs run on your dedicated worker servers, so the export process does not impact your application's responsiveness. Once the export is ready, notify the user via email with a temporary download link.&lt;/p&gt;

&lt;p&gt;Verify the user's identity before providing the export. GDPR requires that you confirm the identity of the person making the request. For authenticated users, requiring them to re-enter their password before initiating an export is usually sufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Right to Erasure
&lt;/h2&gt;

&lt;p&gt;The right to erasure is more complex than it appears. You cannot simply call &lt;code&gt;$user-&amp;gt;delete()&lt;/code&gt; and consider it done. You need to find and remove personal data from every location where it exists, including soft-deleted records, related models, uploaded files, cache entries, session data, log files, and backups.&lt;/p&gt;

&lt;p&gt;Here is a systematic approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Identify all personal data locations.&lt;/strong&gt; Walk through every model in your application that has a relationship to the User model or stores personal data. Create a checklist. Common locations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users table (the obvious one)&lt;/li&gt;
&lt;li&gt;Related models (orders, posts, comments, tickets)&lt;/li&gt;
&lt;li&gt;Media and file uploads (avatar images, documents)&lt;/li&gt;
&lt;li&gt;Activity logs and audit trails&lt;/li&gt;
&lt;li&gt;Cache entries keyed by user ID&lt;/li&gt;
&lt;li&gt;Session data&lt;/li&gt;
&lt;li&gt;Email notification history&lt;/li&gt;
&lt;li&gt;Third-party service records (Stripe customers, email marketing lists)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Decide what to delete versus anonymize.&lt;/strong&gt; GDPR does not require you to delete data that you have a legal obligation to retain. Financial records that you must keep for tax purposes can be anonymized instead of deleted: replace the user's name and email with placeholder values while retaining the transaction data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDeletionService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Anonymize records we must retain for legal reasons&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'customer_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Deleted User'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'customer_email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'deleted@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Delete records we do not need to retain&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;supportTickets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;forceDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;activityLogs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Remove uploaded files from storage&lt;/span&gt;
        &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;avatar_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Clear cache entries&lt;/span&gt;
        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:preferences"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:permissions"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Remove from third-party services&lt;/span&gt;
        &lt;span class="c1"&gt;// Stripe::customers()-&amp;gt;delete($user-&amp;gt;stripe_id);&lt;/span&gt;

        &lt;span class="c1"&gt;// Finally, delete the user&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;forceDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Handle soft-deleted records.&lt;/strong&gt; If you use Laravel's soft deletes, remember that soft-deleted records still contain personal data. Your deletion service should use &lt;code&gt;forceDelete()&lt;/code&gt; for GDPR erasure requests. Alternatively, anonymize the personal data fields and then soft delete, so the record exists for referential integrity but contains no personal information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Address backups.&lt;/strong&gt; This is the hardest part. Your database backups on Deploynix contain personal data, and you cannot selectively remove one user's data from a backup. The accepted approach under GDPR is to document that backups are retained for a defined period (for example, 30 days) and that deleted data will naturally expire from the backup cycle. If a backup is ever restored, you must re-apply any deletion requests that occurred after the backup was taken.&lt;/p&gt;

&lt;p&gt;Maintain a log of deletion requests with timestamps. If you ever restore from backup, query this log and re-execute deletions for any user who requested erasure after the backup date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cookie Consent and Tracking
&lt;/h2&gt;

&lt;p&gt;GDPR (and the ePrivacy Directive) require informed consent before setting non-essential cookies. In a Laravel application, this means you need a cookie consent mechanism that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Blocks analytics scripts, advertising pixels, and non-essential cookies until the user consents.&lt;/li&gt;
&lt;li&gt;Provides granular options so users can consent to some cookie categories but not others.&lt;/li&gt;
&lt;li&gt;Records the consent with a timestamp for your records.&lt;/li&gt;
&lt;li&gt;Allows users to withdraw consent at any time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implement consent storage in your database rather than relying solely on a cookie. A database record provides an auditable consent trail. Store the user's consent preferences, the timestamp, the version of your privacy policy they consented to, and their IP address.&lt;/p&gt;

&lt;p&gt;For your Laravel application's middleware, check consent status before setting cookies or including tracking scripts. A middleware that reads the consent cookie (or database record for authenticated users) and sets a view variable that your Blade templates check before rendering analytics scripts is a clean approach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In your Blade layout&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'analytics_consent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'granted'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server Location and Data Residency
&lt;/h2&gt;

&lt;p&gt;Where your servers are physically located matters under GDPR. Deploynix supports provisioning servers on multiple cloud providers, and for EU data residency, Hetzner is the strongest choice.&lt;/p&gt;

&lt;p&gt;Hetzner's data centers are exclusively in the EU (Germany and Finland). When you provision servers through Deploynix on Hetzner, you know with certainty that your data resides in the EU. There is no ambiguity about data replication to other regions or hidden cross-border transfers.&lt;/p&gt;

&lt;p&gt;For a GDPR-compliant Laravel setup on Deploynix, provision all of your servers in the same EU region:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App server on Hetzner (Nuremberg, Falkenstein, or Helsinki)&lt;/li&gt;
&lt;li&gt;Database server on Hetzner (same region for lowest latency)&lt;/li&gt;
&lt;li&gt;Cache server on Hetzner (same region)&lt;/li&gt;
&lt;li&gt;Worker server on Hetzner (same region)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configure your database backups to store in an EU-only location. If using AWS S3 for backup storage, select the &lt;code&gt;eu-central-1&lt;/code&gt; (Frankfurt) region and do not enable cross-region replication. If using Wasabi, select their Amsterdam or Frankfurt location.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Processing Documentation
&lt;/h2&gt;

&lt;p&gt;GDPR Article 30 requires that you maintain records of your processing activities. For a Laravel application deployed on Deploynix, this documentation should include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What personal data you collect:&lt;/strong&gt; List every field in your database that contains personal data. Name, email, IP address, physical address, payment information, and any other data that can identify a natural person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why you collect it:&lt;/strong&gt; Each data point needs a lawful basis. User email for account authentication (contractual necessity), IP address for security logging (legitimate interest), marketing preferences (consent). Be specific.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it is stored:&lt;/strong&gt; Document your server locations (Hetzner Nuremberg, for example), your backup storage location, and any third-party services that receive personal data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long you retain it:&lt;/strong&gt; Define retention periods for each data category. Active account data is retained for the life of the account. Deleted account data is purged within 30 days. Financial records are retained for 7 years for tax compliance. Log files are retained for 90 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who has access:&lt;/strong&gt; Document which team roles can access personal data. On Deploynix, this maps to your organization roles: which team members have database access, which can view logs, and which can access the server directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What security measures protect it:&lt;/strong&gt; Document your encryption (SSL in transit, encryption at rest for backups), firewall rules (configured through Deploynix), access controls (Deploynix organization roles), and any application-level security measures (password hashing, two-factor authentication).&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Data Subject Requests
&lt;/h2&gt;

&lt;p&gt;Build a system for receiving and tracking data subject requests. GDPR gives you 30 days to respond to a request (extendable by two months for complex cases). You need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the requestor's identity.&lt;/li&gt;
&lt;li&gt;Log the request with a timestamp.&lt;/li&gt;
&lt;li&gt;Process the request (export, deletion, rectification).&lt;/li&gt;
&lt;li&gt;Confirm completion to the user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simple Laravel implementation uses a model and controller dedicated to data subject requests. The model tracks the request type, status, and timeline. A queued job processes the request, and an email notification confirms completion.&lt;/p&gt;

&lt;p&gt;For applications with significant user bases, consider adding a self-service privacy dashboard where authenticated users can download their data, delete their account, and manage their consent preferences without submitting a manual request. This reduces your operational burden and improves the user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third-Party Services Audit
&lt;/h2&gt;

&lt;p&gt;Your Laravel application likely communicates with services outside your Deploynix infrastructure. Each of these services needs to be evaluated for GDPR compliance:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email delivery (Mailgun, SES, Postmark):&lt;/strong&gt; Do they process email addresses and content in the EU? Many offer EU-region processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment processing (Stripe, Paddle):&lt;/strong&gt; Payment processors typically act as independent data controllers or joint controllers. Ensure you have appropriate agreements in place. Deploynix uses Paddle for its own billing, which is EU-based.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics (Google Analytics, Plausible, Fathom):&lt;/strong&gt; Google Analytics has been found non-compliant in several EU jurisdictions. Consider EU-based alternatives like Plausible or Fathom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error tracking (Sentry, Bugsnag):&lt;/strong&gt; Error reports often contain user data (IP addresses, request parameters). Choose a provider with EU hosting options or configure data scrubbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search (Algolia, Meilisearch):&lt;/strong&gt; If you index personal data for search, the search service processes that data. Meilisearch can be self-hosted on your Deploynix servers, keeping search data within your EU infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Here is a condensed checklist for Laravel developers deploying on Deploynix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provision all servers on Hetzner (EU data centers) through Deploynix&lt;/li&gt;
&lt;li&gt;Configure firewall rules to restrict database and cache access&lt;/li&gt;
&lt;li&gt;Set up EU-only backup storage&lt;/li&gt;
&lt;li&gt;Implement user data export functionality&lt;/li&gt;
&lt;li&gt;Implement user data deletion with anonymization for legally required records&lt;/li&gt;
&lt;li&gt;Add cookie consent with granular controls&lt;/li&gt;
&lt;li&gt;Document all processing activities (Article 30 records)&lt;/li&gt;
&lt;li&gt;Audit all third-party services for EU data processing&lt;/li&gt;
&lt;li&gt;Set up a data subject request tracking system&lt;/li&gt;
&lt;li&gt;Configure appropriate retention periods and automated cleanup&lt;/li&gt;
&lt;li&gt;Establish a breach notification process with 72-hour timeline&lt;/li&gt;
&lt;li&gt;Sign DPAs with all sub-processors (Deploynix, Hetzner, email provider, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GDPR compliance is not a destination. It is a continuous practice. The combination of Laravel's expressive framework and Deploynix's EU-friendly infrastructure gives you the tools to build applications that respect your users' privacy rights while running on infrastructure that keeps their data where it belongs.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>laravel</category>
      <category>gdpr</category>
      <category>dataprotection</category>
    </item>
    <item>
      <title>Hosting Laravel in the EU: GDPR Compliance with Hetzner and Deploynix</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Mon, 20 Apr 2026 22:00:05 +0000</pubDate>
      <link>https://dev.to/deploynix/hosting-laravel-in-the-eu-gdpr-compliance-with-hetzner-and-deploynix-3abe</link>
      <guid>https://dev.to/deploynix/hosting-laravel-in-the-eu-gdpr-compliance-with-hetzner-and-deploynix-3abe</guid>
      <description>&lt;p&gt;If you serve European customers, GDPR is not optional. It is not a suggestion, a best practice, or something you can worry about later. Since May 2018, the General Data Protection Regulation has been the law across the European Economic Area, and the fines for non-compliance are not theoretical. Companies of every size have been penalized, and regulators are getting more aggressive, not less.&lt;/p&gt;

&lt;p&gt;For Laravel developers, GDPR compliance starts with a fundamental question: where does your data physically live? The regulation does not prohibit transferring personal data outside the EU, but it imposes strict conditions on such transfers. The simplest path to compliance is keeping EU user data on EU soil, and that is exactly what Deploynix and Hetzner make possible.&lt;/p&gt;

&lt;p&gt;This guide walks you through setting up a fully EU-resident Laravel infrastructure using Deploynix with Hetzner as your cloud provider, covering everything from server provisioning to backups to the operational practices that keep you compliant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Data Residency Matters Under GDPR
&lt;/h2&gt;

&lt;p&gt;GDPR's data transfer rules (Articles 44-49) restrict transferring personal data to countries outside the EEA unless specific safeguards are in place. For years, many companies relied on the EU-US Privacy Shield framework to justify transatlantic data transfers, but the Court of Justice of the European Union invalidated that framework in the Schrems II decision. While the EU-US Data Privacy Framework has since been established as a replacement, relying on adequacy decisions introduces regulatory risk. Frameworks can be challenged and invalidated.&lt;/p&gt;

&lt;p&gt;Keeping data within the EU eliminates this entire category of risk. If personal data never leaves EU-based servers, you do not need to worry about transfer mechanisms, adequacy decisions, or supplementary measures. Your data residency story is simple and defensible.&lt;/p&gt;

&lt;p&gt;This is especially important for applications in sensitive sectors like healthcare, fintech, legal services, and education, where data protection authorities scrutinize data handling practices more closely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Hetzner for EU Hosting
&lt;/h2&gt;

&lt;p&gt;Hetzner is a German cloud provider with data centers exclusively in the EU and Finland. Unlike hyperscale providers that operate globally and require you to carefully select regions and verify that your data does not replicate elsewhere, Hetzner's entire infrastructure is European by design.&lt;/p&gt;

&lt;p&gt;Hetzner's data centers are located in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nuremberg, Germany (multiple facilities)&lt;/li&gt;
&lt;li&gt;Falkenstein, Germany (multiple facilities)&lt;/li&gt;
&lt;li&gt;Helsinki, Finland&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these locations are within the EEA, which means any server you provision on Hetzner through Deploynix is automatically EU-resident. There is no risk of your data being routed through a US data center for caching, failover, or load balancing because Hetzner does not have US data centers.&lt;/p&gt;

&lt;p&gt;Beyond data residency, Hetzner offers practical advantages for GDPR compliance. As a German company, Hetzner is directly subject to EU data protection law and German federal data protection regulations, which are among the strictest in the world. They provide a Data Processing Agreement (Auftragsverarbeitungsvertrag) that meets GDPR Article 28 requirements, and their technical and organizational measures are documented and auditable.&lt;/p&gt;

&lt;p&gt;From a performance and cost perspective, Hetzner is also exceptionally competitive. Their dedicated vCPU servers deliver excellent performance for Laravel applications at price points significantly below comparable offerings from US-based cloud providers. You do not have to pay a premium for EU compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up EU-Only Infrastructure on Deploynix
&lt;/h2&gt;

&lt;p&gt;Deploynix makes it straightforward to build a fully EU-resident infrastructure. Here is how to set up a complete Laravel production environment using only Hetzner servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Connect Hetzner as Your Cloud Provider
&lt;/h3&gt;

&lt;p&gt;In your Deploynix organization settings, add Hetzner as a cloud provider by providing your Hetzner API token. Deploynix will use this token to provision and manage servers on your behalf within your Hetzner account.&lt;/p&gt;

&lt;p&gt;You retain full ownership and control of your Hetzner account. Deploynix manages the servers through the API, but the servers, the data on them, and the Hetzner account itself belong to you. If you ever stop using Deploynix, your servers continue running exactly as they are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Provision Your Server Infrastructure
&lt;/h3&gt;

&lt;p&gt;For a GDPR-compliant Laravel production environment, we recommend the following server setup, all provisioned on Hetzner through Deploynix:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App Server:&lt;/strong&gt; Your primary application server running your Laravel application. Choose FrankenPHP, Swoole, or RoadRunner as your Octane driver for optimal performance. Deploynix configures Nginx, PHP, and your Octane driver automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database Server:&lt;/strong&gt; A dedicated server running MySQL, MariaDB, or PostgreSQL. Running your database on a separate server is both a performance best practice and a compliance advantage: it isolates personal data on a server that you can apply additional access controls to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Server:&lt;/strong&gt; A dedicated Valkey server for caching, sessions, and queue management. If you store any personal data in your cache or sessions, this server is also within your EU-resident infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker Server:&lt;/strong&gt; A dedicated server for processing queue jobs. Jobs that handle personal data, such as sending emails, generating reports, or processing user exports, execute on EU-resident infrastructure.&lt;/p&gt;

&lt;p&gt;When provisioning each server, select a Hetzner datacenter location. Whether you choose Nuremberg, Falkenstein, or Helsinki depends on your latency requirements and preferences, but all options are EU-resident.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Configure Firewall Rules
&lt;/h3&gt;

&lt;p&gt;GDPR requires appropriate technical measures to protect personal data (Article 32). Deploynix's firewall management lets you configure strict access rules for each server.&lt;/p&gt;

&lt;p&gt;At minimum, configure the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your database server should only accept connections from your app server and worker server IP addresses. Block all other inbound traffic on the database port.&lt;/li&gt;
&lt;li&gt;Your cache server should similarly restrict connections to only your app and worker servers.&lt;/li&gt;
&lt;li&gt;SSH access should be limited to your team's IP addresses or accessed exclusively through the Deploynix web terminal.&lt;/li&gt;
&lt;li&gt;Your app server should only expose ports 80 and 443 to the public internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These firewall rules are configured directly in the Deploynix dashboard and applied at the server level. Document these measures as part of your GDPR Article 32 technical safeguards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure EU-Only Backups
&lt;/h3&gt;

&lt;p&gt;Database backups must also remain within the EU to maintain your data residency posture. Deploynix supports several backup storage providers, and for EU compliance, you have several options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hetzner Storage Boxes:&lt;/strong&gt; Hetzner offers storage boxes that are physically located in their EU data centers. These are ideal for keeping backups within the same provider and the same jurisdiction as your servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS S3 with EU Region:&lt;/strong&gt; If you prefer S3, create your backup bucket in an EU region such as &lt;code&gt;eu-central-1&lt;/code&gt; (Frankfurt) or &lt;code&gt;eu-west-1&lt;/code&gt; (Ireland). Configure the bucket with no cross-region replication to ensure data stays in the selected region.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wasabi EU:&lt;/strong&gt; Wasabi offers EU storage locations including Amsterdam and Frankfurt. Their S3-compatible API works seamlessly with Deploynix's backup system.&lt;/p&gt;

&lt;p&gt;Configure your backup schedule in Deploynix to run daily database backups. We recommend retaining daily backups for at least 30 days and weekly backups for a year, but adjust based on your data retention policy and legal requirements.&lt;/p&gt;

&lt;p&gt;Enable encryption for backups. Deploynix encrypts backup data, and you should also enable server-side encryption on your storage bucket so that backups are encrypted at rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: SSL Certificate Configuration
&lt;/h3&gt;

&lt;p&gt;SSL encryption for data in transit is a baseline GDPR requirement. Deploynix automatically provisions Let's Encrypt SSL certificates for your sites with zero configuration needed.&lt;/p&gt;

&lt;p&gt;For DNS validation, which is required for wildcard certificates, Deploynix integrates with Cloudflare, DigitalOcean DNS, AWS Route 53, and Vultr DNS. If you are using Cloudflare for DNS management (common even when hosting on Hetzner), the DNS management layer does not affect data residency because DNS records are metadata about where to route traffic, not personal data.&lt;/p&gt;

&lt;p&gt;Your application traffic flows directly from the user's browser to your Hetzner server over HTTPS. It does not pass through Cloudflare unless you have enabled Cloudflare's proxy mode. If you want to ensure traffic does not pass through non-EU infrastructure, use Cloudflare in DNS-only mode (grey cloud) rather than proxied mode (orange cloud).&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational Practices for GDPR Compliance
&lt;/h2&gt;

&lt;p&gt;Infrastructure is only one piece of the GDPR puzzle. Your operational practices matter equally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Control
&lt;/h3&gt;

&lt;p&gt;Deploynix's organization roles (Owner, Admin, Manager, Developer, Viewer) let you implement the principle of least privilege for server access. Not everyone on your team needs access to the database server or the ability to trigger deployments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers can view server details and deployment logs but cannot modify server configurations or access the production database.&lt;/li&gt;
&lt;li&gt;Managers can trigger deployments and view more detailed server information.&lt;/li&gt;
&lt;li&gt;Admins have broader access but can be restricted from billing and organization-level settings.&lt;/li&gt;
&lt;li&gt;Owners have full access to everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Map these roles to your team structure so that only team members who need access to personal data for their job function actually have it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging and Audit Trails
&lt;/h3&gt;

&lt;p&gt;GDPR requires that you can demonstrate compliance (the accountability principle, Article 5(2)). Deploynix maintains logs of all server management actions: who deployed what, when servers were provisioned or modified, when firewall rules were changed, and when backups were created or restored.&lt;/p&gt;

&lt;p&gt;These audit logs serve double duty. They help you maintain operational awareness and provide evidence of your security practices if a data protection authority ever asks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incident Response
&lt;/h3&gt;

&lt;p&gt;Article 33 of GDPR requires notifying the relevant supervisory authority within 72 hours of becoming aware of a personal data breach. Deploynix's health alerts and monitoring give you early warning of potential security incidents, including unauthorized access attempts, unusual server behavior, and service disruptions.&lt;/p&gt;

&lt;p&gt;Configure your health alerts to notify your team immediately via the fastest channel available. When an alert fires, you need to assess quickly whether personal data may have been compromised and start your 72-hour breach notification clock if necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Processing Agreement
&lt;/h3&gt;

&lt;p&gt;As a tool that manages your servers, Deploynix acts as a data processor in certain contexts. We provide a Data Processing Agreement that meets GDPR Article 28 requirements. Make sure you also have a DPA in place with Hetzner and any other sub-processors in your stack (email providers, payment processors, analytics services).&lt;/p&gt;

&lt;p&gt;Maintain a list of all sub-processors that handle personal data from your application. GDPR requires that you can identify every entity involved in processing personal data on your behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do not assume EU servers mean full compliance.&lt;/strong&gt; Data residency is necessary but not sufficient. You still need lawful bases for processing, privacy notices, data subject rights mechanisms, and proper consent management in your application code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not forget about logs.&lt;/strong&gt; Application logs, error tracking services, and monitoring tools often capture personal data (IP addresses, email addresses, user IDs). If you send logs to a service hosted outside the EU, you have a data transfer. Either choose EU-hosted logging services or anonymize personal data before shipping logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not overlook third-party services.&lt;/strong&gt; Your servers may be in the EU, but if your application calls APIs hosted in the US (analytics, email delivery, payment processing), personal data may still leave the EU during those API calls. Audit every external service your application communicates with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not ignore email delivery.&lt;/strong&gt; If your Laravel application sends emails containing personal data, the email delivery service is processing that data. Choose an email provider that offers EU-based infrastructure or has appropriate data transfer safeguards in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deploynix Advantage
&lt;/h2&gt;

&lt;p&gt;Building a GDPR-compliant infrastructure manually is possible but tedious and error-prone. Deploynix streamlines the process by letting you provision, configure, and manage EU-only infrastructure through a single dashboard. Hetzner integration is first-class, firewall rules are straightforward to configure, backups can be directed to EU-only storage, and SSL is automatic.&lt;/p&gt;

&lt;p&gt;More importantly, Deploynix gives you the operational tools, such as monitoring, health alerts, access controls, and audit logs, that support ongoing compliance. GDPR is not a one-time checkbox. It is a continuous obligation, and having the right tooling makes that obligation manageable.&lt;/p&gt;

&lt;p&gt;Your European users trust you with their data. Deploynix and Hetzner make it possible to honor that trust with infrastructure that is European from the ground up.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>gdpr</category>
      <category>dataresidency</category>
      <category>euhosting</category>
    </item>
    <item>
      <title>The Deploynix Roadmap: What We're Building Next and Why</title>
      <dc:creator>Deploynix</dc:creator>
      <pubDate>Mon, 20 Apr 2026 10:00:05 +0000</pubDate>
      <link>https://dev.to/deploynix/the-deploynix-roadmap-what-were-building-next-and-why-4c08</link>
      <guid>https://dev.to/deploynix/the-deploynix-roadmap-what-were-building-next-and-why-4c08</guid>
      <description>&lt;p&gt;Building a server management platform is a never-ending exercise in prioritization. Every week, we receive feature requests from customers, identify improvements through our own dogfooding, and spot opportunities in the evolving Laravel ecosystem. The challenge is never "what should we build?" It is "what should we build next?"&lt;/p&gt;

&lt;p&gt;We believe in building in public. Deploynix exists because Laravel developers deserve a deployment platform built specifically for them, and that means the community should have a voice in where the platform goes. This post lays out our roadmap transparently: what we are actively working on, what is planned for the near future, what is on our radar for later, and how we decide what to prioritize.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Prioritize
&lt;/h2&gt;

&lt;p&gt;Before diving into specific features, it helps to understand how we decide what gets built. We evaluate every potential feature against four criteria:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; How many customers will benefit, and how significantly? A feature that saves every customer five minutes per deployment ranks higher than a feature that solves a niche problem for a handful of users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alignment:&lt;/strong&gt; Does this feature strengthen our core value proposition as a Laravel-first deployment platform? We intentionally avoid becoming a generic cloud management tool. Every feature should make deploying and managing Laravel applications specifically better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Effort:&lt;/strong&gt; How long will it take to build, and what is the ongoing maintenance burden? A feature that takes a week to build but requires constant attention is less attractive than one that takes two weeks but runs reliably with minimal maintenance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Urgency:&lt;/strong&gt; Is there a time-sensitive reason to build this now? Security features, ecosystem compatibility updates, and breaking-change responses get priority regardless of other criteria.&lt;/p&gt;

&lt;p&gt;We also weight customer feedback heavily. When multiple customers independently request the same feature, that signal is hard to ignore. Our support conversations, community discussions, and direct feedback all feed into a prioritization scorecard that we review weekly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Currently in Development
&lt;/h2&gt;

&lt;p&gt;These features are actively being built and will ship in the coming weeks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Support
&lt;/h3&gt;

&lt;p&gt;This is the most requested feature we have ever received, and we are deep into development. Container support will allow Deploynix to manage Docker-based deployments alongside our traditional server-based approach.&lt;/p&gt;

&lt;p&gt;The implementation focuses on what Laravel developers actually need from containers rather than exposing the full complexity of container orchestration. You will be able to define your application's container configuration through a simple Dockerfile or a Deploynix-managed template, and Deploynix will handle building, pushing, and deploying containers to your servers.&lt;/p&gt;

&lt;p&gt;We are not building Kubernetes. We are building container deployment that feels as straightforward as our current git-based deployments. You push code, Deploynix builds the container, and deploys it with zero downtime. The same deploy scripts, rollback capabilities, and monitoring you already use will work with containerized applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Transfer Between Organizations
&lt;/h3&gt;

&lt;p&gt;Currently, if you need to move a server from one organization to another, it requires a manual process through our support team. We are building a self-service transfer system that lets organization owners transfer servers cleanly, including all associated sites, databases, SSL certificates, and configurations.&lt;/p&gt;

&lt;p&gt;This matters particularly for agencies that build projects for clients and then hand off the infrastructure. The transfer will preserve all server history and configurations while changing ownership and billing responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhanced Deployment Metrics
&lt;/h3&gt;

&lt;p&gt;We are adding detailed deployment analytics to help teams understand their deployment patterns. This includes deployment frequency tracking, average deployment duration, success and failure rates over time, and rollback frequency. These metrics will be available both in the dashboard and through the API.&lt;/p&gt;

&lt;p&gt;The goal is not just vanity metrics. Understanding that your deployments have gotten 30% slower over the past month, or that a particular deploy script step fails 5% of the time, gives you actionable information to improve your pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planned for the Near Term
&lt;/h2&gt;

&lt;p&gt;These features have been scoped and designed and will enter active development once current work ships.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Region Deployment
&lt;/h3&gt;

&lt;p&gt;For applications that serve a global audience, deploying to a single region means some users experience high latency. Multi-region deployment will allow you to define a deployment that rolls out across servers in multiple regions, with configurable ordering and health checks between stages.&lt;/p&gt;

&lt;p&gt;This builds on our existing load balancer infrastructure. You will be able to set up geographic load balancing so that European users hit your Hetzner servers while North American users hit your Vultr or DigitalOcean servers, all deployed and managed as a single application through Deploynix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Read Replicas
&lt;/h3&gt;

&lt;p&gt;Managing MySQL, MariaDB, or PostgreSQL read replicas is a common scaling pattern for Laravel applications, but setting up replication correctly is error-prone. Deploynix will automate the provisioning and configuration of read replicas, including the initial data sync, replication monitoring, and automatic failover.&lt;/p&gt;

&lt;p&gt;On the Laravel side, we will provide configuration guidance for setting up your &lt;code&gt;database.php&lt;/code&gt; config to use read and write connections effectively, so your application can distribute query load across replicas without code changes beyond configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team Notifications and Webhooks
&lt;/h3&gt;

&lt;p&gt;Currently, Deploynix sends notifications about deployments, health alerts, and other events through the dashboard. We are expanding this with configurable notification channels: Slack, Discord, Microsoft Teams, email digests, and custom webhooks.&lt;/p&gt;

&lt;p&gt;Custom webhooks in particular will enable powerful integrations. You will be able to trigger external CI/CD pipelines, update status pages, notify project management tools, or feed deployment data into your own analytics systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Staging Environments
&lt;/h3&gt;

&lt;p&gt;One-click staging environment provisioning is something we have wanted to build since day one. The feature will let you clone your production server configuration into a staging environment that mirrors production as closely as possible, including database schema, server configuration, and environment variables with sensitive values masked.&lt;/p&gt;

&lt;p&gt;Staging environments will support automatic deployment from a staging branch, so your team can test changes in a production-like environment before merging to your deployment branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the Radar
&lt;/h2&gt;

&lt;p&gt;These features are things we are thinking about seriously but have not committed to specific timelines. They may move into active development based on community feedback and strategic priorities.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitOps Configuration
&lt;/h3&gt;

&lt;p&gt;Instead of configuring servers and deployments through the Deploynix dashboard, GitOps configuration would let you define your infrastructure in a YAML or JSON file committed to your repository. Changes to that configuration file would trigger Deploynix to reconcile the actual infrastructure with the desired state.&lt;/p&gt;

&lt;p&gt;This appeals strongly to teams that want infrastructure changes to go through the same code review process as application changes. It also enables reproducibility: you could spin up an identical infrastructure stack by pointing Deploynix at a configuration file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-In CI/CD
&lt;/h3&gt;

&lt;p&gt;Several customers have asked for basic CI/CD capabilities directly in Deploynix, specifically running tests before deployment. Currently, most teams use GitHub Actions, GitLab CI, or Bitbucket Pipelines for their test suite and then deploy through Deploynix once tests pass.&lt;/p&gt;

&lt;p&gt;We are evaluating whether building lightweight test running into Deploynix would be valuable enough to justify the complexity, or whether deeper integration with existing CI/CD providers would serve customers better. The honest answer is we are not sure yet, and we want to hear more from the community before committing to an approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed Redis and Managed Database Services
&lt;/h3&gt;

&lt;p&gt;Some customers have asked about managed database and cache offerings where Deploynix handles high availability, automated failover, and point-in-time recovery at the infrastructure level rather than the application level. This would be a significant expansion of our service scope, moving from server management into managed service territory.&lt;/p&gt;

&lt;p&gt;We are cautious about this one. Managed databases done well require deep operational expertise and 24/7 on-call support. Done poorly, they create more problems than they solve. We would rather not offer this feature than offer a mediocre version of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile Application
&lt;/h3&gt;

&lt;p&gt;A Deploynix mobile app for monitoring servers and triggering deployments on the go comes up regularly in feedback. The core use case is clear: you get a health alert on your phone, and you want to check the dashboard, view logs, or trigger a rollback without opening a laptop.&lt;/p&gt;

&lt;p&gt;We are leaning toward a progressive web app approach rather than native iOS and Android apps. A well-built PWA would give us the push notifications and offline capabilities that matter most while letting us maintain a single codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community-Driven Features
&lt;/h2&gt;

&lt;p&gt;Some of our best features have come directly from community feedback. Scheduled deployments, for example, started as a feature request from a customer who needed to deploy database migrations during off-peak hours. Vanity domains on &lt;code&gt;deploynix.cloud&lt;/code&gt; came from indie developers who wanted to share staging sites without configuring custom domains.&lt;/p&gt;

&lt;p&gt;We actively track every feature request and tag them by theme. When we see clusters of requests around the same problem, that signals real demand. Here are the themes generating the most community interest right now:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better log management:&lt;/strong&gt; Customers want centralized log aggregation across all servers in a project, with search, filtering, and alerting. This is firmly on our radar and will likely move into active development soon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment approval workflows:&lt;/strong&gt; Teams with compliance requirements want the ability to require approval from specific team members before a deployment proceeds. This ties into our existing organization roles (Owner, Admin, Manager, Developer, Viewer) and would add a configurable approval gate to the deployment pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import existing servers:&lt;/strong&gt; Several customers have servers already running Laravel applications on other platforms or manually configured servers that they want to bring under Deploynix management without reprovisioning. This is technically complex because existing servers have unpredictable configurations, but we are exploring approaches that could work for common setups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform provider:&lt;/strong&gt; DevOps-oriented teams want to manage Deploynix resources through Terraform. Since we already have a comprehensive API with Sanctum token authentication and granular scopes, building a Terraform provider is mostly a matter of wrapping our API in the Terraform provider framework. This is something we may open-source and accept community contributions for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Will Not Build
&lt;/h2&gt;

&lt;p&gt;Being transparent about our roadmap also means being honest about what falls outside our scope. Deploynix is a Laravel-first server management platform, and we are intentional about maintaining that focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will not become a generic cloud provider.&lt;/strong&gt; We provision servers on DigitalOcean, Vultr, Hetzner, Linode, AWS, and custom providers, but we are not competing with those providers. We are a management layer that makes them easier to use for Laravel deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will not build a CMS or application framework.&lt;/strong&gt; Deploynix deploys and manages Laravel applications. We do not generate application code, scaffold projects, or provide application-level services beyond what is needed for deployment and server management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will not chase every DevOps trend.&lt;/strong&gt; Service mesh, serverless edge functions, AI-powered auto-scaling: these are interesting technologies, but we will only adopt them when they genuinely improve the experience of deploying Laravel applications. We are not going to add complexity for the sake of a marketing checkbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Influence the Roadmap
&lt;/h2&gt;

&lt;p&gt;Your voice matters more than you might think. Here is how to make sure your feedback reaches us:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature requests:&lt;/strong&gt; Submit them through the Deploynix dashboard or email our support team directly. Specific, detailed requests with real use cases are the most helpful. "I want container support" is useful. "I need container support because I run a Laravel app with a Python ML service sidecar and managing them as separate deployments is painful" is extremely useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upvoting:&lt;/strong&gt; When we share roadmap updates, engage with the features that matter most to you. Seeing concentrated interest helps us prioritize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beta testing:&lt;/strong&gt; For major features, we run beta programs with interested customers. Beta testers get early access and direct influence over how the feature ships. If you want to be part of the container support beta, let us know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Commitment
&lt;/h2&gt;

&lt;p&gt;Roadmaps are promises, and we take promises seriously. We would rather under-promise and over-deliver than announce flashy features that slip indefinitely. Every feature listed in the "Currently in Development" section will ship. Features in "Planned" have high confidence of happening. "On the Radar" items are genuine considerations, not marketing fluff.&lt;/p&gt;

&lt;p&gt;We will continue to publish roadmap updates regularly so you can track our progress and hold us accountable. Building Deploynix is a partnership with our community, and the best deployment platform for Laravel developers is one that Laravel developers help shape.&lt;/p&gt;

</description>
      <category>deploynix</category>
      <category>laravel</category>
      <category>deploynixcommunity</category>
      <category>productupdates</category>
    </item>
  </channel>
</rss>
