Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Fastcgi Daemon Laravel Package

phpfastcgi/fastcgi-daemon

View on GitHub
Deep Wiki
Context7

Getting Started

Understand what this package is before you install it. phpfastcgi/fastcgi-daemon is a FastCGI responder — a server that binds a socket, speaks the FastCGI wire protocol, and hands you a PSR-7 ServerRequestInterface plus a prototype ResponseInterface. It is the thing you point fastcgi_pass at. It is not a FastCGI client, and it is not a Laravel application server in the Octane/RoadRunner sense. Read it as "a PHP-FPM replacement written in userland, for people who want to write their own FPM."

That framing matters for how you'll use it day-to-day: as a test double, a lab tool, and occasionally a tiny sidecar service — not as the runtime for your Laravel app.

composer require phpfastcgi/fastcgi-daemon

Pin the version. The last tagged release is 2018-08-14, which predates ReactPHP 1.x and PHP 8 strictness; composer why-not phpfastcgi/fastcgi-daemon is your first command, not your last.

Where to look first, in this order:

  1. FastCGIDaemon\Kernel\KernelInterface — the single seam. One method, handleRequest(ServerRequestInterface $request, ResponseInterface $response), returns a ResponseInterface. That's the whole extension model.
  2. FastCGIDaemon\Daemon — socket setup, connection acceptance, and run(). Also check ApplicationFactory in your pinned version; the factory signature moved around across releases.
  3. FastCGIDaemon\Http\Request / Http\Response — the concrete PSR-7 implementations. Read Request::getServerParams() carefully, because that array is your only link back to the FastCGI params nginx sent.
  4. phpfastcgi/connection in vendor/ — the protocol codec actually lives there, not here. If frames look wrong, the bug is usually one level down.
  5. The examples/ directory in the repo — it is more current than most blog posts about this package.

A minimal responder to prove the wiring end-to-end:

<?php
require __DIR__ . '/vendor/autoload.php';

use FastCGIDaemon\Kernel\KernelInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final class EchoKernel implements KernelInterface
{
    public function handleRequest(
        ServerRequestInterface $request,
        ResponseInterface $response
    ): ResponseInterface {
        $response->getBody()->write(json_encode([
            'method'  => $request->getMethod(),
            'uri'     => (string) $request->getUri(),
            'params'  => $request->getServerParams(),
        ], JSON_PRETTY_PRINT));

        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus(200);
    }
}

$daemon = FastCGIDaemon\Daemon::create(new EchoKernel(), 'tcp://127.0.0.1:9000');
$daemon->run(); // blocks; this is the event loop

Then in nginx:

location / {
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /var/www/public/index.php;
    fastcgi_param SCRIPT_NAME     /index.php;
}

Your first real use case should be contract testing, not serving Laravel. Stand this up in a CI container behind your actual nginx/Ingress config and assert on the params that arrive — SCRIPT_NAME, PATH_INFO, REQUEST_URI, HTTPS, HTTP_X_FORWARDED_*. It takes twenty minutes and catches the class of bug (wrong fastcgi_param, missing rewrite header, mismatched SCRIPT_FILENAME) that otherwise only surfaces in production.

Implementation Patterns

Pattern 1: Fake upstream to pin your nginx/Ingress contract

The highest-value, lowest-risk daily use. Your kernel records what it received and returns a canned response; the test asserts on the recorded array.

final class RecordingKernel implements KernelInterface
{
    public array $seen = [];

    public function handleRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $this->seen[] = [
            'params'  => $request->getServerParams(),
            'uri'     => (string) $request->getUri(),
            'headers' => $request->getHeaders(),
            'body'    => (string) $request->getBody(),
        ];

        return $response->withStatus(200);
    }
}

Drive it from PHPUnit by forking the daemon on an ephemeral port and hitting it through curl via your real nginx config, or by using the client half of phpfastcgi/connection directly for a round-trip with no web server in the loop. Both are worth having; the first validates nginx, the second validates the codec.

Pattern 2: Boot Laravel inside the kernel (one-shot)

This is the ambitious pattern and it works — it just buys you nothing over FPM. Each request boots the framework, exactly as FPM does, minus the process manager.

final class LaravelKernel implements KernelInterface
{
    public function handleRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        /** @var \Illuminate\Foundation\Application $app */
        $app = require __DIR__ . '/../bootstrap/app.php';
        $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        $params = $request->getServerParams();

        $symfony = \Illuminate\Http\Request::create(
            (string) $request->getUri(),
            $request->getMethod(),
            $request->getParsedBody() ?? [],   // usually empty — see Gotchas
            $request->getCookieParams(),
            $request->getUploadedFiles(),
            $this->toServerArray($params)
        );

        $symfony->headers->add($request->getHeaders());
        $symfony->server->set('CONTENT_TYPE', $request->getHeaderLine('Content-Type'));

        $laravelResponse = $app->handle($symfony);

        $response->getBody()->write((string) $laravelResponse->getContent());

        foreach ($laravelResponse->headers->allPreserveCase() as $name => $values) {
            $response = $response->withHeader($name, $values);
        }

        return $response->withStatus($laravelResponse->getStatusCode());
    }

    private function toServerArray(array $params): array
    {
        // Laravel/Symfony read SCRIPT_NAME, REQUEST_URI, PATH_INFO, QUERY_STRING,
        // SERVER_PROTOCOL, HTTP_HOST. FastCGI names HTTP_* for you; the rest you map.
        return $params + ['SCRIPT_NAME' => $params['SCRIPT_NAME'] ?? '/index.php'];
    }
}

