After migrating from Laravel Sail to Herd on Mac I needed to also find a way to make websockets work, as this app used them. Previously I was relying on soketi and it was running under its own docker compose, but with Sail gone, this went too. Sure I could just run soketi from npm but since Laravel offers a first-class solution, a Laravel Reverb, I decided to try that.

After changing a few files and a few configs, my app could connect to the websocket, because I could see it in the browser Network tab or via:

php artisan reverb:start --debug

The problem was that the dispatched events were failing with the infamous error:

cURL error 60: SSL certificate problem: unable to get local issuer certificate

I have spent considerable time debugging this as no search or AI magic could point me to right direction, with Herd specifically. I had to get my hands dirty.

Laravel Herd php.ini

First I needed to make sure the php binary used was in fact served by Herd:

/Users/peterbabic/Library/Application Support/Herd/bin/php

The above file is a symlink to a proper binary:

ll /Users/peterbabic/Library/Application\ Support/Herd/bin/php
lrwxr-xr-x@ 1 peterbabic  staff    61B Jul 30 09:18 /Users/peterbabic/Library/Application Support/Herd/bin/php@ -> /Users/peterbabic/Library/Application Support/Herd/bin//php84

Probing around Herd more, I could find a php.ini related to the running PHP 8.4 version:

cat /Users/peterbabic/Library/Application\ Support/Herd/config/php/84/php.ini

Returns these bits, the second line specifically is the one we are looking for:

curl.cainfo=/Users/peterbabic/Library/Application Support/Herd/config/php/cacert.pem
openssl.cafile=/Users/peterbabic/Library/Application Support/Herd/config/php/cacert.pem
pcre.jit=0
output_buffering=4096

memory_limit=128M
upload_max_filesize=2M
post_max_size=2M

However, when I tried to check if php binary is loading this setting, I could see it doesn't:

php -i | grep -i ssl

Specifically the line openssl.cafile => no value => no value was the culprit:

Registered Stream Socket Transports => tcp, udp, unix, udg, ssl, tls, tlsv1.0, tlsv1.1, tlsv1.2, tlsv1.3
SSL => Yes
MULTI_SSL => No
SSL Version => OpenSSL/3.5.1
SSL Support => enabled
libmongoc SSL => enabled
libmongoc SSL library => OpenSSL
core SSL => supported
extended SSL => supported
openssl
OpenSSL support => enabled
OpenSSL Library Version => OpenSSL 3.5.1 1 Jul 2025
OpenSSL Header Version => OpenSSL 3.5.1 1 Jul 2025
Openssl default config => /etc/ssl/openssl.cnf
openssl.cafile => no value => no value
openssl.capath => no value => no value
OpenSSL support => enabled

Of course the system is unable to load the certificate when it is not even instructed to do so!

The certificate

Probing around the Herd directory structures, I could clearly see the certificate was present:

stat /Users/peterbabic/Library/Application\ Support/Herd/config/php/cacert.pem

If for any reason it is not there, regenerate it easily by running herd secure which outputs the following and creates the file:

The [laravel.test] site has been secured with a fresh TLS certificate.

So why php was not loading its php.ini file provided by Herd? Well, this was a good thing to search for as it proved fruitful.

Setting up

For my fish shell setup I needed to add these variables into config.fish so Herd could pick them up:

set -x HERD_PHP_85_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/85/"
set -x HERD_PHP_84_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/84/"
set -x HERD_PHP_83_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/83/"
set -x HERD_PHP_82_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/82/"
set -x HERD_PHP_81_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/81/"
set -x HERD_PHP_80_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/80/"
set -x HERD_PHP_74_INI_SCAN_DIR "/Users/peterbabic/Library/Application Support/Herd/config/php/74/"

After re-opening/restarting everything, I could do a few confirming commands:

php -i | grep -i scan

Was now properly showing the location to scan for herd's php.ini file:

Scan this dir for additional .ini files => /Users/peterbabic/Library/Application Support/Herd/config/php/84/

And the openssl.cafile now had a correct value too:

openssl.cafile => /Users/peterbabic/Library/Application Support/Herd/config/php/cacert.pem => /Users/peterbabic/Library/Application Support/Herd/config/php/cacert.pem

Enjoy!