php runtime and server environment
Professional Runtime Orientation
Professional PHP runs in more than one shape. The same application may use PHP-FPM for web requests, CLI for console commands, workers for queues, cron for scheduled jobs, and a long-running runtime for high-throughput HTTP or websocket work.
The runtime matters because it decides how code starts, how long memory lives, how configuration is loaded, how failures are logged, and whether state is reset between requests.
The Common Production Shape
A traditional PHP web request usually flows like this:
browser
-> Nginx / Apache / Caddy
-> PHP-FPM worker
-> framework front controller
-> response
The web server handles TLS, static files, request buffering, and routing PHP requests to PHP-FPM. PHP-FPM keeps a pool of PHP worker processes ready to execute requests.
In this model, each request gets a fresh application lifecycle. After the response, most in-memory state is discarded. That request/response reset is one reason classic PHP is forgiving for beginners.
CLI Is A Different Runtime
CLI PHP runs commands in a terminal or process manager. It is used for Composer, framework commands, migrations, queue workers, cron jobs, import scripts, and maintenance tools.
php -v
php framework-command migrate
php bin/console cache:clear
php worker.php
A small diagnostic can prove which runtime is executing:
<?php
declare(strict_types=1);
echo 'SAPI: ' . PHP_SAPI . PHP_EOL;
echo 'Memory limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'APP_ENV: ' . (getenv('APP_ENV') ?: 'not set') . PHP_EOL;
// Example CLI output:
// SAPI: cli
// Memory limit: 128M
// APP_ENV: not set
CLI and FPM can load different php.ini files and different environment variables. When a command works but the website fails, compare the runtime first.
PHP-FPM Workers
PHP-FPM manages worker processes. Each worker can handle many requests over its lifetime, but the application code normally runs one request at a time in that worker.
Important FPM concepts:
- pools define groups of workers with their own user, limits, and configuration
pm.max_childrenlimits how many requests can run at once in a pool- slow logs help find requests that take too long
- status pages help show busy and idle workers
- web server configuration must pass the correct script filename to FPM
FPM problems often appear as 502 Bad Gateway, timeouts, slow requests, or PHP files being downloaded instead of executed.
Queue Workers And Cron
Queue workers and cron scripts are CLI processes, but they behave differently from one-off commands.
A migration command starts, does work, and exits. A queue worker may stay alive for hours and process thousands of jobs.
<?php
declare(strict_types=1);
while (true) {
echo 'Process one job' . PHP_EOL;
sleep(1);
}
That loop is intentionally simple, but it shows the important difference: memory and static state can survive between jobs. A leak that is harmless in one web request can become serious in a long-running worker.
Long-Running Runtimes
Tools such as RoadRunner, FrankenPHP worker mode, Swoole, OpenSwoole, and Laravel Octane keep PHP applications in memory between requests.
That can improve performance because the framework does not need to bootstrap from scratch on every request. The tradeoff is that application state can leak between users if code assumes the classic request/response reset.
This is safe:
<?php
declare(strict_types=1);
function greetingFor(string $name): string
{
return 'Hello ' . $name;
}
echo greetingFor('Ada') . PHP_EOL;
// Prints:
// Hello Ada
This is risky in a long-running runtime:
<?php
declare(strict_types=1);
final class CurrentUser
{
public static ?int $id = null;
}
CurrentUser::$id = 42;
echo 'Current user: ' . CurrentUser::$id . PHP_EOL;
// Prints:
// Current user: 42
Static request-specific state can survive longer than expected. In long-running runtimes, per-request data should usually live in request objects, scoped services, or explicit context that is reset after each request.
Event Loops
ReactPHP and Amp use event loops for asynchronous work. The idea is that one process can manage multiple non-blocking operations without waiting idly for each one to finish.
Event-loop code is powerful for sockets, websockets, streaming, and high-concurrency I/O. It also changes how you think about blocking calls.
In an event-loop application, a normal blocking database query, file read, or HTTP request can pause the whole loop unless it uses compatible non-blocking APIs.
You do not need to start with event loops as a junior PHP developer. You do need to recognise them so you do not accidentally add blocking code to a non-blocking application.
Runtime Choice Changes Debugging
When a production issue appears, first identify the runtime:
- browser request through FPM
- CLI command
- queue worker
- cron job
- websocket server
- event-loop process
- long-running HTTP worker
Then ask runtime-specific questions:
- Which
php.inidid it load? - Which user owns the process?
- Which environment variables are available?
- Does memory reset after each request or job?
- Where are errors logged?
- Is the process supervised by systemd, Supervisor, Docker, Kubernetes, or something else?
- Does changing code require a reload or worker restart?
Those questions often find the bug faster than reading application code first.
What You Should Be Able To Do
After this lesson, you should be able to describe the difference between CLI, PHP-FPM, queue workers, long-running runtimes, and event-loop runtimes. You should also be able to explain why state leakage is a real risk when PHP processes stay alive.
For junior PHP work, this matters because production PHP is not just index.php. You need to know what kind of process is running your code before you can debug it professionally.
Practice
Practice: Identify The Runtime Before Debugging
Create a short runtime investigation note for a PHP application.
Task
Write a checklist for an application that has:
- normal web requests through Nginx and PHP-FPM
- a queue worker started from CLI
- a scheduled cron command
- a possible long-running worker runtime
For each runtime, list:
- how you would prove it is the code path being used
- which configuration or environment values you would check
- what kind of state-leak or restart issue could happen
Include one small PHP diagnostic script or snippet that prints PHP_SAPI, memory_limit, and APP_ENV.
Afterward, add a short note explaining why the same PHP code can behave differently in CLI and web runtimes.
Show solution
This solution starts with a small diagnostic, then maps the checks to each runtime.
<?php
declare(strict_types=1);
echo 'SAPI: ' . PHP_SAPI . PHP_EOL;
echo 'Memory limit: ' . ini_get('memory_limit') . PHP_EOL;
echo 'APP_ENV: ' . (getenv('APP_ENV') ?: 'not set') . PHP_EOL;
// Example CLI output:
// SAPI: cli
// Memory limit: 128M
// APP_ENV: local
For web requests through Nginx and PHP-FPM, prove the path by hitting a known route and checking the web server and FPM logs. Check the FPM pool, loaded php.ini, document root, SCRIPT_FILENAME, environment variables, and whether PHP errors are logged where you expect.
For a queue worker, prove the path by dispatching a known test job and checking the worker log. Check the CLI PHP version, CLI php.ini, supervisor configuration, memory limit, environment variables, retry settings, and whether the worker needs a restart after deployment.
For cron, prove the path by checking the crontab entry and writing a timestamped log line from the command. Check the user that runs the cron, working directory, PATH, environment variables, and absolute paths.
For a long-running worker runtime, prove the path through the runtime's status command, process list, logs, or a request that reports the runtime name. Check whether code changes require reloads, whether request-specific state is reset, and whether static properties or singletons can leak data between requests.
The same PHP code can behave differently in CLI and web runtimes because they may use different SAPIs, configuration files, users, working directories, extensions, environment variables, process lifetimes, and logging paths.