Only do this if you have a specific reason (embedding Laravel in a larger ReactPHP process, single-binary deployments, protocol experiments). Otherwise Octane gives you the same result with worker reuse, lifecycle events, and a supported upgrade path.

Pattern 3: Compose with an existing ReactPHP loop

This is the one genuine advantage over FPM: the daemon is an event-loop participant. You can run a FastCGI endpoint alongside a WebSocket server, a queue consumer, or a timer in a single process, sharing state and a socket.

$loop   = React\EventLoop\Loop::get();
$daemon = FastCGIDaemon\Daemon::create(new HealthKernel(), 'tcp://0.0.0.0:9000', $loop);

$loop->addPeriodicTimer(30, fn() => $metrics->flush());
$loop->run();

Keep these services tiny and I/O-light. A /health responder or an internal routing endpoint is a good fit; a CRUD API is not.

Pattern 4: PSR-7 middleware around the kernel

KernelInterface is a single callable, so PSR-7 middleware composes cleanly. Because the daemon passes in the response prototype, write a small adapter that seeds a middleware stack, then copies the result onto the prototype before returning:

$stack = new class($kernel) implements KernelInterface {
    public function handleRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        $handler = fn (ServerRequestInterface $r) => $this->kernel->handleRequest($r, $response);

        foreach (array_reverse($this->middleware) as $m) {
            $next = $handler;
            $handler = fn (ServerRequestInterface $r) => $m->process($r, $next);
        }

        return $handler($request);
    }
};

Useful for logging, timing, CORS, and request-ID propagation around your kernel.

Pattern 5: Supervisor and process model

One process, single-threaded, no prefork. Run it under systemd or Supervisor with autorestart=true, and set stopwaitsecs generously — run() blocks, and shutdown happens on the loop, not on a signal handler you wrote. Scale by running N daemons on N ports and letting nginx upstream round-robin them; there is no cluster mode.

Gotchas and Tips

It's a responder, not a client. The most common first mistake is reaching for this to talk to PHP-FPM. It's the wrong direction. For a client, look at phpfastcgi/connection's client classes.

The 2018 release is the main risk. Expect conflicts with react/socket 1.x, and expect PHP 8 deprecations in the connection layer. Before investing, verify it runs on your target PHP with a five-minute smoke test. If it doesn't, the codebase is small — a "repositories": [{"type": "vcs", ...}] fork with a handful of PHP 8 fixes is often cheaper than picking a different tool.

Everything is single-threaded — blocking calls serialize the whole daemon. This is the killer for Laravel. A synchronous PDO query, a Redis GET, a Guzzle call, file_get_contents, sleep() — each one stops the event loop for every other in-flight request. Head-of-line blocking is not theoretical here. If you're not going all-in on async clients, you are strictly worse off than FPM.

$_SERVER does not exist. There are no superglobals in a daemon. Everything must come from $request->getServerParams(), and Laravel/Symfony's URL generation, redirects, and asset() helpers all read SCRIPT_NAME, REQUEST_URI, PATH_INFO, QUERY_STRING, SERVER_PROTOCOL, HTTP_HOST. If you skip the mapping, you get subtly wrong redirect URLs that test green and break behind a proxy.

getParsedBody() will be empty. FastCGI hands you a raw body stream. application/x-www-form-urlencoded and multipart/form-data are not parsed for you. If your app accepts form posts or uploads, you parse them yourself — and $_FILES never gets populated, so Laravel's UploadedFile handling needs hand-wiring or avoidance.

Responses are buffered. The daemon accumulates the body and writes it as one FastCGI response. No chunked streaming, no SSE, no X-Accel-Redirect-style passthrough. Large downloads consume memory proportional to the payload.

No worker reuse, so no config:cache-style boot amortization unless you deliberately hoist bootstrap out of handleRequest. If you do hoist it, you inherit the full Octane problem set: container singletons holding request-scoped state, Auth, Session, Request resolving to the previous request. Reset the container or don't hoist.

Timeouts fail asymmetrically. fastcgi_read_timeout on nginx will 504 the client while your daemon keeps grinding on the request. Tune both sides, and log request duration inside the kernel — that's the only place you'll see the truth.

Debugging is binary. FastCGI frames aren't readable. Reach for cgi-fcgi -bind -connect 127.0.0.1:9000 for manual probing, tcpdump -X on the loopback, and nginx error_log ... debug. When params look truncated or headers vanish, check CONTENT_LENGTH and REQUEST_METHOD first — the codec drops values it can't size correctly.

Assume no multiplexing. Design as one request per connection with no FCGI_KEEP_CONN reuse. Concurrency is cooperative and connection-count-bounded by the loop, not by a worker pool.

The kernel is your only extension point — there's no middleware pipeline, no event dispatcher, no lifecycle hooks. Everything you want (logging, timing, tracing, error pages) goes in a wrapper kernel or a decorator. Keep that wrapper thin and framework-free; you'll want to reuse it in tests.

Have an exit plan. If the goal is "run Laravel without PHP-FPM," reach for Octane or FrankenPHP first. Adopt this package when the goal is specifically to control the FastCGI layer — mocking an upstream, probing a proxy, or embedding an endpoint in a ReactPHP process. That's where it still earns its keep.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle