One thing that I have to do fairly frequently is to move a WordPress site from one server to another. Examples of this can be moving a site from staging to production or production to my local environment. There are a lot of ways to do this but I thought I would document my process. The workflow I am going to walk through assumes that you have shell access to the server in both the source and target locations.
The first thing that I do is back up and archive the files that I want to move to a new location. The transfer of files will be much more efficient when downloading one large archive file then if downloading each file individually through FTP. It is my understanding that repeatedly connecting for small individual files via FTP really slows down the transfer of the files. When connected to the source site’s server through SSH, I will tar and gzip the site’s folder to create a single file containing all my site’s files. Here is the command that I use:
tar -zcvf website.tar.gz public_html
This command will take all of the files in public_html
and compress them into a new archive file called website.tar.gz
. Of course I will replace “website” in my archive file name with something representing the site I am actually transferring to avoid confusion. Let me explain the command above in a little more detail.
First, I am using Unix’s tar utility to package up my website’s folder. I am sending four flags to tar, z, c, v, and f. What do these do? Well, you can find the full documentation for tar
on the command line by simply typing tar man
. But these particular flags do the following:
- z – Tells tar to use gzip to compress the archive it creates with the files. So, it will take the single resulting file and compress it to a smaller size. That is also why I name my file with the
.gz
extension to denote it as being processed with gzip. - c – This simply lets
tar
know that I am creating a new archive file. - v – The v flag is totally optional. It just turns on “verbose” output so you will see what your command is currently doing and let you feel like you’re starring in The Matrix as the list of processed files quickly scrolls by in your terminal.
- f – Tar is instructed to write to the specified file. In my case, this is
website.tar.gz
.
There are a couple of things that I will check at before creating the archive.
- Is there enough drive space on the server for the resulting archive? Obviously, you will need to have enough room on the drive for your site’s archive file.
- Are there full site backups of the site or other unnecessary large files within your site’s folder? For example, it will make your site’s archive a lot larger than needed if you are including multiple copies of the site in the archive file. Many WordPress backup plugins will create backups in the
wp-content
directory. The verbose flag can help determine if there are any huge files in the site directory as the output will pause whentar
is working on a large file like a backup or Photoshop file. Another example of a directory you don’t necessarily need is thenode_modules
directory. You could recreate these from thepackage.json
file if needed.
With tar, you can exclude certain files from the archive. Here is an example of creating a tar archive of a site without the wp-content
directory:
tar --exclude "public_html/wp-content" -zcvf public_html.tar.gz public_html
After the archive is created, I will list the contents of the directory just to make sure it looks like the resulting file was created correctly.

Obviously, if the file size is 0 or very small, you will know that something likely went awry.
Now we have our site files. The other thing that we will need is the site’s database. Since we are already at the command line, I will just run a mysqldump
command to generate a SQL file. Another option would be to use a tool like phpMyAdmin to simply export the database.
If I don’t remember the database credentials, I will open up the wp-config.php
file on the command line. I use the less
command for something this simple but you could also use vim
, nano
, or something else.
less public_html/wp-config.php
I will open up a text editor to store the database name, user, and password from the wp-config.php
file for easy access.

If you haven’t used less
in the past it is helpful to know that you can exit out of it by hitting the q
key.
With the credentials stored in a handy location, I will create the mysqldump
command and run it from on the server where I created my site archive.
mysqldump -u root -p'root' -h localhost local > local.sql
This command simply passes in the username (-u
), password (-p
), database name (local
), and tells it to dump the database to a file called local.sql
. I wrap my password in single quotes to handle any special characters that otherwise would need to be escaped. Again, I will verify that the resulting SQL file makes sense size-wise by listing the files in the directory.
We now have the files that we need to stand the site up on another server. I transfer them to the target location using an FTP program like Panic’s Transmit. I put the files on the same level as the source site’s folder. For example, let’s say I have created a site using Local by Flywheel on my laptop. The folder structure that Local uses for websites is:
<Local Sites Directory>/<site name>/app/public
The public
folder is the root of the WordPress site where your wp-config.php
file lives. My file structure will look something like this:

Since Local by Flywheel helpfully creates the wp-config.php
file for you, I like to keep that. After all, your credentials for the site on the production or staging server will be different. So I copy the wp-config.php
file outside of the site’s folder that will soon be replaced.
mv public/wp-config.php .
The above command will move the wp-config.php
to outside of the site folder to where you are working.
Now we are ready to move the website’s files into place. You can remove the the starter site that Local created for you now that wp-config.php
has been moved out. I will delete the folder:
rm -Rf public
Be very careful with this command. It will totally delete the public
folder. If you have multiple tabs open in your SSH program you will want to be absolutely sure you’re in the correct place and not deleting your production website.
With the old site gone, I extract my archive file to create the site directory:
tar -xvf website.tar.gz
The v and f flags are the same as before. But instead of creating a new archive and compressing it, we are extracting the archive with x.
Something to keep in mind is that Local by Flywheel uses the directory public
to hold the website. Many other hosts tend to use public_html
. So if there is a discrepancy, you will need to rename the extracted folder. If my production host uses public_html
and I am moving it to my Local by Flywheel site I will just rename it using the mv
command:
mv public_html public
I will then move the wp-config.php
file into the extracted site’s folder to ensure we have the correct database credentials and WordPress configurations for the site.
mv wp-config.php public/
This will replace the configuration file from the source website. But this just gets us ready to read the database. We still will need to import the SQL file from the site we are moving.
I delete the existing database tables for the site. I do this using my favorite MySQL client, Navicat. You could also use phpMyAdmin or Local’s included tool, Adminer.

Now, import the SQL file that you created from the source site. I also will do this in Navicat. You could also do this in other tools or directly from the command line using MySQL if you have MySQL available on your command line:
mysql -u root -p'root' local < database.sql
The above command specifies the username, password and instructs the command to replace the database, local
, with the contents of database.sql
.
We now have site files set up and a configuration that will read the imported database. We need to make a couple changes to our database content first, though, using a MySQL client tool. Specifically, we want to modify WordPress’s settings for the site’s address. If we were to try to go to our new site with the freshly-imported database we would be redirected to our source site. So, let’s open up the wp_options
table in our MySQL client. We will look for two records – the records with option_name
values of siteurl
and home
. The option_value
for these records, which will likely be near the top of the wp_options
table, will be the source site’s URL. Change the option_value
to your new site’s URL:

With that done, we can go to our new site’s WordPress admin. In the above example, I would go to http://starter.local/wp-login.php. Because we copied the database from the source site, the users will have been moved over to the new location. So any administrator you had at the existing website will be valid at your site’s new instance. So go ahead and log in!
We replaced just two instances of the site URL to represent the site’s new location. But other existing fields will still reference the source location. So, if you were clicking around the copy of the site, you will soon follow a link back to the source site. We need to update the URL in all of our database’s records. To do this, I will often install Delicious Brain’s Better Search Replace plugin.

There are other tools that you could use to update the URLs in your site’s database. One notable option would be WP CLI which allows you to make the replacements on the command line. In fact, if you have access to WP CLI, you could also use it to remove all the tables from your database, import the SQL file, and replace your URLs all from the command line. So you could likely write a shell script to do a few steps in a single command.
With the URLs updated, you should be all set to use the copy of your site. If your site is broken, one thing to check would be your .htaccess
file in the root directory. Sometimes a host will have server-specific settings in there that will not be compatible with your new server. A common example I see is when a version of PHP is specified. Another file that can commonly cause trouble is the php.ini
file which, again, sets up host options that might not work with the destination server.