skip to content

With Xdebug 3, how does the xdebug.mode setting choose which features run, and how do comma-combined values and XDEBUG_MODE change it?

level: middleimportance: must knowfreq 40%

answer

  1. one setting replaced many switches
  2. default is develop, not off
  3. comma list: develop,trace
  4. read once at process start
  5. environment variable wins over php.ini

basics

~20 s

In Xdebug 3, xdebug.mode lists the features to enable (off, develop, coverage, debug, gcstats, profile, trace), comma-separated to combine them; it defaults to develop, is fixed when PHP starts, and the XDEBUG_MODE environment variable overrides it.

solid answer

~40 s

Xdebug 3 replaced Xdebug 2's per-feature switches (`xdebug.profiler_enable`, `xdebug.auto_trace`, `xdebug.coverage_enable`) with one setting, `xdebug.mode`. Its values are `off`, `develop`, `coverage`, `debug`, `gcstats`, `profile` and `trace`, and you combine them with commas, e.g. `xdebug.mode=develop,trace`. The default is `develop`, so an installed but unconfigured Xdebug is never truly off. The mode is locked in when the PHP process starts: it belongs in `php.ini` or a `99-xdebug.ini`, and `.htaccess`, `.user.ini`, `php_admin_value` and `ini_set()` cannot change it. For a one-off run you set the `XDEBUG_MODE` environment variable instead, e.g. `XDEBUG_MODE=profile php report.php`; it takes precedence over `xdebug.mode` without changing that setting's value. An invalid `XDEBUG_MODE` falls back to `xdebug.mode`, and an invalid `xdebug.mode` falls back to `develop`.

code

ini · 8 lines
ini
; /etc/php/8.5/cli/conf.d/99-xdebug.ini
zend_extension=xdebug

; develop helpers plus function traces; read once at startup
xdebug.mode=develop,trace

; trace only when XDEBUG_TRIGGER is present
xdebug.start_with_request=trigger

go deeper

for a junior

Recall that xdebug.mode names the features Xdebug enables, that its default is develop, and that you can list several modes with commas.

for a middle

Explain that the mode is read once at process start, why .user.ini and ini_set() cannot change it, and how XDEBUG_MODE overrides it for one command.

for a senior

Show you can audit a setup: spot Xdebug 2 directives that do nothing, the silent fallback to develop on an invalid value, and an environment variable stripped before PHP sees it.

for a principal

Weigh one shared image with xdebug.mode=off and per-command XDEBUG_MODE against separate dev and CI images, trading convenience against the risk of Xdebug reaching production.

