skip to content

In PHP, why is $_ENV often empty even though getenv() returns the variable, and what controls whether $_ENV is filled?

level: middleimportance: should knowfreq 45%

answer

  1. one letter in a directive
  2. variables_order, shipped as "GPCS"
  3. built-in default "EGPCS"
  4. PHP_INI_SYSTEM or PHP_INI_PERDIR only
  5. under FPM, FastCGI params come first

basics

~10 s

$_ENV is filled only when the variables_order directive contains E. Both php.ini-production and php.ini-development ship variables_order = "GPCS", so $_ENV stays empty, while getenv() reads the environment directly and still works.

solid answer

~40 s

The `variables_order` directive lists which superglobals PHP registers at startup: `E` for `$_ENV`, `G` `$_GET`, `P` `$_POST`, `C` `$_COOKIE`, `S` `$_SERVER`. The compiled-in default is `"EGPCS"`, but both shipped php.ini files set `"GPCS"`, dropping `E` because copying the environment into an array on every request costs time and few scripts use it. So `$_ENV` is empty, while `getenv()` reads the environment on demand and works regardless. You cannot fix it with `ini_set()`: the directive is only changeable in php.ini or per-directory configuration. Either read with `getenv()`, add `E` to `variables_order`, or let a .env loader populate `$_ENV` explicitly. In the CLI, `$_SERVER` also carries the environment variables.

code

ini · 5 lines
ini
; php.ini-production and php.ini-development both ship this line
variables_order = "GPCS"

; to have $_ENV populated, add E (costs a copy per request)
; variables_order = "EGPCS"

go deeper

for a junior

Recall that $_ENV depends on the E in variables_order and that the shipped php.ini files leave it out, so getenv() is the reliable way to read the environment.

for a middle

Explain the letters of variables_order, the difference between the built-in default and the shipped files, and why ini_set() cannot change it.

for a senior

Trace a value under FPM through its three possible sources — worker environment, FastCGI parameters, request headers — and use local_only to pin reads of secrets to the process environment.

for a principal

Set one convention for where configuration is read, so libraries that use $_ENV, $_SERVER or getenv() do not silently see different values in different environments.

## The symptom A developer sets `DB_HOST` in a container or shell, then writes `$_ENV['DB_HOST']` and gets an "Undefined array key" warning. `getenv('DB_HOST')` in the same script returns the value. Both are "reading the environment", so the difference looks like a bug. ## `variables_order` The `variables_order` directive tells PHP which superglobal arrays to register when a request starts, and in what order. Each letter is one array: | Letter | Superglobal | |---|---| | `E` | `$_ENV` | | `G` | `$_GET` | | `P` | `$_POST` | | `C` | `$_COOKIE` | | `S` | `$_SERVER` | Three values matter: - the **built-in default** compiled into PHP (`main/main.c`) is `"EGPCS"` — with `E`; - **php.ini-production** sets `variables_order = "GPCS"`; - **php.ini-development** also sets `"GPCS"`. The comment in the shipped files explains why: registering these arrays costs time on every request, and because `ENV` is rarely used, it is not recommended on production servers — `getenv()` remains available. Almost every installation starts from one of those two files, so `$_ENV` is empty in most installations; a PHP running with no php.ini at all falls back to the built-in `EGPCS`. ## Why `getenv()` still works `getenv()` does not read `$_ENV`. It looks the name up when called: first through the SAPI (under PHP-FPM, the FastCGI parameters sent with the request), then in the process environment. `variables_order` therefore has no effect on it. ## Why `ini_set()` does not help `variables_order` is registered with the modes `PHP_INI_SYSTEM | PHP_INI_PERDIR`. It can be set in php.ini or in per-directory configuration, but **not** with `ini_set()` at runtime — and even if it could, the superglobals are already built by the time your code runs. ## Your options 1. **Use `getenv()`** for environment variables and treat `$_ENV` as unreliable. This is the most portable choice. 2. **Add `E`** — set `variables_order = "EGPCS"` in php.ini — if a library insists on reading `$_ENV`. The cost is an extra array copy per request. 3. **Let a .env loader fill it.** Loader libraries read a `.env` file and write the values into `$_ENV` and `$_SERVER` (and, depending on the loader and its settings, into the process environment via `putenv()`). Code then reads whichever store the loader wrote. 4. **Read `$_SERVER`** in the CLI: the CLI SAPI copies the environment into `$_SERVER`, so `$_SERVER['DB_HOST']` works there even with `GPCS`. ## Where the values come from under PHP-FPM Under FPM, three sources can supply a name and they are easy to confuse: - the **worker process environment** — what the FPM master passed on, which the pool configuration controls; - the **FastCGI parameters** of the request — set by the web server, for example with `fastcgi_param` in nginx; `getenv()` consults these first unless `local_only` is `true`; - **HTTP request headers**, which arrive as `HTTP_*` parameters. PHP's `getenv()` deliberately refuses to return `HTTP_PROXY` from the SAPI, because a client could otherwise set it with a `Proxy:` header. For secrets such as database passwords, prefer names without the `HTTP_` prefix, read them once at boot, and use `getenv('DB_PASSWORD', true)` when you want the process environment only. ## Quick diagnosis - `var_dump(ini_get('variables_order'));` — no `E` explains an empty `$_ENV`. - `var_dump(getenv('DB_HOST'));` — `false` means the variable really never reached the process, which is a deployment problem, not a PHP one.

  • Can you call ini_set('variables_order', 'EGPCS') at the top of the script to fill $_ENV?
    No. `variables_order` is changeable only in php.ini or per-directory configuration (`PHP_INI_SYSTEM | PHP_INI_PERDIR`), so `ini_set()` cannot change it. And by the time any line of your script runs, the superglobals have already been built for this request.
  • Why might getenv('APP_ENV') return a different value than the FPM worker's real environment?
    Without `local_only`, `getenv()` asks the SAPI first, and under FPM that means the FastCGI parameters the web server sent. A `fastcgi_param APP_ENV ...` line in the web server config therefore wins over the worker's environment. `getenv('APP_ENV', true)` reads the process environment only.

saying these in an interview costs you the question

  • $_ENV is empty because the environment variables were never exported
  • ini_set('variables_order', 'EGPCS') at runtime fills $_ENV
  • getenv() reads from $_ENV, so both are empty together
  • variables_order also decides whether getenv() works
  • PHP's built-in default for variables_order already omits E