Skip to Content

Debugging WordPress Performance Issues on Localhost

Spoiler alert, it's plugins.

Published on

Random characters surrounding "localhost", "dns_get_record", and "DNS_CNAME".

I'm running WordPress inside Docker locally because I don't want to bother with setting up MAMP(opens in new tab) and jumping over hoops just to get the same PHP version. Since we use Bitnami(opens in new tab) on production, I'm running this Bitnami image(opens in new tab).

Once I run the image, everything works great and is quick. However, once I import the live site content, I notice that the server is significantly slower, compared to the live site. I added Xdebug(opens in new tab) and enabled profiling(opens in new tab) to find the bottleneck.

I saw that there were two calls to dns_get_record()(opens in new tab) in /bitnami/wordpress/wp-content/plugins/wordfence/lib/wfUtils.php, each taking almost exactly 8 seconds every time.

Apparently, Wordfence(opens in new tab), a popular WordPress plugin, was causing the issue. Surprise, surprise. It could've been way easier to debug, but unfortunately the errors were silenced by using the error control operator @(opens in new tab). As can be seen in the source code(opens in new tab):

$cnameRaw = @dns_get_record($host, DNS_CNAME);
// ─────────┴─── this silences errors

Anyways, I added a couple of error_log() calls to see what the queried $host names are. They both turned out to be localhost.

I then created a test.php file in the container to poke at the problematic line in isolation:

<?php var_dump(dns_get_record('localhost', DNS_CNAME));

When I ran it, I indeed got an 8 second delay error before receiving the following error:

[10-Nov-2023 08:22:36 UTC] PHP Warning:  dns_get_record(): A temporary server error occurred. in /test.php on line 1

The interesting thing was that if I changed DNS_CNAME to DNS_A, there was no delay and the output was:

array(1) {
  array(5) {
    string(9) "localhost"
    string(2) "IN"
    string(1) "A"
    string(9) ""

It looks like PHP has trouble resolving CNAMEs for localhost, at least in Docker. Since CNAMEs don't make sense in such a situation, this is kind of expected.

But anyway, Wordfence's internal code uses DNS_CNAME, so I have to make it resolve them. Unfortunately, as explained in Stack Overflow(opens in new tab), this isn't quite possible, at least not in an easy way by using /etc/hosts(opens in new tab):

The file /etc/hosts contains IP addresses and host names only. […] If you were running your own DNS server you'd be able to add a CNAME record to make an alias for domain.example, but otherwise you're out of luck.

Since running an entire DNS server just for this would've been extremely over-engineered and unreasonable, I decided not to.

Perhaps the ideal solution would've been to monkey patch(opens in new tab) the dns_get_record() function and make it return false immediately once invoked with the arguments "localhost" and DNS_CNAME. Unfortunately, monkey patching in PHP(opens in new tab) isn't possible unless you install runkit7(opens in new tab), which may unnecessarily complicate things too much, as it did with my Bitnami docker image.

It appears that the only adequate solution is to just turn off Wordfence locally until there's a new version of the plugin that fixes this. Or until PHP gets fixed. Or until Docker gets fixed?