Disable PHP warnings when running wp-cli

It is not uncommon to test sites on a development environment (locally or in a staging environment where others can see the work-in-progress). On a development environment, usually we have configured WP_DEBUG to be true. Here’s the sample of wp-config.php file in a development / test / staging environment…


<span class="hljs-function"><span class="hljs-keyword">define(<span class="hljs-string">'WP_CACHE'</span>, <span class="hljs-literal">false</span>)</span></span>;

<span class="hljs-function"><span class="hljs-keyword">define(<span class="hljs-string">'DB_NAME'</span>, <span class="hljs-string">'actual_db'</span>)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">define(<span class="hljs-string">'DB_USER'</span>, <span class="hljs-string">'db_user'</span>)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">define(<span class="hljs-string">'DB_PASSWORD'</span>, <span class="hljs-string">'Super_Secret_Passw0rd'</span>)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">define(<span class="hljs-string">'DB_HOST'</span>, <span class="hljs-string">'localhost'</span>)</span></span>;

<span class="hljs-function"><span class="hljs-keyword">define(<span class="hljs-string">'WP_DEBUG'</span>, <span class="hljs-literal">true</span>)</span></span>;

/<span class="hljs-regexp">/ Other directives such as salts...</span>
<span class="hljs-regexp"></span>

While the above code is perfectly okay, if the site creates PHP warnings, it is a nuisance to see them repeatedly when using wp-cli multiple times. Even if you try configuring error_reporting to various values and turn off everything under the hood, you may still see PHP warning with the above code. It’s because the the warning are configured to be displayed when WP_DEBUG is set to true. In order to disable WP_DEBUG only for wp cli operations, the following modified code can be used…


define('WP_CACHE', false);

define('DB_NAME', 'actual_db');
define('DB_USER', 'db_user');
define('DB_PASSWORD', 'Super_Secret_Passw0rd');
define('DB_HOST', 'localhost');

if(!defined('$_SERVER["HTTP_HOST"]')) define('WP_DEBUG', false);
if( !defined( WP_DEBUG ) ) define('WP_DEBUG', true);

// Other directives such as salts...

Basically, we added the following code into the original code. The first line checks if it is a visit from a browser or from command line. When we evoke wp from command line, it doesn’t send HTTP_HOST. This way, we can tweak WP_DEBUG depending on the presence (or absence) of HTTP_HOST.

if(!defined('$_SERVER["HTTP_HOST"]')) define('WP_DEBUG', false);
if( !defined( WP_DEBUG ) ) define('WP_DEBUG', true);

The above code eliminates most PHP warnings when running WP-CLI. I hope this helps someone. There are multiple steps involved in getting a perfect development, local, test or staging environment for a WordPress site. If you are looking for a perfect hosting environment or a customized server to meet a better workflow for your developments, please get in touch.

Nginx compatibility for “Cookies for Comments” plugin

Whether you are aware or not, spammers are more interested in your site than anyone else. You’ll understand this more vividly, when your blog starts to become famous and brings more and more visitors week after week, month after month, year after year.

The most annoying thing about spam comments is the amount of time that you need to waste in dealing with it. There are some bright minds in WordPress that help you save your time with spam comments. One such person is Donncha, who put together a nice plugin named Cookies for Comments that blocks the spam at the server level, in such as a way, it doesn’t even reach WordPress. Because, all the work is done by the server itself. Here, we show an example code for Apache and Nginx web server. It could be migrated any web server in general.

Integrating with Apache is straightforward. The code to configure Apache is displayed at the bottom of plugin’s settings page at https://example.com//wp-admin/options-general.php?page=cfc_config. It’d look like this…

# If you're feeling adventerous, you can add the following two lines before
# the regular WordPress mod_rewrite rules in your .htaccess file.
# They will stop comments from spambots before they reach
# the database or are executed in PHP:

RewriteCond %{HTTP_COOKIE} !^.*abcdefghijklmnopqrstuvwxyz0123456789.*$
RewriteRule ^wp-comments-post.php - [F,L]

In the above code, the value of abcdefghijklmnopqrstuvwxyz0123456789 may change for each site. It is also part of the name of the cookie set by this plugin.

In Nginx, the code is little different. Here’s the actual code…

