To migrate a WordPress site manually, you copy all of the site’s files to the new server, export the database from the old server and import it into a new database on the new one, update wp-config.php with the new database details, then fix file permissions and URL rewrites and test. No migration plugin is involved, which means no upload limits, no timeouts and no mystery about what was copied. It does mean you need server access and some comfort with the command line or tools like phpMyAdmin.
This guide shows how to migrate a WordPress site manually, step by step, with the commands we use and the errors you are most likely to meet.
When a manual migration makes sense
Before working out how to migrate a WordPress site manually, decide whether you should. Plugins are fine for many small sites. Going manual is the better choice when:
- The site is large (big uploads folder, large database) and plugins time out or hit upload limits.
- You are moving to a server you manage yourself, such as a VPS running Nginx.
- A plugin migration has already failed or produced a half-working site.
- You want to know exactly what moved and keep full control of the process.
If you just want the big picture of a host move, including testing before DNS changes and the cutover itself, read our guide to migrating WordPress to a new host first. This article is the hands-on part.
What you need before you start
- Access to the old server: SFTP or, ideally, SSH, plus database access through phpMyAdmin, a hosting panel or the MySQL command line.
- A new server ready to run WordPress: a web server (Nginx or Apache), PHP with the extensions WordPress needs, and MySQL or MariaDB. Check the current requirements on wordpress.org rather than relying on an old tutorial.
- The old database credentials, found in the old site’s
wp-config.php. - Enough disk space on both ends for the files plus a compressed archive.
- A quiet window, and an agreement that nobody edits the site while you work.
How to migrate a WordPress site manually: step by step
Step 1: Back up and freeze the old site
Take a full backup of files and database, and store it off both servers. Then stop changes: ask editors not to publish, and for stores or membership sites consider a maintenance mode so orders do not land on the old server after you export.
Step 2: Copy the files
With SSH access, the fastest approach is to compress the whole WordPress folder into one archive on the old server, then transfer that single file. Adjust the paths to match your server:
cd /var/www/example.com
tar -czf /tmp/site-files.tar.gz public_html
Move the archive to the new server with scp, rsync or SFTP, then extract it into the new web root:
tar -xzf /tmp/site-files.tar.gz -C /var/www/example.com
With SFTP only, download the full WordPress directory and upload it to the new server. It is slower but works. Either way, make sure you get:
- The core files and
wp-config.php. - The whole
wp-contentfolder: themes, plugins and especiallyuploads. - Hidden files such as
.htaccessand any.user.ini. Some SFTP clients hide these by default.
If you are moving to a fresh install of the same WordPress version, you could copy only wp-content and wp-config.php. Copying everything is simpler and avoids version mismatches.
Step 3: Export the database
From the command line on the old server:
mysqldump -u old_db_user -p old_db_name > /tmp/site.sql
You will be prompted for the password. In phpMyAdmin, select the database, choose Export and use the custom option to export all tables as SQL. For large databases the command line is more reliable, because browser exports can time out.
Compress the dump with gzip if it is large, then transfer it to the new server.
Step 4: Create the database and user on the new server
On the new server, open the MySQL or MariaDB prompt and create an empty database plus a user that only has access to it:
CREATE DATABASE wp_example DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_example_user'@'localhost' IDENTIFIED BY 'use-a-long-random-password';
GRANT ALL PRIVILEGES ON wp_example.* TO 'wp_example_user'@'localhost';
FLUSH PRIVILEGES;
On a hosting panel, you do the same through its database section.
Step 5: Import the database
mysql -u wp_example_user -p wp_example < /tmp/site.sql
In phpMyAdmin, select the new empty database and use Import. If the file is too large for the browser upload limit, use the command line instead.
If the import fails with an “unknown collation” error, the old server used a newer collation than the new one supports. The usual fix is to export again with a compatible collation, or edit the collation names in the dump to one the new server supports. Make a copy of the dump before editing it.
Step 6: Update wp-config.php
Open wp-config.php on the new server and update the database settings to match what you just created:
define( 'DB_NAME', 'wp_example' );
define( 'DB_USER', 'wp_example_user' );
define( 'DB_PASSWORD', 'use-a-long-random-password' );
define( 'DB_HOST', 'localhost' );
Leave the table prefix setting exactly as it was on the old site. If the prefix in wp-config.php does not match the table names in the imported database, WordPress will behave as if it is not installed and offer to run the setup. While you are in the file, check for any hard-coded WP_HOME or WP_SITEURL values and any paths that point to the old server.
Step 7: Set ownership and permissions
Files copied from another server often arrive with the wrong owner. The web server user needs to be able to read everything and write to the uploads folder (and to other folders if you update plugins from the dashboard). The WordPress documentation recommends 755 for directories and 644 for files as a baseline. Set the owner to the user your PHP process runs as on the new server, and keep wp-config.php unreadable to other users on shared servers.
Step 8: Handle URL changes, if any
If the domain is staying the same, skip this step. You do not need to touch URLs in the database.
If the address is changing (new domain, a temporary address, or moving into or out of a subfolder), update the URLs with a tool that handles serialized data, such as WP-CLI:
wp search-replace 'https://old-example.com' 'https://new-example.com' --skip-columns=guid --dry-run
Check the dry run, then run it without --dry-run. Never run a plain text replace on the SQL file. We explain why, and what else to change on a copy at a new address, in our guide to cloning a WordPress site.
Step 9: Get permalinks working
Pretty permalinks rely on rewrite rules from the web server.
- On Apache, they live in the
.htaccessfile you copied. If posts return 404 errors, confirm.htaccessexists and that the server allows overrides. Re-saving Settings > Permalinks in wp-admin regenerates the standard rules. - On Nginx,
.htaccessis ignored. The server block must send requests for non-existent files toindex.php, using the standard WordPress rule. Any redirects or rules that lived in.htaccesson the old server need rewriting as Nginx config.
Step 10: Test before you switch DNS
Load the site from the new server before changing DNS, ideally by pointing your own computer’s hosts file at the new IP. Log in, click through posts, pages and archives, submit forms, test checkout in test mode and check that images load. When it all works, switch DNS and set up SSL on the new server.
Troubleshooting common errors
“Error establishing a database connection”
The database name, user, password or host in wp-config.php is wrong, the user lacks privileges, or the database server is not running. Test the credentials by logging into MySQL with them directly.
A blank page or “critical error”
Usually a PHP problem: a missing PHP extension, an incompatible PHP version or a plugin that does not like the new server. Turn on debug logging in wp-config.php with WP_DEBUG and WP_DEBUG_LOG set to true, reload the page and read wp-content/debug.log. Turn debugging off again when you are done.
The home page works but every other page is 404
Rewrite rules are missing. See Step 9.
Missing images
The uploads folder did not copy completely, or permissions block the web server from reading it. Compare file counts between old and new, and check ownership.
Redirect loop after enabling SSL
Often caused by a proxy or CDN in front of the site sending traffic to the server over HTTP while WordPress insists on HTTPS. Check the SSL mode in the proxy and how the server detects HTTPS.
Mixed content warnings
Some content still loads over HTTP. Run a serialized-safe search and replace from the HTTP to the HTTPS version of your domain, and check theme settings and hard-coded URLs.
Clean up afterward
Delete the archive and SQL dump from both servers, since they contain your whole site and customer data. Set up backups on the new server, ideally off-site. Keep the old server running until DNS has fully moved, then take a final backup and cancel it.
If you would rather not spend an evening in a terminal, our WordPress migration specialists can move the site onto a tuned Nginx server with page caching, Redis object cache, SSL and off-site backups. Write to support@growthscribe.com.
FAQ
Is it safe to migrate a WordPress site manually?
Yes, if you take a full backup first and keep the old site running until the new one is tested. A manual migration gives you complete control over what moves, and avoids the timeouts and upload limits that can break plugin migrations on large sites.
Which files do I need to move in a manual WordPress migration?
At minimum the wp-content folder (themes, plugins and uploads) and wp-config.php, plus hidden files like .htaccess. Copying the entire WordPress directory is simplest, because it keeps core files and versions identical. The database must be exported and imported separately.
Do I have to change anything in the database?
Not if the domain stays the same. If the address changes, use WP-CLI search-replace or a plugin that handles serialized data to update the URLs, and leave the GUID column alone. Never run a plain text find and replace on the SQL file.
Why does my site show the WordPress install screen after migrating?
WordPress cannot find its tables. The usual cause is a table prefix in wp-config.php that does not match the imported tables, or wp-config.php pointing at the wrong, empty database. Check both settings against the database you imported.


