I recently spent a good few hours getting Xdebug to work with my development setup (which is PhpStorm running inside WSL 2 on Windows 10, and PHP/Xdebug running inside a Docker container, inside WSL 2, with Docker Desktop), so here I am writing up the surprisingly simple solution I ended up with -- partially for my own future reference, but also to help out anyone who finds themselves in a similar situation.
(Don't get discouraged by my ultra-convoluted setup -- this configuration should actually theoretically work for pretty much any environment, for reasons I'll get into a little later on.)
Preamble
Xdebug can be tricky to configure, because it works in reverse from the way you're probably used to interacting with your PHP application -- instead of sending requests to your PHP code, Xdebug needs to know how to send requests from where your code is running to your client application (i.e. probably your editor or IDE), in order to establish a debugging connection.
This is fine if your setup is relatively simple (i.e. Xdebug can just go to localhost:9003
and hit your client), but if your setup is a little more complicated than that -- i.e. if PHP/Xdebug is running on a different physical machine, in a VM, in a container, or similar -- it won't work out of the box.
Xdebug 3 makes this whole exercise a little easier for us by generally overhauling the way Xdebug is configured to make it simpler, but if your setup is a little convoluted (like mine), it can still be finicky to get right, hence this post.
The Meat and Potatoes
Okay, so here's what I ended up with.
This is the bit you need to put somewhere in your PHP configuration (i.e. your php.ini
, or wherever you usually configure your PHP extensions):
[Xdebug]
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.discover_client_host=true
xdebug.client_host=host.docker.internal
Let's go over this line-by-line:
xdebug.mode=debug
enables step debugging (which is probably what you want to use Xdebug for.)
xdebug.start_with_request=yes
tells Xdebug that we want to activate step debugging at the start of every request, for simplicity's sake. I recommend setting this to yes
and forgetting about it, but if you've done this before and you know you prefer to activate step debugging only for specific requests, leave this line out and Xdebug will default to trigger
.
The last two lines are where it gets interesting. xdebug.discover_client_host=true
tells Xdebug to attempt to extract the IP of the client from the HTTP request, and xdebug.client_host
tells it what to try if that doesn't work.
With all of these set the way we've set them, here's what happens every time you send a request to your PHP application:
- Xdebug activates step debugging (because our
xdebug.mode
isdebug
, andxdebug.start_with_request
is set toyes
). - Xdebug attempts to automatically connect to the host the HTTP request came from (using
$_SERVER['REMOTE_ADDR']
,$_SERVER['HTTP_X_FORWARDED_FOR']
, or a custom HTTP header you've configured). - If the above didn't work, it falls back to trying to connect to our
xdebug.client_host
, which ishost.docker.internal
, a DNS name for the host helpfully set for us by Docker Desktop on Windows or macOS (though unfortunately not on Linux).
The fact that Xdebug lets us set both xdebug.discover_client_host
and xdebug.client_host
as a fallback is the key to making this configuration work for just about any setup, whether you're on Linux, macOS or Windows, and even whether you're using Docker or not.
If you're using Docker Desktop, having host.docker.internal
set as a fallback host means Xdebug will always be able to find its way to the host, and on most other setups, client discovery (via xdebug.discover_client_host
) should just work. Theoretically, the only scenario this config should break in is if you're using something that breaks client discovery that isn't Docker (e.g. a Vagrant virtual machine), in which case there's probably something else you can set xdebug.client_host
to to fix it.
Installing Xdebug in Your Docker Image
If you're using Docker (and if you're not, I highly recommend thinking about it!), the below is an example of what I do to install and enable Xdebug in development without carrying it over to the production image, using multi-stage builds:
# (or your base image)
FROM php:8.0-fpm-alpine as base
# whatever you do to set up your image
# for example:
# - copying your source code
# - installing extensions
# - doing a `composer install`
FROM base as development
RUN pecl install xdebug && docker-php-ext-enable xdebug
FROM base as production
# whatever you do to prepare your image for production
# for example: removing packages you don't need in production
Conclusion
That's it! You should now have an Xdebug config that works for everything from Docker Desktop to just running PHP on the same Linux machine as your editor.
If you're using this config and you've found a setup it doesn't work for, please yell at me on Twitter and I'll probably add a note to this post!
Top comments (2)
Since XDEBUG 3, whenever we run our code we get the following error if our IDE is not listening for XDEBUG (e.g. F5 -> Step Debugging). It's more apparent when we run stuff in CLI, especially
drush
command in our case.Xdebug: [Step Debug] Time-out connecting to debugging client, waited: 200 ms. Tried: host.docker.internal:9003 (fallback through xdebug.client_host/xdebug.client_port) :-(
Are you getting this too? Do you have a solution? or a way to silence them?
Yup, I get this too if I run any code while my editor isn't listening for debugging connections. I haven't tried this, but I think you can get it to stop doing that by changing
xdebug.log_level
to a lower setting?