skip to content

In PHP, how does a script tell whether the CLI or a web server started it, and which names can it get?

level: middleimportance: should knowfreq 45%

answer

  1. php_sapi_name() or PHP_SAPI
  2. lowercase string, false on failure
  3. cli vs cli-server vs fpm-fcgi
  4. apache2handler for Apache's module
  5. $argv presence is not proof

basics

~10 s

php_sapi_name() and the PHP_SAPI constant name the server API running the script: cli from a terminal or cron, cli-server under php -S, fpm-fcgi under PHP-FPM, apache2handler under Apache's module, cgi-fcgi under php-cgi.

solid answer

~40 s

PHP runs inside a **SAPI** (Server API), the layer that connects the engine to its host. `php_sapi_name()` returns its name as a lowercase string (`string|false`), and the constant `PHP_SAPI` holds the same value. Common values: `cli` for the command line and cron jobs, `cli-server` for the `php -S` development server, `fpm-fcgi` for PHP-FPM, `apache2handler` for Apache's PHP module, `cgi-fcgi` for the `php-cgi` binary, plus `phpdbg`, `embed` and `litespeed`. A guard like `if (PHP_SAPI !== 'cli') { exit(1); }` keeps a cron-only script from being triggered over HTTP. Checking whether `$argv` is set is not reliable, because web SAPIs can populate it too; the manual recommends checking the SAPI name.

code

php · 11 lines
php
<?php
// send-queue.php: run by cron every five minutes
declare(strict_types=1);

if (PHP_SAPI !== 'cli') {
    http_response_code(403);
    exit('This script runs from the command line only.');
}

$sent = sendQueuedNewsletters(batchSize: 500);
fwrite(STDOUT, "Sent {$sent} messages\n");

go deeper

for a junior

Remember php_sapi_name() and PHP_SAPI, and that cli means a terminal or cron job.

for a middle

Name the common SAPIs, including cli-server, fpm-fcgi and apache2handler, and explain why the SAPI name beats checking $argv.

for a senior

Guard cron-only scripts by SAPI, keep them outside the document root, and remember that shell diagnostics show the CLI's configuration, not the web SAPI's.

for a principal

Treat entry points by SAPI as part of the security surface: decide which code may run over HTTP and enforce it structurally, not by convention.

## What a SAPI is The PHP engine never talks to a terminal or a web server directly. Between them sits a **SAPI**, a Server Application Programming Interface: a small adapter that tells the engine where input comes from, where output goes, how headers are sent and how requests start and end. The same script can run under several SAPIs, and some behaviour, such as default time limits, output handling and which superglobals are filled, depends on which one. ## Reading the SAPI name Two equivalent ways: - `php_sapi_name(): string|false` returns the SAPI name as a lowercase string, or `false` on failure; - `PHP_SAPI` is a constant holding the same string, handy in a guard at the top of a file. | Value | Where the script is running | |---|---| | `cli` | the command line, including cron jobs and queue workers | | `cli-server` | the built-in development server started with `php -S` | | `fpm-fcgi` | PHP-FPM, behind a web server speaking FastCGI | | `apache2handler` | PHP loaded as a module inside Apache httpd | | `cgi-fcgi` | the `php-cgi` binary, as CGI or FastCGI | | `phpdbg` | the phpdbg debugger | | `embed` | PHP embedded in another C program | | `litespeed` | the LiteSpeed SAPI | The manual's list is marked as not exhaustive, and it notes a common surprise: Apache's module reports `apache2handler`, not `apache`. ## Where the name comes from The SAPI is fixed by **which program started PHP**, and it cannot change during a request: - the `php` binary runs the `cli` SAPI, or `cli-server` when given `-S`; - the `php-fpm` daemon runs `fpm-fcgi`; - the `php-cgi` binary runs `cgi-fcgi`; - Apache's PHP module runs `apache2handler` inside the Apache process; - the `phpdbg` binary runs `phpdbg`. One server often has several of them installed side by side: `fpm-fcgi` for web traffic and `cli` for cron jobs, deploy scripts and Composer. They share the PHP version and extensions compiled in, but each process starts separately, so their configuration and defaults can differ. ## The newsletter scenario A newsletter tool has a script, `send-queue.php`, that a cron job runs every five minutes to send queued mail. It lives in the project and, through a misconfigured document root, is also reachable at a URL. Two things go wrong when a web request hits it: 1. anyone can trigger a mass send by loading the URL; 2. the web request runs under the web SAPI's limits, so a long send may be cut off by `max_execution_time` halfway through a batch. A SAPI guard at the top makes the script refuse to run over HTTP: - check `PHP_SAPI !== 'cli'`; - send a `403` status if it is a web request, and exit. The real fix is also to keep such scripts **outside the document root**, but the guard makes the intent explicit and fails safe. ## Why not check $argv? A tempting shortcut is `isset($argv)` or `isset($_SERVER['argv'])`. The manual warns that their presence is not a reliable sign of the command line, because web SAPIs can populate them from the query string when the relevant ini setting is on. `php_sapi_name()` is the documented check. ## Other uses of the SAPI name - **Output format.** A script can print plain text in `cli` and HTML otherwise. - **Error display.** A CLI tool can write errors to the terminal while the web front end logs them. - **Dev-only routing.** A router script can serve static files only when `PHP_SAPI === 'cli-server'`. - **Diagnostics.** `php -r 'echo PHP_SAPI;'` in a terminal always prints `cli`, which is a common source of confusion when someone tests configuration from the shell and expects the web server's values. That last point matters in production debugging: running `php -i` in a shell shows the **CLI's** configuration. To see what the web server uses, print it from a page served by that SAPI.

  • Why can php -i in a shell mislead you when debugging a web request's settings?
    The shell runs the cli SAPI, which can load different configuration and applies its own defaults, such as no execution time limit. The web request runs under fpm-fcgi or apache2handler with its own values. Inspect settings from a page served by the web SAPI itself.
  • What does php_sapi_name() return under Apache's PHP module?
    apache2handler. The manual flags it as a gotcha because people expect apache. Code that tests for an exact name should use the names the manual lists, or test for the one SAPI it cares about, such as cli.

saying these in an interview costs you the question

  • isset($argv) reliably proves the script runs from the command line.
  • Under Apache's module, php_sapi_name() returns apache.
  • PHP_SAPI and php_sapi_name() can return different values.
  • php -i in a shell shows the web server's PHP settings.
  • PHP-FPM reports its SAPI name as cli.