With Xdebug 3, how does the xdebug.mode setting choose which features run, and how do comma-combined values and XDEBUG_MODE change it?
answer
- one setting replaced many switches
- default is develop, not off
- comma list: develop,trace
- read once at process start
- environment variable wins over php.ini
basics
~20 sIn 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 sXdebug 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; /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=triggergo deeper
Recall that xdebug.mode names the features Xdebug enables, that its default is develop, and that you can list several modes with commas.
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.
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.
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