How to Debug PHP with Xdebug in VS Code
By Pawan Singh · Jun 2026
Stepping through PHP execution line-by-line, inspecting real variable state, beats var_dump-and-refresh debugging for anything non-trivial. Here's the full setup, plus the parts that actually go wrong (Docker, WSL, remote servers).
Install the Xdebug Extension
Xdebug is a PECL extension, not a bundled PHP feature. Install it for your specific PHP version and OS:
pecl install xdebug
Then enable it in php.ini and configure it to talk to your editor:
zend_extension=xdebug.so
xdebug.mode=debug
xdebug.start_with_request=trigger
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
Confirm it loaded with php -v — it should print an Xdebug line alongside the PHP version. Port 9003 is Xdebug 3's default (Xdebug 2 used 9000, a common source of confusion when following outdated tutorials).
Install the VS Code Extension
Install "PHP Debug" (by Xdebug's own maintainer, xdebug.org) from the VS Code marketplace. It provides the launch.json configuration type that speaks the Xdebug protocol.
Configure launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003
}
]
}
Start this configuration (F5), set a breakpoint by clicking in the gutter next to a line number, then trigger the request — load the page in a browser, or run the script from the CLI. Execution pauses at the breakpoint, and the Debug sidebar shows the full variable scope, call stack, and lets you step in/over/out.
Debugging Inside Docker or DDEV
This is where most setups actually break: Xdebug running inside a container needs to reach your editor on the host machine, not localhost from the container's own perspective. Set:
xdebug.client_host=host.docker.internal
DDEV-based projects (Drupal, Laravel, or otherwise) have this largely handled already — ddev xdebug on enables it with the right host configuration for you, and ddev xdebug off disables it again (leaving it on permanently adds real overhead to every request, so toggle it only when actively debugging).
Debugging from WSL
If your PHP process runs inside WSL but VS Code (or its window) is on Windows, set xdebug.client_host to the actual IP WSL uses to reach Windows — not always 127.0.0.1, depending on your WSL networking mode. Run cat /etc/resolv.conf inside WSL to find the nameserver IP, which is usually also the right host IP for this purpose in WSL2's default NAT networking mode.
Conditional and Logpoint Breakpoints
Right-click a breakpoint to add a condition (e.g. $userId === 42) so it only pauses on the iteration you actually care about inside a loop — far faster than manually stepping through hundreds of irrelevant iterations. Logpoints (also right-click) print a message to the debug console without pausing execution at all, a lower-friction alternative to temporary error_log() calls you'd otherwise have to remember to remove.
Common Issues
- "Could not connect to debugging client" in the browser — check
xdebug.client_hostmatches where your editor is actually listening, especially across Docker/WSL boundaries. - Debugger never triggers — with
xdebug.start_with_request=trigger, you need a trigger: either a browser extension that sets theXDEBUG_TRIGGERcookie, or append?XDEBUG_TRIGGER=1to the URL manually. - Everything feels slow — set
xdebug.start_with_request=triggerinstead ofyes/always, so Xdebug's overhead only applies to requests you're explicitly debugging. - Breakpoints show as unbound (hollow) circles — usually a path mapping mismatch between the file VS Code has open and the path Xdebug reports from inside a container; add a
pathMappingsentry tolaunch.jsonmatching your container's filesystem layout. - Works for web requests, not CLI scripts — CLI runs need
xdebug.mode=debugexported in the shell's environment too, since CLI PHP doesn't always read the same effectivephp.inias your web server's PHP-FPM pool.
Need your local dev environment set up properly, debugging included? Get in touch — I work as a freelance PHP developer.