# support for cookies for comments plugin!
location = /wp-comments-post.php {
    if ($http_cookie !~* "abcdefghijklmnopqrstuvwxyz0123456789") { return 403; }
    # rest of the code to process PHP.

Considering Akismet can not be used on a commercial site, this solution works great. With Akismet, there is a lot going on behind the scene. With ‘Cookies for Comments’ plugin, a cookie is set for all the visitors and it is checked when a comment is posted by the same visitor. Since, this plugin sets a cookie for all visitors, you may use GDPR consent to include this cookie at the top of every comment form. At least, you could inform about cookies before they comment like how it is done on this site’s comment box…

comment form with cookie warning
Sample comment form showing a warning of cookies being used!

By adding just two lines of code, we can save a lot of trouble and frustration in the long run. If you have any other method to tackle spam, please share it in the comments!

Delete all pending comments at once

If you are presented with a new website with thousands or millions of pending comments and if you know that all pending comments are actually spam comments, you’d want to quickly get rid of all such comments before you do anything further on that site to speed-up your actual workflow. Here’s a quick tip to delete all pending comments.

wp comment delete $(wp comment list --status=hold --format=ids) --force

Word of warning: Never execute any random command found on the internet. NEVER.


Before executing the above command, lets break it up and understand what’s going on.

wp comment list --status=hold --format=ids

The above command creates a list of IDs (of comments) that are under moderation queue (pending).

wp comment delete $(wp comment list --status=hold --format=ids)

The above command will send all the pending comments to trash. Any comment in trash is usually get deleted automatically after 30 minutes. If you’d like to delete a comment permanently skipping trash, then we can use --force flag.

Buypass CA – SSL with 180 Days Validity

Buypass is a Certificate Authority (CA) based on Europe. It offers free SSL certificates with a validity of 180 days. Unlike LetsEncrypt, Buypass CA also offers paid SSL too. So, it is neither a competitor to LetsEncrypt, nor it is a nonprofit. It is a for-profit company that also offers free SSL certificates. There are other CAs that offer free SSL certificates too. However, Buypass CA offers ACME API that is compatible with LetsEncrypt. For example, certbot can be used to authenticate the domain and obtain free SSL certificates.

Starting Afresh

Certbot is the recommended tool / client-side software. However, the procedure for test certificates and live certificates are slightly different, if you have used LetsEncrypt previously.

Here’s the procedure to get started with Buypass CA using certbot…

sudo certbot register --server 'https://api.test4.buypass.no/acme/directory'

The above command would do the following…

  • ask for your email
  • option to agree or disagree with the terms of service
  • an option to share your e-mail address with EFF

If you would like to shorten this long process, you may use the following one-liner, replacing ‘YOUR_EMAIL’ with your actual email address…

sudo certbot register -m 'YOUR_EMAIL' --no-eff-email --agree-tos --server 'https://api.test4.buypass.no/acme/directory'

Once the email is registered, we are free to test drive the domain authentication and fetching the test SSL certificates by running the following command…

sudo certbot certonly --webroot -w /var/www/example.com/public_html/ -d example.com -d www.example.com --server 'https://api.test4.buypass.no/acme/directory'

Please know that the test certificates can not be used on live sites.

The above command issues real certificates for testing purpose, even though the test certificates cant’ be used on live domains. Since, the test certificates are real, we have to remove them before fetching live SSL certificates for live domains. We can remove the test SSL certificates using the following command and selecting correct options when prompted…

sudo certbot delete

Output of the above command would look something similar to the following…

Saving debug log to /var/log/letsencrypt/letsencrypt.log

Which certificate(s) would you like to delete?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: tinywp.dev
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel):

Please be careful on selecting the correct certificates to delete. If you hit “Enter” key without reading the above, you are likely to lose all the certificates listed in it, including the live SSL certificates, if any. If everything goes well, it is time to go live.

Obtaining Live Certficates

Once testing is successful, obtaining the live certificates is likely to go through as expected. The only difference between the test and live environment is the server URL. For live environment, Buypass CA uses “https://api.buypass.com/acme/directory”.


While the advantage of using Buypass CA is in the extended validity, compared to LetsEncrypt, there are a few limitations…

  • The free Go SSL certificates from Buypass CA doesn’t allow wildcard. It doesn’t mean wildcard isn’t supported at all. Wildcard is a paid product from Buypass CA.
  • Total number of domains that we can attach to a single SSL certificate is limited to only two (enough for 99% of the sites on the internet). So, it can easily cover the bare / root domain example.com and the www version www.example.com.
  • There is no dry-run. As seen earlier, the testing process is bit complicated than LetsEncrypt where we can do “dry-run” of authentication. However, with Buypass CA, we authenticate the domain/s, and then fetch test SSL certificates that need to be deleted before fetching the live SSL certificates.

