MDX Content: # Achieving Fast Response Times with Laravel and Nginx
Achieving very fast response times with a Laravel app behind Nginx is mostly about avoiding bootstrapping PHP/Laravel on every request.
You can accomplish this with:
variant="ordered"
items={[
{
content: ["Full-page caching at the Nginx level"]
},
{
content: ["Laravel response caching for situations where Nginx cannot cache everything"]
},
{
content: ["Static or partial rendering combined with AJAX for dynamic parts"]
}
]}
/>
Understanding the 300ms TTFB Problem
A 300 ms TTFB (time to first byte) for a small site with about 50 records is often caused by:
variant="unordered"
items={[
{
content: ["Laravel boot time (autoloading, service providers, configuration, middleware)"]
},
{
content: ["PHP-FPM process handling"]
},
{
content: ["Database/ORM overhead"]
}
]}
/>
Even with small datasets, the cost of spinning up the framework on every request is high compared to serving a static or cached response directly from Nginx.
Nginx FastCGI Cache
Nginx FastCGI cache is the key to implementing full-page caching in front of Laravel. It allows you to store the final HTML response on disk (or in memory) and serve it directly, bypassing PHP on subsequent requests.
Conceptually, you need to:
variant="unordered"
items={[
{
content: ["Define a cache zone"]
},
{
content: ["Set the cache path and size"]
},
{
content: ["Configure which responses to cache (and for how long)"]
},
{
content: ["Define cache keys that distinguish pages, languages, and other variants"]
}
]}
/>
A typical configuration includes fastcgi_cache_path to define the cache storage, fastcgi_cache_key (often including scheme, method, host, and URI), and settings like fastcgi_cache_valid 200 301 302 10m to cache successful responses for 10 minutes.
You can also use fastcgi_cache_use_stale to serve stale content when PHP is slow or failing and add an X-Cache header to inspect whether responses are HIT, MISS, or BYPASS.
This setup can reduce TTFB to just a few milliseconds for cached pages.
Controlling What Should Not Be Cached
A critical aspect of FastCGI caching is controlling what should not be cached. You must bypass the cache for logged-in users (session or authentication cookies), CSRF-protected forms, and admin or sensitive areas. This is typically done using Nginx variables and conditions based on cookies or request URIs.
For example, you can set a \$no_cache flag to 1 when a laravel_session cookie is detected or when the URI matches /admin, and then use fastcgi_cache_bypass \$no_cache; and fastcgi_no_cache \$no_cache; in the PHP location block.
There are numerous walk-throughs, such as DigitalOcean's guide to FastCGI caching, that provide step-by-step examples.
Laravel's Caching Mechanisms
When Nginx cannot fully cache a response (for example, on user-specific pages), you should rely on Laravel's own caching mechanisms to minimize server-side latency after the first request.
Laravel response caching (e.g., via the spatie/laravel-responsecache package) can store entire HTML responses in Redis or on disk so that subsequent requests are served from cache instead of being fully regenerated.
For database operations, especially in small datasets that rarely change, you can use Cache::remember() or rememberForever() to cache query results.
Furthermore, Laravel's deployment optimizations—php artisan config:cache, route:cache, and view:cache—help reduce the cost of bootstrapping the framework on every request.
An example is caching all Article records:
Cache::remember('articles.all', 3600, function () {
return Article::all();
});
This avoids hitting the database repeatedly when the underlying data is stable.
Static Shell with AJAX for Dynamic Content
For perceived performance and better Google PageSpeed scores, you can combine a static or cached HTML shell with AJAX for non-critical dynamic parts. In this pattern, Nginx serves an almost-static page very quickly—containing the core layout, styles, and primary content—while secondary or user-specific data is loaded asynchronously after the initial render.
The server returns a cached HTML template with placeholders and a small JavaScript file. After DOMContentLoaded, the JavaScript calls API endpoints like /api/latest-records using fetch() or AJAX and injects the returned JSON data into the DOM. This allows for fast first paint and interaction while still supporting dynamic functionality.
System-Level Optimizations
Beyond caching strategies, there are several system-level optimizations that improve the speed of uncached or first-time requests.
items={[
{
title: "PHP-FPM Tuning",
content: [
"Ensures that there are enough worker processes and that `pm` settings are configured to avoid delays from process spawning under load."
]
},
{
title: "OPcache",
content: [
"Enabling and configuring OPcache prevents PHP from re-parsing scripts on every request, significantly reducing CPU overhead."
]
},
{
title: "HTTP/2 and Compression",
content: [
"Enabling HTTP/2 and compression (gzip or Brotli) speeds up the transfer of HTML, CSS, and JavaScript assets."
]
}
]}
/>
Taken together, these optimizations complement Nginx and Laravel caching to provide both low latency and high throughput, ensuring that even cache misses perform adequately under real-world conditions.
For a small Laravel site with roughly 50 records and observed TTFB around 300 ms, a practical strategy is to combine Nginx FastCGI caching, Laravel optimizations, and selective use of AJAX for dynamic content.
Nginx FastCGI Caching
Begin by enabling FastCGI cache for all anonymous pages, caching 200/301/302 responses for several minutes or longer if content updates are infrequent. Configure rules to bypass the cache for authenticated users and administrative routes using cookie-based and URI-based conditions.
After deployments, warm the cache by programmatically requesting key URLs (with a cron job or a simple script using curl) so that the first real visitor receives a cache HIT instead of incurring the cost of a cold start.
Laravel Internal Optimizations
Next, enable Laravel's internal optimizations with:
php artisan config:cache
php artisan route:cache
And caching of expensive or frequently used queries. Where full-page caching is not possible—such as user dashboards or personalized pages—use response caching and query caching to mitigate the performance cost.
Layered Content Strategy
Finally, design pages so that above-the-fold and SEO-critical content is served from the cached HTML, while non-essential or secondary components (for example, "latest stats" or personalized suggestions) are fetched asynchronously through JSON APIs.
Top comments (0)