The request lifecycle — from nginx hit to response, step by step
Concept
The Laravel request lifecycle describes every step from the moment a client's HTTP request hits your server to the moment a response is sent back. Understanding this pipeline in depth is the mental model that makes debugging, performance tuning, and extending the framework intuitive rather than mysterious.
The journey begins at the web server level. Nginx (or Apache) receives the raw TCP connection, reads the HTTP request, and — because of FastCGI configuration — proxies it to PHP-FPM. PHP-FPM picks an available worker process and tells it to execute public/index.php. This single file is the only entry point for every web request in Laravel.
Inside public/index.php, Composer's autoloader is required first, then the application bootstrap file bootstrap/app.php is included. This file creates the Illuminate\Foundation\Application instance — the IoC container that holds the entire framework. It binds the HTTP Kernel, Console Kernel, and Exception Handler to their interfaces. The Application instance is returned and stored in $app.
Next, $app->make(Illuminate\Contracts\Http\Kernel::class) resolves the HTTP Kernel. The Kernel stores a list of bootstrappers — classes that prepare the environment before any request processing occurs. These are: LoadEnvironmentVariables, LoadConfiguration, HandleExceptions, RegisterFacades, RegisterProviders, and BootProviders. They run in exactly that order via $kernel->bootstrap().
Once bootstrapped, the request enters the middleware pipeline. The global middleware stack defined in App\Http\Kernel::$middleware wraps the route dispatch. Each middleware calls $next($request) to pass execution deeper. At the centre, the Router matches the URI and method to a registered route, resolves the controller or closure, and executes it. The return value (a Response or a value that can be converted to one) travels back up through the middleware stack. Finally, $response->send() writes headers and body to the output buffer, and $kernel->terminate() runs terminable middleware.
| Phase | Class responsible | What happens |
|---|---|---|
| Entry | public/index.php | Autoload, create Application, make Kernel |
| Bootstrap | Illuminate\Foundation\Http\Kernel | Run 6 bootstrappers in order |
| Pipeline | Illuminate\Pipeline\Pipeline | Global middleware wraps everything |
| Routing | Illuminate\Routing\Router | Match URI, resolve controller, execute |
| Response | Illuminate\Http\Response | Headers + body sent to client |
| Terminate | Kernel::terminate() | Terminable middleware cleanup |
A critical insight: service providers are not all booted immediately. Deferred providers are registered in a manifest file and only instantiated when their bindings are first resolved. This lazy-loading is why Laravel's bootstrap is fast even with dozens of providers.
Code Example
<?php
// public/index.php — annotated with what each line does
// 1. Start timing for performance monitoring
define('LARAVEL_START', microtime(true));
// 2. Maintenance mode check (bypasses the full bootstrap if in maintenance)
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
require $maintenance;
}
// 3. Load Composer's PSR-4 autoloader — maps class names to file paths
require __DIR__.'/../vendor/autoload.php';
// 4. Bootstrap the Application — returns Illuminate\Foundation\Application
// bootstrap/app.php binds: Http\Kernel, Console\Kernel, ExceptionHandler
$app = require_once __DIR__.'/../bootstrap/app.php';
// 5. Resolve the HTTP Kernel from the container
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
// 6. Capture the current PHP superglobals into an Illuminate\Http\Request
// Illuminate\Http\Request extends Symfony\Component\HttpFoundation\Request
$request = Illuminate\Http\Request::capture();
// 7. Run the full pipeline: bootstrap → middleware → routing → response
$response = $kernel->handle($request);
// 8. Write headers and body to the SAPI output
$response->send();
// 9. Terminable middleware (e.g. session write, logging) — after response is sent
$kernel->terminate($request, $response);<?php
// bootstrap/app.php — the Application factory (simplified)
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
// Laravel 11: configure() replaces the old manual bindings
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// Register global middleware, groups, aliases here
})
->withExceptions(function (Exceptions $exceptions) {
// Customize exception rendering/reporting here
})
->create(); // Returns the Application instanceInterview Q&A
Q: Walk me through the Laravel request lifecycle from nginx receiving a request to the response being sent.
The request hits Nginx, which proxies it via FastCGI to a PHP-FPM worker. That worker executes public/index.php, which loads the Composer autoloader, creates the Illuminate\Foundation\Application (the IoC container), and resolves the HTTP Kernel. The Kernel runs six bootstrappers in order — loading env vars, configuration, registering exception handlers, facades, service providers, and booting those providers. Then the request enters the global middleware pipeline via Illuminate\Pipeline\Pipeline. At the centre, the Router matches the route and executes the controller. The Response bubbles back up through middleware, gets sent via $response->send(), and then $kernel->terminate() runs terminable middleware (session writes, deferred logging) after the response is already on the wire.
Q: Why does public/index.php check for maintenance.php before loading the autoloader?
The maintenance mode file is generated by php artisan down. It contains PHP code that returns the maintenance response directly, before the full Laravel bootstrap — no autoloader, no container, no service providers. This means maintenance mode works even when Composer dependencies are broken or the application is in an inconsistent state. It's a safety net: the file is checked with a raw file_exists() and require, bypassing everything else.
Q: What is the difference between service provider register() and boot() phases, and when does each run in the lifecycle?
register() runs during the RegisterProviders bootstrapper, before any boot() call. Its only job is to bind things into the container — you must not access other services here because they may not be registered yet. boot() runs during the BootProviders bootstrapper, after all providers are registered. By the time boot() fires, every binding is available, so you can safely resolve services, register event listeners, macros, or validation rules. Violating this order — for example, resolving a service in register() that hasn't been registered yet — causes hard-to-trace BindingResolutionException errors.