This article will contain a number of tips and tricks when working with WordPress.
Working with permaLinks
When changing permalinks around in wp-admin, WordPress will warn you if it is unable to make the changes directly to your .htaccess file. This happens when:
- The main .htaccess file for the site is not writable by the web server user - The Apache vhost setting, AllowOverride, is not set to 'All' - Apache mod_rewrite may not be enabled
If wp-admin is unable to write the changes into the .htaccess, you can do this manually by:
[root@web01 ~]# vim /var/www/vhosts/example.com/.htaccess ... # BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress ...
Password protecting wp-admin
As people can oftentimes use weak passwords for their WordPress users, it is recommended to password protect the entire wp-admin login portal with a strong password as shown below:
First create the htaccess username and password:
[root@web01 ~]# htpasswd -c /etc/httpd/conf.d/example.com-wp-admin-htpasswd your_username
Then update the .htaccess file within the wp-admin directory by:
[root@web01 ~]# vim /var/www/vhosts/example.com/wp-admin/.htaccess ... <Files admin-ajax.php> Order allow,deny Allow from all Satisfy any </Files> AuthType Basic AuthName " Restricted" AuthUserFile /etc/httpd/conf.d/example.com-wp-admin-htpasswd Require valid-user ...
Disable PHP execution in uploads directory
When a site becomes compromised, malware is often uploaded that can be executed easily. Below is a common example for disabling PHP execution within the wp-content/uploads directory to help minimize the impact of a compromise:
[root@web01 ~]# vim /var/www/vhosts/example.com/wp-content/uploads/.htaccess ... # Prevent PHP execution <Files *.php> deny from all </Files> ...
Blocking xmlrpc.php attacks
XML-RPC is often subjected to brute force attacks within WordPress. These attempts can create severe resource contention issues, causing performance issues for the site.
Before blocking this blindly, there are modules such as JetPack, WordPress Desktop and Mobile apps that need XML-RPC enabled. So use caution! JetPack can mitigate these brute force attacks if the option is enabled within the plugin.
First determine if xmlrpc.php is being brute forced by checking your site’s access log as shown below. Generally hundreds or thousands of these entries would be found within a short period of time.
[root@web01 ~]# tail /var/log/httpd/example.com-access.log .... xxx.xxx.xxx.xxx - - [19/May/2016:15:45:02 +0000] "POST /xmlrpc.php HTTP/1.1" 200 247 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1" xxx.xxx.xxx.xxx - - [19/May/2016:15:45:02 +0000] "POST /xmlrpc.php HTTP/1.1" 200 247 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1" xxx.xxx.xxx.xxx - - [19/May/2016:15:45:03 +0000] "POST /xmlrpc.php HTTP/1.1" 200 247 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1" ...
The brute force attacks against xmlrpc.php can be blocked by adding the following in the site’s .htaccess file:
[root@web01 ~]# vim /var/www/vhosts/example.com/.htaccess ... # Block WordPress xmlrpc.php requests <Files xmlrpc.php> order allow,deny deny from all </Files> ...
Force SSL on wp-admin
To force all logins for wp-admin to go over SSL, update the site’s wp-config.php with the options below. Just be sure to put this before the line “/* That’s all, stop editing! Happy blogging. */”:
[root@web01 ~]# vim /var/www/vhosts/example.com/wp-config.php ... define('FORCE_SSL_LOGIN', true); define('FORCE_SSL_ADMIN', true); /* That's all, stop editing! Happy blogging. */ ...
Make WordPress aware of SSL termination on the load balancer
When using SSL termination on the load balancer or perhaps through something like CloudFlare, you can sometimes create a redirect loop on the site. WordPress needs to believe that everything is really going over SSL since the load balancer is already handling that, and not the server. This can be corrected by adding the following near the top of the site’s .htaccess file:
[root@web01 ~]# vim /var/www/vhosts/example.com/.htaccess ... SetEnvIf X-Forwarded-Proto https HTTPS=on ...
Search for outdated versions of WordPress
A primary reason why WordPress sites get compromised is due to outdated versions of the software. If a server has dozens of WordPress sites, it can be time consuming to determine what sites are running what versions. Shown below is a quick method of obtaining the versions of WordPress on the server:
[root@web01 ~]# yum install mlocate [root@web01 ~]# updatedb [root@web01 ~]# locate wp-includes/version.php | while read x; do echo -n "$x : WordPress Version " && egrep '^\s*\$wp_version\s*=' "$x" | cut -d\' -f2; done | column -t -s : /var/www/vhosts/example.com/wp-includes/version.php WordPress Version 4.3.1 /var/www/vhosts/example2.com/wp-includes/version.php WordPress Version 4.3.3 /var/www/vhosts/example3.com/wp-includes/version.php WordPress Version 3.9.4 /var/www/vhosts/example4.com/wp-includes/version.php WordPress Version 3.7.1
Compare the versions returned against the following site to see how old the version is:
https://codex.wordpress.org/WordPress_Versions
Error establishing a database connection
This error usually means one of three things:
- The database credentials within wp-config.php may be wrong - The database server is busy and cannot accept additional connections - The database itself may be corrupted
To ensure the database credentials are correct, test them by doing the following:
[root@web01 ~]# cat /var/www/vhosts/example.com/wp-config.php | grep -iE 'DB_USER|DB_PASSWORD|DB_HOST|DB_NAME' define('DB_NAME', 'example'); define('DB_USER', 'wordpress'); define('DB_PASSWORD', 'mysecurepassword'); define('DB_HOST', 'localhost'); [root@web01 ~]# mysql -h localhost -uwordpress -pmysecurepassword mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | example | +--------------------+ 2 rows in set (0.00 sec)
Confirm that Apache MaxClients does not exceed the max-connections variable within MySQL. While this example is specific for CentOS 6, it can be easily adapted for any distro. To check these variables, run the following:
[root@web01 ~]# cat /etc/httpd/conf/httpd.conf |grep MaxClients |grep -v \# | head -1 MaxClients 63 [root@web01 ~]# mysql -e 'show variables where Variable_name like "max_connections";' +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | max_connections | 65 | +-----------------+-------+
Check for database corruption by adding the following before the line ‘/* That’s all, stop editing! Happy blogging. */’ in the wp-config.php:
[root@web01 ~]# vim /var/www/vhosts/example.com/wp-config.php ... define( 'WP_ALLOW_REPAIR', true ); ...
From there, do the following to repair the corruption:
- Point your browser to the following URL replacing placing the domain according: http://www.example.com/wp-admin/maint/repair.php - Select 'Repair database' - Once done, remove the WP_ALLOW_REPAIR from the wp-config.php
Reset the WordPress Admin password
If you find yourself locked out of wp-admin, you can restore access to the portal by updating the active themes function.php file right after the opening comments as shown below. Just be sure to remove this code immediately after the password is updated:
[root@web01 ~]# vim /var/www/vhosts/example.com/wp-content/themes/twentyfifteen/functions.php <?php wp_set_password( 'your_secure_password_here', 1 ); ...
Another way of resetting the admin password is to update MySQL directly by:
[root@web01 ~]# mysql mysql> use your_wordpress_db_name; mysql> UPDATE wp_users SET user_pass=MD5('your_new_password_here') WHERE user_login='admin';
Find number of SQL queries executed on each page load
To quickly determine how many queries a page is making to the database, add the following to the active theme’s footer.php near the top:
[root@web01 ~]# vim /var/www/vhosts/example.com/wp-content/themes/twentyfifteen/footer.php ... <?php if ( current_user_can( 'manage_options' ) ) { echo $wpdb->num_queries . " SQL queries performed."; } else { // Uncomment the below line to show SQL queries to everybody echo $wpdb->num_queries . " SQL queries performed."; }?>
This will display the query count at the bottom of every page. The public will be able to see this, so do not leave this in your footer.php longer than needed. In the example above on the second to last line, you can comment that out so only someone logged into WordPress will be able to see the results.
Enable the WordPress debug log
WordPress has the ability to log all errors, notices and warnings to a file called debug.log. This file is placed by default in wp-content/debug.log. This will hide the errors from showing up on the production site, and simply allow the developers to review them at their leisure.
To enable this, first create the log file and allow it to be writable by the web server user, then insert the following before the line ‘/* That’s all, stop editing! Happy blogging. */’ in the wp-config.php file as shown below:
[root@web01 ~]# touch /var/www/vhosts/example.com/wp-content/debug.log [root@web01 ~]# chown apache:apache /var/www/vhosts/example.com/wp-content/debug.log [root@web01 ~]# vim /var/www/vhosts/example.com/wp-config.php // Enable WP_DEBUG mode define( 'WP_DEBUG', true ); // Enable Debug logging to the /wp-content/debug.log file define( 'WP_DEBUG_LOG', true ); // Disable display of errors and warnings define( 'WP_DEBUG_DISPLAY', false ); @ini_set( 'display_errors', 0 ); ... /* That's all, stop editing! Happy blogging. */
Deactivating WordPress plugins
This is useful when trying to determine which plugins are causing memory leaks or overall performance issues. This should only be done after creating a backup of the database and also manually backing up the wp-content/plugins directory so a rollback option exists just in case.
Keep in mind this will break the site since you may be disabling plugins that the site requires to work.
If you prefer to disable to disable the modules one by one until the problem module is identified:
[root@web01 ~]# cd /var/www/vhosts/example.com/wp-content/plugins [root@web01 ~]# mv akismet akismet.disabled
To disable all the modules at once, then enable them one by one after testing the site each time to see if the issue manifests, do the following:
[root@web01 ~]# mkdir /var/www/vhosts/example.com/wp-content/plugins.disabled [root@web01 ~]# mv /var/www/vhosts/example.com/wp-content/plugins/* /var/www/vhosts/example.com/wp-content/plugins.disabled [root@web01 ~]# cd /var/www/vhosts/example.com/wp-content/plugins [root@web01 ~]# mv ../plugins.disabled/akismet . [root@web01 ~]# mv ../plugins.disabled/buddypress . etc