Author Archives: Stanislav Khromov

About Stanislav Khromov

Web Developer at Aftonbladet (Schibsted Media Group)
Any opinions on this blog are my own and do not reflect the views of my employer.
Twitter Profile
Visit my other blog

WireGuard performance on the low-end GL.iNet GL-AR150 mini router


Back when I got my GL-AR150 mini router, I was blown away by the powerful capabilities of this $25 OpenWRT router, even being able to (somewhat) run WordPress!

One thing that the router didn’t do so well was acting as an OpenVPN client or server, due to its weak CPU and the relatively high computing overhead of the OpenVPN protocol. The speeds you could expect were often 5 megabit per second or lower.

Recently, the WireGuard protocol with its promise of lower CPU overhead was added as an alternative, so I decided to dust off my AR-150 and try it out.

The performance test

After setting up a WireGuard server on the GL-AR150, I connected to it from a MacBook and ran a short download performance test:

debian-31r2-arm-netinst.iso 100%[===========>]  85,89M  2,97MB/s    
Average speed: 3,05 MB/s.

This means that this tiny router can push 25 megabits per second, and I’ve seen burst of up to 35 megabits per second for short periods, which is acceptable for many VPN applications.


GL-AR150 is the weakest router CPU-wise in the GL.iNet line-up, so it means that essentially purchasing any of their newer model would provide an even better VPN experience.

For example, their newest Brume router promises a whopping 280 megabits per second in throughput with WireGuard!

Note: This post contains no affiliate links.

Cleaning up hacked WordPress spam, content injection and defacement using WP-CLI regex search

This post will show how you can use the build in search-replace function in WP-CLI with regex matching to batch remove harmful content from your WordPress site. This means that you can remove hundreds or thousands of injections in a matter of seconds instead of going through content and dumps manually.

Example of post_content injection:

Lorem ipsum dolor sit amet. <script src="" type="text/javascript"></script><script src="" type="text/javascript"> </script>Lorem ipsum dolor sit amet.

Go to and figure out a good regex that fits your type of defacement.

For the defacement above, I settled with:


Now it’s time to run WP-CLI to remove the defacement.

It’s always good to test first with the --dry-run flag, which simulates a run but doesn’t actually do any replacements.

wp search-replace '<script.*?tk.*?<\/script>' '' --all-tables --dry-run --report-changed-only --precise --regex --regex-delimiter='/'

WP-CLI will tell you how many replacements are expected. When you feel like you’ve got a good result, remove --dry-run and you get the final command to run:

wp search-replace '<script.*?tk.*?<\/script>' '' --all-tables --report-changed-only --precise --regex --regex-delimiter='/'


Using Regex with WP CLI to Search & Replace in the Database

How to cache the wp_oembed_get() function

The wp_oembed_get() function is not cached by default. Unfortunately, many themes (like Elegant Themes Divi) still use it which leads to your page making many background requests to YouTube and similar video embedding sites. This slows down your pageloads since WordPress has to wait for YouTube to response before showing your page.

If you’re not able to fix your theme, you can add the code below into an mu-plugin or into your themes functions.php file. It will cache embeds that are using wp_oembed_get(). Please note that embeds that fail to resolve (for example: deleted youtube video) will still not be cached. This is due to the way wp_oembed_get() works internally and is not trivial to fix.

 * Cache wp_oembed_get()
 * Version: 1.0

define('CUSTOM_OEMBED_CACHE_KEY', 'coc_');

function _wp_custom_oembed_cache_key($url, $args) {
  $args_serialized = serialize($args);
  return CUSTOM_OEMBED_CACHE_KEY . md5("{$url}-{$args_serialized}");

 * This function caches the result
add_filter('oembed_result', function($data, $url, $args) {
  // Cache result
  set_transient(_wp_custom_oembed_cache_key($url, $args), $data, DAY_IN_SECONDS);
  return $data;
}, 999, 3);

 * This function serves the cached result
add_filter('pre_oembed_result', function($result, $url, $args) {

  // Clean out empty oembed calls caused by Divi
  if(trim($url) === '') {
    return '';

  // Return cached result if available
  if($cached_result = get_transient(_wp_custom_oembed_cache_key($url, $args))) {
    return $cached_result;

  return $result;
}, 2, 3);

Configure how often WordPress sends recovery mode emails about errors

By default, WordPress sends a maximum of one email per day to the site administrator if a fatal error is discovered. (Since WordPress 5.2)

To configure this, use the following snippet:

add_filter('recovery_mode_email_rate_limit', function($rate) {

Instead of using one of the predefined constants like HOUR_IN_SECONDS, you can use any integer value, like 600, which would correspond to every 10 minutes.

Sending emails to multiple users when WordPress goes into recovery mode

WordPress 5.2 pioneered a new “Recovery mode” feature that can notify site administrators by email if their site experiences a fatal error.

By default, the email is sent to either an email configured under the RECOVERY_MODE_EMAIL constant or the user that created the website (admin_email option).

If you want this email to multiple addresses, you can use the snippet below in your themes functions.php or in a plugin. Configure the extra address on line 6.

add_filter('recovery_mode_email', function($email, $url) {
  $email_array = [];
  $email_array[] = $email['to'];

  // Adds another email
  $email_array[] = '';

  $email['to'] = $email_array;

  return $email;
}, 10, 2);