Switching from LetsEncrypt

Switching from LetsEncrypt isn’t hard. Delete the existing certificate and do the above steps. If you ever go wrong, you can always go back and re-issue a free SSL certificate from LetsEncrypt.


Overall, SSL certificate with 180-days validity is the main reason to go with Buypass Go SSL. Also, if you are a person like me who doesn’t always depend on a single entity (even it means nonprofit), then this is a real alternative to LetsEncrypt. Compatibility with ACME API makes it easier to switch from existing LetsEncrypt installations where only the bare domain and www version need to be covered under HTTPS.

Version Control


WordPress uses SVN internally such as for plugins repository. So, a plugin author must use SVN tools to upload a plugin and update the existing plugin/s in wp.org plugins repository. If you are new to SVN, WordPress docs team has a nice guide to get started with SVN and plugins repository. The best practices section is a gem. It is possible to update WP core using SVN. However, things have been changing. Now, you can host your plugin in Github and push it to wordpress.org plugins repo.

Continue reading “Version Control”

Handling WordPress Cron failures using wp-cli

To understand WP cron, let me quote the following text from Cavalcade documentation

wp-cron is not actually a real task scheduler, and doesn’t actually operate like cron. Instead, it’s a pseudo-cron system, which is run as a loopback HTTP call when you access a page on the site (essentially, the page “forks” itself to run scheduled tasks). This is fine for high traffic single-sites, but lower traffic sites might not have their cron activated if the site isn’t viewed.

As you can see, even if a site has moderate traffic, if the site uses aggressive caching techniques, it is possible for real traffic to never hit PHP or WordPress to process a request. When wp-cron fails, a number of things can fail… a scheduled may not get published on time, a newsletter may not be sent as scheduled, an important WordPress security may not be applied, a backup schedule may have been missed, etc.

Cavalcade offers an excellent way to prevent such failures. It is more suitable for multi-site networks with hundreds of cron entries. For single sites, it is easier to handle a failure in wp-cron using wp-cli tool and a server cron.

Most common alternative is to disable internal WP cron and trigger it externally using wp-cron.php file and a server cron. If you are using this alternative method, then please remove cron entry and remove the corresponding entry in wp-config.php file, as wp-cli wouldn’t work if internal cron is disabled.

Let’s make sure we are ready to use the internal wp-cron by running the following command…

wp --path=/path/to/wordpress cron test

You’d see a success message… “Success: WP-Cron spawning is working as expected.“.

Most common error message is… “Error: The DISABLE_WP_CRON constant is set to true. WP-Cron spawning is disabled.” In this case, please disable the line in wp-config.php that disables wp-cron.

Now, it is time to set up a server cron that runs every minute like this…

* * * * * /path/to/wp-cli --path=/path/to/wordpress cron event run --due-now &> /dev/null


The 5 stars indicate a schedule to execute the command every minute. The command that follows executes cron events that are due now. Please make sure you provide full path to wp-cli and your WP site installation directory. I have tested the above in multiple sites on various servers. So far, it has been the most effective way to handle the failures in WP-Cron gracefully on single sites. If you run a multi-site network or a complex site with hundreds of crons, I’d highly recommend Cavalcade.

WordPress Migration Checklist

A successful WordPress site gets migrated to another host or another server within the same host, at least once in every few years for a number of reasons. Better operating system, to upgrade the PHP version, to handle larger traffic, etc. The first step is to test your migration on a staging server. Most issues (such as theme / plugins incompatibility) can be caught, if we test our migration using an existing (full) backup of the site. So, the following list applies only to live site migration. Here are the list of areas to check after the migration…


There are hundreds of plugins available to take a complete backup of your site. Each one is prone to errors. If you visit the official wp.org support forum for each of the backup plugins, you’d understand what I mean. The new host (or the server) may have some limitations that may not work well the current backup plugin. That’s why it is recommended to use an external backup solution that doesn’t rely on WordPress core, or its plugins. I have a dedicated Github project to take backups of WordPress sites using simple bash scripts.

Four things to consider while checking backups.

  1. Make sure local database backups are taken correctly.
  2. Make sure local files backups are taken correctly.
  3. Make sure offsite backups are working.
  4. Make sure the backups are verified.

No backup is useful, if it is not verified for authenticity of what it contains. That’s why it is important to have a workflow to test the backups, with automated tests.


