Why does getenv('DATABASE_URL') return false in PHP under PHP-FPM when the variable is set in the service's environment?
answer
- clear_env defaults to yes
- workers start with an empty environment
- env[NAME] = $NAME copies from the master
- values fixed at worker start
- clear_env = no passes everything
basics
~20 sPHP-FPM's clear_env defaults to yes, so workers start with an empty environment plus only the pool's env[] entries. Add env[DATABASE_URL] = $DATABASE_URL to copy it from the master's environment, or set clear_env = no, then reload FPM.
solid answer
~50 sThe variable reaches the FPM **master** process, but PHP code runs in **workers**, and each pool's `clear_env` directive, which defaults to `yes`, clears the worker environment before adding the variables listed with `env[NAME] = value`. So `getenv()`, `$_ENV` and the environment part of `$_SERVER` do not see it. Fix it per variable with `env[DATABASE_URL] = $DATABASE_URL`: a value starting with `$` is copied from the master's environment when the pool starts, and becomes an empty string if the master does not have it. Alternatively set `clear_env = no` to pass the whole environment, which is simpler in containers but also hands every variable of the service to PHP code. Values are copied when the pool is set up, so a change needs a reload — and a restart from the updated environment if the master's own environment changed.
code
ini · 7 lines; pool.d/app.conf
[app]
; default: clear_env = yes, so list what PHP may see
env[DATABASE_URL] = $DATABASE_URL
env[APP_ENV] = $APP_ENV
; a literal value
env[TMPDIR] = /tmpgo deeper
Recall that PHP-FPM clears the environment of its workers by default, so variables must be passed with env[] or clear_env = no.
Explain master versus worker environments, how env[NAME] = $NAME copies from the master, and why a reload is needed after changes.
Weigh an explicit env[] list against clear_env = no in containers, with credential exposure through debug output and logs in mind.
Set the platform rule for how configuration and secrets reach PHP workers, and how that is kept consistent between web and CLI processes.
## Where the variable goes missing PHP-FPM has two kinds of process: - The **master**, started by systemd, a container entrypoint or an init script. It inherits the environment of whatever started it — including `DATABASE_URL` if you set it there. - The **workers**, forked from the master, which execute PHP code. Each pool decides what environment its workers get, through `clear_env`: | `clear_env` | Worker environment | |---|---| | `yes` (default) | Emptied, then only the pool's `env[...]` entries are added | | `no` | Inherited from the master, plus the pool's `env[...]` entries | The PHP-FPM sample configuration describes `clear_env` as preventing "arbitrary environment variables from reaching FPM worker processes". With the default, `getenv('DATABASE_URL')` returns `false`, `$_ENV` lacks it, and so does `$_SERVER` — even though the same variable is visible to a CLI script run in the same container. ## Fix 1: pass specific variables with env[] ```ini [app] env[DATABASE_URL] = $DATABASE_URL env[APP_ENV] = production env[PATH] = /usr/local/bin:/usr/bin:/bin ``` - A value beginning with **`$`** is looked up in the **master's** environment when the pool is configured; if the master does not have it, the worker gets an empty string, not a missing variable. - A plain value is used literally. - These entries are added after the clearing, so they work with `clear_env = yes`. This is the precise option: PHP code sees exactly the variables you list. It also documents, in one file, which configuration the application depends on. ## Fix 2: clear_env = no Setting `clear_env = no` passes the master's whole environment to the workers. It is common in container images, where the environment is built specifically for the application and the list of variables changes often. The trade-off: - Every variable of the service reaches PHP code — including ones meant for other tools, such as cloud credentials or a proxy password. - Any code path that dumps the environment (a debug page, `phpinfo()`, an error handler that logs `$_SERVER`) now exposes all of them. ## Things that still surprise people 1. **Reload or restart needed.** Values are copied from the master's environment when the pool is set up. Editing `env[]` lines needs a reload; changing the service's environment needs a full restart, because the master only has the environment it was started with. 2. **Empty, not missing.** `env[X] = $X` with `X` unset in the master gives `getenv('X') === ''`, which code checking for `false` treats as present. 3. **CLI differs.** Cron jobs and queue workers run the CLI, not FPM, and see the shell's environment directly; a variable can work in console commands and fail in web requests. 4. **The web server is a different source.** Variables set in the web server's FastCGI parameters arrive in `$_SERVER` as request parameters, not in the worker's process environment; `getenv()` in FPM can return those too, so the two sources are easy to confuse. ## A container example A typical image starts `php-fpm` as PID 1 with variables injected by the orchestrator. Two working setups: ```ini ; explicit list (preferred when the set is stable) [www] env[DATABASE_URL] = $DATABASE_URL env[CACHE_URL] = $CACHE_URL ``` ```ini ; pass everything (simpler, broader exposure) [www] clear_env = no ``` ## Different values per pool Because `env[]` lines live in each pool section, one FPM master can give each application its own environment: the shop pool with `env[DATABASE_URL] = $SHOP_DATABASE_URL`, the blog pool with `env[DATABASE_URL] = $BLOG_DATABASE_URL`. Each application reads the same variable name and gets its own database, and neither sees the other's credentials — which `clear_env = no` would give away. ## Checking what a worker sees A temporary script behind the pool, `var_dump(getenv('DATABASE_URL'));`, shows the worker's view; compare with `printenv DATABASE_URL` in the container shell, which shows the master's. If they differ, the pool's `clear_env` and `env[]` lines are the place to look. Remove the script afterwards; reading configuration in application code is a separate concern from getting it into the worker.
- After adding env[DATABASE_URL] = $DATABASE_URL, getenv() returns an empty string. Why?A value starting with `$` is copied from the FPM master's environment when the pool is set up. If the master was started without `DATABASE_URL` — for example systemd does not pass the shell's variables — the worker gets an empty string. Set the variable where the master is started, restart FPM, and check again.
- Why not always set clear_env = no?It hands every variable in the service's environment to PHP code, including credentials meant for other tools. Any debug output, `phpinfo()` page or log of `$_SERVER` then leaks them. Listing variables with `env[]` keeps the exposure to what the application needs.
saying these in an interview costs you the question
- PHP-FPM workers inherit the master's environment by default
- clear_env = yes also removes variables listed with env[]
- env[X] = $X reads the variable from the HTTP request
- Changing the environment takes effect on the next request without a reload