## One setting instead of many switches **Xdebug** is a PHP extension (loaded with `zend_extension=xdebug`) that bundles several independent tools: development helpers, a step debugger, a profiler, a function tracer, code-coverage collection and garbage-collection statistics. Xdebug 2 switched each tool on with its own directive — `xdebug.remote_enable`, `xdebug.profiler_enable`, `xdebug.auto_trace`, `xdebug.coverage_enable`, `xdebug.default_enable`. Xdebug 3 replaced all of them with a single **mode** setting, `xdebug.mode`, so that Xdebug only pays overhead for the features you actually asked for. | Value | What it enables | |---|---| | `off` | Nothing; Xdebug only checks whether anything is enabled | | `develop` | Development helpers: the overloaded `var_dump()`, stack traces on errors, the recursion guard | | `coverage` | Code-coverage collection, mainly for PHPUnit | | `debug` | Step debugging with an IDE over DBGp | | `gcstats` | Garbage-collection statistics files | | `profile` | The profiler, writing Cachegrind-format files | | `trace` | Function traces and flame-graph data | ## Combining modes The value is a **comma-separated list**, so `xdebug.mode=develop,trace` gives you the nicer `var_dump()` and a function trace at the same time, and `xdebug.mode=debug,develop` is a common local setup. Two things to keep in mind: - Every mode you add costs something on every request, so combine only what you are using right now. Profiling and step debugging together make little sense: the time you spend paused at a breakpoint lands in the profile. - An unrecognised item makes the whole value invalid. Xdebug then logs a critical configuration message and falls back to `develop`, which is easy to miss. ## When the mode is decided `xdebug.mode` is a **system-level** setting that Xdebug reads once, while the PHP process starts up, and the features are wired into the engine at that moment. Consequences: 1. It must be set in `php.ini` or an extra ini file read at startup, such as `99-xdebug.ini` in the `conf.d` scan directory. 2. Per-directory files read per request — `.htaccess` and `.user.ini` — cannot set it, and neither can `php_admin_value` in an Apache vhost or a PHP-FPM pool. 3. `ini_set('xdebug.mode', 'profile')` inside a script cannot enable anything: by then the mode is locked in. 4. After editing the ini file you must restart PHP-FPM or the web server; a new request alone is not enough. On the command line, `php -d xdebug.mode=profile script.php` works, because `-d` is applied before the extension starts. ## The XDEBUG_MODE environment variable Setting `XDEBUG_MODE` in the environment of the PHP process **takes precedence** over `xdebug.mode` without changing that setting's value, so `ini_get('xdebug.mode')` can still report `develop` while profiling is actually running. This is the everyday way to switch a feature on for one command: ```bash XDEBUG_MODE=profile php bin/generate-report.php XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html build/coverage XDEBUG_MODE=off composer install ``` The variable is read at the same moment as the ini setting — process start — so for web requests it has to be present where the PHP-FPM service or web server itself starts. The Xdebug documentation also warns that servers can strip environment variables before PHP sees them; PHP-FPM's `clear_env` is on by default. If `XDEBUG_MODE` holds an invalid value, Xdebug logs a critical message and uses `xdebug.mode` instead. ## How the mode interacts with start_with_request The mode says which features *may* run; `xdebug.start_with_request` says *when* they activate. Its `default` value resolves per mode: `yes` for `profile` (every request is profiled), `trigger` for `trace` and `debug` (only requests carrying an `XDEBUG_TRIGGER` activate), and `no` for `gcstats`. Develop helpers and coverage do not use it: they are active whenever their mode is set. ## Why it matters in practice - **Installed means `develop`.** Installing the extension without writing `xdebug.mode` runs develop helpers on every request. - **Explicit `off` in shared images.** A container image used both locally and in CI often sets `xdebug.mode=off` in the ini file and turns features on per command with `XDEBUG_MODE`. - **CLI and web differ.** The CLI and PHP-FPM usually read different ini directories, so a mode set for one is not automatically set for the other. - **Replaced names are history.** Xdebug 2 directives such as `xdebug.profiler_enable` or `xdebug.remote_enable` do nothing in Xdebug 3; seeing them in a config is a sign the setup was never migrated.

  • You put xdebug.mode=profile in a project's .user.ini and nothing is profiled. Why?
    `.user.ini` files are read per request by the CGI/FPM SAPIs, but `xdebug.mode` is a system-level setting that Xdebug reads once at process start. It has to go into `php.ini` or a startup-scanned file such as `99-xdebug.ini`, followed by a restart of PHP-FPM, or be supplied through the `XDEBUG_MODE` environment variable of the process that starts PHP.
  • What happens if xdebug.mode contains a typo such as 'profiel'?
    The whole value is rejected: Xdebug logs a critical configuration message saying the mode is invalid and falls back to `develop`. So you get the development helpers and no profiler, with no fatal error to warn you. If the typo is in the `XDEBUG_MODE` environment variable instead, Xdebug logs it and uses the `xdebug.mode` setting.

saying these in an interview costs you the question

  • Xdebug does nothing until you set xdebug.mode, so installing it is harmless
  • ini_set('xdebug.mode', 'profile') at the top of a script starts the profiler
  • Xdebug 3 still reads xdebug.profiler_enable and xdebug.remote_enable
  • You can only run one Xdebug mode at a time
  • XDEBUG_MODE rewrites the xdebug.mode value that ini_get() reports