Unlike backup (that is basically a complex process), email is relatively an easy process. That’s why there are thousands of plugins available related to emails in wp.org repo. Because, people use email in a WordPress site for a number of reasons. To send a upgrade email to administrator, to get messages via contact form, to send newsletters, to get informed about the new user registration, to get notified about new e-commerce order, etc.

Even though, email is as old as internet itself, reaching inbox is a complex process. Most common issue with WordPress sites is that the email never leaves WordPress in most cases and would be simply dropped at WordPress itself. That’s why the most used contact form plugin still recommends an add-on plugin to keep an archive of all messages that come via the contact form.

If you use a SMTP service and a plugin to send emails via WordPress, then, in most cases, the emails would continue to work, after the migration. It is still recommended to test emails by submitting a test entry via the contact form.

If you depend on the server or host to send emails, then you may be in trouble, as certain host blog ports related to sending emails. For example, Google blocks port 25 on its compute platform.

Other items to check

There are some less-priority items to check after the migration. Here’s an incomplete list…

  • Cron: Check if cron hasn’t stopped and continues to run events as expected.
  • Cache: Check if your full-page caching plugin caches posts and the web server serves cached content if exists. Server response time is the most critical factor. You may check it at https://web.dev/ or in Google Search Console that provides historical data.
  • CDN: If your CDN depends on the IP of the server and if your server IP changes, then it may not be able to pull the content. You wouldn’t even aware of this, as CDN caches contents for an average of 24 hours and checks for newer content only once in a day or week. So, you’d understand the CDN related issues only if you create and publish a new post with images.

I will add other items as and when I come across or if it is important. Do you have a checklist whenever you migrate? If I missed anything, please feel free to add it via the comments section.

Happy hosting!

DigitalOcean Hosting Review

No affiliate links are inserted in this post. I did not have and do not have any monetary benefits in posting a positive review about DigitalOcean (or any other hosts in this blog), either. No host is perfect. So, I am posting only what I like about DigitalOcean. There may be some cons of using any host. If you search the internet, you may find them. I use Google Compute Engine for hosting this site, though.

Logo - DigitalOcean

Time to Restart the Whole Server

Whenever I build a server from scratch, I make sure that the automatic security updates are enabled. Most security updates are applied automatically. However, certain updates, particularly, the updates related to Linux kernel needs a restart of the entire server. This means a short downtime of the site/s hosted in that particular server. No one likes a downtime. At least, Google Search Bots do not.

The server restart and the resulting downtime is the primary reason for the popularity of containers such as Docker, LXC, LXD, etc. Of course, there are other benefits of using a container. Not everyone can afford to use containers, load balancers, etc. Maintaining them cost a lot more than maintaining a single server without the container eco-system. Most individual site owners and small businesses opt for single server system to lower the cost of running a server.

In shared hosts, the Linux kernel updates are applied on-the-fly, using one of the multiple technologies available, such as kpatch, livepatch (commercial), etc. Most of them are commercial services. The free services come with a big warning such as the following…

WARNING: Use with caution! Kernel crashes, spontaneous reboots, and data loss may occur!

So, most vendors (shared hosts) opt for paid commercial services to apply Linux kernel updates without a complete restart of the server. If you use a shared host, then no worries. This post is valid only for VPS servers and dedicated servers. The term VPS may also mean cloud servers with certain hosts. Some popular VPS providers are AWS (Amazon Web Services), Google Compute Engine (GCE), Linode, DigitalOcean, Vultr, etc.

The time to restart a server (VPS or dedicated server) depends on various factors. The number of running processes, the number of running programs, the choice of web server, the database server, etc. Every tiny thing matters. Even the hypervisor used by the host can have an impact on the time to restart the whole server.

Choosing a host is hard. There are lots of factors to consider. Price, the location of data centers, the Operating Systems provided, the bandwidth or the network speed, overall value for money, etc. Among these, you might also want to consider the time to restart a server when choosing a host. Because, some hosts take more than 5 minutes to restart a server, resulting in a downtime that may be a considerable amount for Google Search Bots.

In my experience working with various hosts, DigitalOcean restarts the servers in the shortest time possible, often less than 30 seconds for a server with a memory of up to 32GB. An idle server with no sites hosted on it, may restart much more quickly, like less than 15 seconds.

There are other advantages of choosing DigitalOcean. I will update this post with those, if it is worth mentioning.

Happy Hosting!