Xdebug on demand

My MediaWiki test instance normally runs without Xdebug, since that gives good performance. But when I request a debug session in PHPStorm, Xdebug is automatically enabled.

I don’t often share config snippets since they are so specific to the way I have things set up. You will most likely have to adapt this to your situation. But at least it should help you to know that conditionally enabling Xdebug is possible.

I use PHP-FPM which, unlike Apache mod-php, allows you to run multiple versions of PHP. I use Apache to detect Xdebug’s cookie or query string. Then I have a separate instance of PHP-FPM just for Xdebug, with a separate php.ini.

I’m using Xdebug 3.0. I upgraded from Xdebug 2 for this project since I figured there is no point putting a lot of effort into an Xdebug 2 installation which I would almost certainly have to upgrade within a year.

I have a file called /etc/apache2/php.conf along the lines of:

<FilesMatch ".+\.php$">
	<If "%{HTTP_COOKIE} =~ /XDEBUG_SESSION/ || %{QUERY_STRING} =~ /XDEBUG_SESSION_START=/ || %{REQUEST_URI} =~ /intellij_phpdebug_validator/">
		SetHandler "proxy:unix:/run/php/php8.0-fpm-xdebug.sock|fcgi://localhost"
	</If>
	<Else>
		SetHandler "proxy:unix:/run/php/php8.0-fpm.sock|fcgi://localhost"
	</Else>
</FilesMatch>

I include that into any VirtualHost directives that need PHP execution.

<VirtualHost *:443>
	ServerName test.internal
	...
	Include php.conf
</VirtualHost>

The non-Xdebug instance of PHP-FPM is installed in the usual way, in my case using Ondřej Surý’s PPA. I copied the systemd service file for PHP-FPM from /lib/systemd/system/php8.0-fpm.service to /etc/systemd/system/php-fpm-xdebug.service and changed the paths to avoid conflicts:

[Unit]
Description=The PHP 8.0 FastCGI Process Manager (with xdebug)
Documentation=man:php-fpm8.0(8)
After=network.target

[Service]
Type=notify
ExecStart=/usr/sbin/php-fpm8.0 --nodaemonize --fpm-config /etc/php/8.0/fpm-xdebug/php-fpm.conf -c /etc/php/8.0/fpm-xdebug
ExecStartPost=-/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm-xdebug.sock /etc/php/8.0/fpm-xdebug/pool.d/www.conf 80
ExecStopPost=-/usr/lib/php/php-fpm-socket-helper remove /run/php/php-fpm-xdebug.sock /etc/php/8.0/fpm-xdebug/pool.d/www.conf 80
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

/etc/php/8.0/fpm-xdebug is the configuration directory for the new instance of PHP-FPM, initially based on a copy of /etc/php/8.0/fpm. To avoid loading Xdebug into the non-Xdebug instance, I commented out the zend_extension line in /etc/php/8.0/mods-available/xdebug.ini , since the package has a bug which recreates the conf.d symlinks if you delete them.

I don’t usually use the default php.ini, I just wipe it after package installation and start with an empty file, since that makes it easier to see which defaults I’ve overridden. You lose the documentation comments, but I’m able to read the manual, so that’s fine.

My /etc/php/8.0/fpm-xdebug/php.ini has:

zend_extension = xdebug.so
xdebug.mode = debug
xdebug.client_host = 169.254.1.1

Replace 169.254.1.1 with the address of the host on which your IDE runs, as seen by the container. I am using systemd-nspawn with bridged networking, which allows PHP to connect to the host using link-local addresses.

A few more little things to avoid conflicts with the main PHP-FPM instance. Like php.ini, I started with empty files, not default files. /etc/php/8.0/fpm-xdebug/php-fpm.conf contains only:

[global]
pid = /run/php/php8.0-fpm-xdebug.pid
error_log = /var/log/php8.0-fpm-xdebug.log

include=/etc/php/8.0/fpm-xdebug/pool.d/*.conf

And /etc/php/8.0/fpm-xdebug/pool.d/www.conf:

[www]
user = www-data
group = www-data
listen = /run/php/php8.0-fpm-xdebug.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3

I think that’s it. Now it should just be a matter of

systemctl daemon-reload
systemctl start php-fpm-xdebug
systemctl enable php-fpm-xdebug
systemctl reload apache2

To debug a web request, set a breakpoint, then in Run > Edit Configurations, create a “PHP Web Page” configuration. The URL you use here does not have to be the exact one you want to debug, it just has to be in the same cookie domain. Then Run > Debug… and select the configuration. This will spawn a browser tab which activates Xdebug with the XDEBUG_SESSION_START query string parameter. Xdebug modifies the response to set the XDEBUG_SESSION cookie.

Since XDebug 3.1, the cookie has no expiry time set, but you can append XDEBUG_SESSION_STOP=1 to the query string to cause it to remove its session cookie. With no session cookie, you will automatically be back to PHP without Xdebug.

You can get PHPStorm to run command-line scripts via SSH, but I find it’s most convenient to run them in the usual way, with the PHPStorm option “Listen for PHP Debug Connections” enabled.

I use a shell script wrapper along the lines of:

XDEBUG_TRIGGER=1 PHP_IDE_CONFIG=serverName=test.internal \
  php -dzend_extension=xdebug.so -dxdebug.mode=debug -dxdebug.client_port=9000 \
  -dxdebug.client_host=169.254.1.1 "$@"

The serverName configuration variable here is necessary to select the right path mappings — it must correspond to the name of the server in PHPStorm’s File > Settings > PHP > Servers.

Leave a Reply

Your email address will not be published. Required fields are marked *