Last updated: 28 April 2021

Updating WordPress can easily be done from the WordPress dashboard, and you can even configure automatic updates. Sometimes, though, you may need to manually update WordPress. This can be necessary if your website is badly broken or has been compromised.

It is worth mentioning that you can use WP-CLI to update WordPress. If you got SSH access and you are comfortable on the command line then you might want to learn more about installing and using WPI-CLI.

Is a manual update needed?

Manually updating WordPress is fairly straight forward. It is also relatively risky, and often there are alternatives for a manual update. For instance, if your website is showing errors then you should look into the errors first – fixing errors makes more sense than a “nuke and pave” approach. So, determine what the error is, check any error logs and enable WordPress debugging.

The main use-case for manually updating WordPress is to fix websites that have been hacked. When a website is hacked there may be malicious files all over the place. Trying to find the files manually is difficult and time-consuming, so it makes sense to delete all files that can be deleted and reinstall WordPress.

However, if you got an up to date backup that is clean then you should use that to replace the entire website. The reason that is a better alternative is that you don’t want to delete anything in the wp-content directory. This is where assets such as images live, as well as your website’s plugins and themes. It is quite possible that the directory has also been infected, and manually updating WordPress won’t remove the malicious files.

In short, manually updating WordPress is a last resort option. With that lecture out of the way, let’s get started!

Make a backup

Before removing anything you should make a backup of the website files. If anything goes wrong you got something to revert to. Of course, we also got backups – we take a full daily backup of every server and keep copies for seven days – but making a backup is still strongly recommended.

You can quickly take a backup using the tar utility. Here, we are making a tar.gz backup of the public_html directory from our home directory.

[example@cpweb6-premium ~]$ tar -czf website_files.tar.gz public_html

Download WordPress

We can now download and extract WordPress. As we are about to delete most of the contents of the public_html directory we keep the new WordPress files outside the website’s root directory – we will copy the files to the website’s root directory later.

[example@cpweb6-premium ~]$ wget https://wordpress.org/latest.tar.gz
[example@cpweb6-premium ~]$ tar -xzf latest.tar.gz

Consider files that need to be kept

Before we purge files and directories in the public_html directory you need to check if there is any data that needs to be kept. You almost certainly need the database credentials and table prefix that are stored in the wp-config.php file, and you may have specific rules in your .htaccess file that you want to keep. If you have a custom set-up then there may be other files you need to look at as well.

If you are sure that the .htaccess and wp-config.php files are not corrupted then you can copy the files from the public_html directory to the wordpress directory. In the case of the wp-config.php file a better option may be to create a new wp-config.php file and manually update the database credentials and, if applicable, the table prefix. For this example we simply copy both files:

[example@cpweb6-premium ~]$ cp public_html/wp-config.php wordpress/
[example@cpweb6-premium ~]$ cp public_html/.htaccess wordpress/

Deactivate plugins

It is good practice to disable plugins when you manually update WordPress. You can do that by updating the database, either via phpMyAdmin or from command line. To disable all plugins the value of active_plugins in the options table needs to be changed to a:0:{}.

If you prefer the command line then you can disable all plugins using two commands. The first command retrieves the ID of the record you want to change, and the second updates the record. Obviously, the commands below are an example only. You need to change the name of the database user and database, as well as the name of the options table (which is c2_options in the below command).

[example@cpweb6-premium ~]$ mysql -u example_usr -p example_wpdb20 -e "SELECT option_id, option_name, option_value FROM c2_options WHERE option_name = 'active_plugins';"
Enter password: 
+-----------+----------------+---------------------------------------------+
| option_id | option_name    | option_value                                |
+-----------+----------------+---------------------------------------------+
|        33 | active_plugins | a:1:{i:0;s:25:"two-factor/two-factor.php";} |
+-----------+----------------+---------------------------------------------+

Here, the ID is 33. You can now update that row:

[example@cpweb6-premium ~]$ mysql -u example_usr -p example_wpdb20 -e "UPDATE c2_options SET option_value = 'a:0:{}' WHERE option_id = 33 LIMIT 1;"
Enter password: 

Delete the files

We now get to the scary part. We are going to delete most the WordPress files in the public_html directory. As already mentioned, the main exception is the public_html/wp-content directory.

Let’s first list the contents of the public_html directory. As you can see, we got three WordPress directories (wp-admin, wp-content and wp-includes, as well as various files:

[example@cpweb6-premium ~]$ ls -1FA --group-directories-first public_html/
.well-known/
cgi-bin/
wp-admin/
wp-content/
wp-includes/
.htaccess
index.php
license.txt
readme.html
wp-activate.php
wp-blog-header.php
wp-comments-post.php
wp-config.php
wp-config-sample.php
wp-cron.php
wp-links-opml.php
wp-load.php
wp-login.php
wp-mail.php
wp-settings.php
wp-signup.php
wp-trackback.php
xmlrpc.php

You can purge the files we want to replace with the following commands:

[example@cpweb6-premium ~]$ rm -rf public_html/wp-admin/ public_html/wp-includes/ public_html/.well-known
[example@cpweb6-premium ~]$ rm -f public_html/.htaccess public_html/license.txt public_html/readme.html public_html/index.php public_html/xmlrpc.php public_html/wp-*.php

[example@cpweb6-premium ~]$ ls -A1F --group-directories-first public_html/
cgi-bin/
wp-content/

In case you are wondering, it is safe to remove .well-known directory. The directory is not part of WordPress (it is used to validate SSL certificates) but we often see malicious PHP scripts in this directory when a website has been compromised. Purging the directory doesn’t do any harm, so we may as well get rid off it.

Sync the new files

You can now use rsync to copy the files in the wordpress directory to the public_html directory:

[example@cpweb6-premium ~]$ rsync -a wordpress/ public_html/

rsync won’t remove any files in the wp-content directory. However, it will overwrite existing files if the file size is different. In most cases that is what you want, but you can add the --ignore-existing option if you want to be extra cautious.

As an aside, it is important to understand how rsync works. It is one of those utilities that is really useful but can do a lot of damage. For instance, the trailing slashes (/) in the above command are absolutely necessary. If you want to learn more, we got an article on using rsync to copy files.

Check the site

Your website should now be working again. Test the website without being logged in, and then check if the WordPress dashboard is working as expected. And don’t forget to active the plugins you disabled earlier!

Updating plugins and themes

So far we have only looked at manually updating the core WordPress files. It may be necessary to also update any plugins and themes you have installed. If so, the process is pretty much identical:

  • Download the tarball or Zip file for the plugin or theme.
  • Delete the plugin or theme files.
  • Extract the archive.
  • Check if the plugin or theme is working.