Shared hosting works until it does not. The signs are usually gradual: your WordPress admin takes a few extra seconds to load, TTFB climbs above one second, you hit resource limits during traffic spikes, or you want to install Redis for object caching and find the host does not support it. At some point the cost of staying on shared hosting (in performance, in limitations, in time spent on workarounds) exceeds the cost of managing your own server.
Moving a live WordPress site to a VPS involves three distinct phases: setting up the new server and installing the web stack, copying your files and database, and cutting over the DNS without losing content or traffic. The process is straightforward, but the order of operations matters. This guide walks through it end to end, including the checks that confirm everything is working before you cancel your old hosting.
This guide assumes you have a VPS provisioned and have completed the initial Ubuntu setup guide, including creating a non-root user, configuring SSH key authentication, and setting up UFW. It also assumes you have installed Nginx, PHP-FPM, and MariaDB on the VPS by following the WordPress on Nginx guide. You will need SFTP or rsync access to your old host, which most shared hosts provide by default.
Before touching the new server, export a complete copy of your WordPress database from the old host. This is the definitive version of your content, and you want the export to be as recent as possible relative to the cutover.
Most shared hosts provide phpMyAdmin inside cPanel. Navigate to phpMyAdmin, select your WordPress database from the left panel, click Export, and confirm the format is SQL before downloading. If you have WP-CLI or SSH access on the old host, mysqldump is more reliable for large databases:
mysqldump -u DB_USER -p DB_NAME > wordpress_backup.sql
Replace DB_USER and DB_NAME with the credentials from your wp-config.php. Store this file locally; you will upload it to the new server shortly.
The WordPress files that need to move are the entire wp-content/ directory (your themes, plugins, and uploads) and your wp-config.php. WordPress core files can be downloaded fresh and do not strictly need copying, but copying everything is simpler and less error-prone during a migration.
On most shared hosts, your WordPress installation lives in public_html/ or www/. Connect via SFTP and download the full directory to your local machine. If you have SSH access to the old host, rsync is faster:
rsync -avz --progress [email protected]:~/public_html/ ./wordpress-backup/
Once you have the files locally, upload them to the new server into the Nginx web root you configured when setting up WordPress:
rsync -avz --progress ./wordpress-backup/ [email protected]:/var/www/yoursite.com/public/
Replace deploy with your VPS username and yoursite.com with your domain. The upload time depends on the size of your wp-content/uploads/ directory; a site with a large image library may take several minutes over a residential connection. Once the upload completes, all the WordPress files are in place on the new server; the next step creates the database they will connect to.
Log into the new server and create a database and user for WordPress. Connect to MariaDB:
sudo mysql -u root -p
Then run:
CREATE DATABASE wordpress_db;
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Replace wordpress_db, wp_user, and the password with your chosen values. Keep these credentials to hand; you will need them in the next two steps.
Upload the SQL file exported in Step 1 to the new server, then import it into the database you created:
mysql -u wp_user -p wordpress_db < wordpress_backup.sql
You will be prompted for the database user password. The import may take a moment for large databases. Once complete, verify the tables imported correctly:
mysql -u wp_user -p wordpress_db -e "SHOW TABLES;"
Output:
+----------------------------+
| Tables_in_wordpress_db |
+----------------------------+
| wp_commentmeta |
| wp_comments |
| wp_links |
| wp_options |
| wp_postmeta |
| wp_posts |
| wp_term_relationships |
| wp_term_taxonomy |
| wp_termmeta |
| wp_terms |
| wp_usermeta |
| wp_users |
+----------------------------+
If the list is empty, the import file may have been empty or the wrong database was targeted. Re-check the filename and database name and run the import again. Once the tables are confirmed, the database is in place and the next step connects WordPress to it.
The copied wp-config.php still points to the old host's database credentials. Open it on the new server and update it to reflect the new database name, user, and password:
nano /var/www/yoursite.com/public/wp-config.php
Update these three lines:
define( 'DB_NAME', 'wordpress_db' );
define( 'DB_USER', 'wp_user' );
define( 'DB_PASSWORD', 'your_password' );
Save and close the file. The DB_HOST value should remain localhost unless your database is running on a separate server. With the credentials updated, WordPress can connect to the new database; the next step corrects the site URL stored inside it.
WordPress stores the site URL in the database, and the copied database still contains the old host's URL. Before the site will work correctly on the new server, these values need to match the domain you will be using.
WP-CLI is the safest way to handle this. Run a dry run first to confirm what would be changed. Replace https://old-host-url.com with the URL from your old host and https://yoursite.com with the domain you will use on the new server:
wp search-replace 'https://old-host-url.com' 'https://yoursite.com' --dry-run --all-tables
If the output looks correct, remove --dry-run to apply the change. If you are previewing the site with a temporary hostname before DNS cutover, use that temporary URL as the replacement target and run the search-replace again with the real domain once DNS has been updated.
If WP-CLI is not installed on the new server, install it:
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
With the site URL corrected in the database, WordPress will generate the right links across all pages and posts.
WordPress needs the correct file permissions to write uploads, cache files, and update plugins. Set the web root to be owned by the web server user:
sudo chown -R www-data:www-data /var/www/yoursite.com/public
sudo find /var/www/yoursite.com/public -type d -exec chmod 755 {} \;
sudo find /var/www/yoursite.com/public -type f -exec chmod 644 {} \;
Incorrect permissions are one of the most common sources of post-migration problems. If WordPress cannot write to wp-content/uploads/ after the migration, the ownership and mode settings above are where to look first. With permissions correct, the site is ready to test on the new server before DNS is updated.
Before updating DNS, verify the site loads correctly on the new server by editing your local machine's hosts file. This lets you access the new server via the correct domain name without changing the live DNS records that other visitors use.
On macOS or Linux, add a line to /etc/hosts:
YOUR_VPS_IP yoursite.com www.yoursite.com
On Windows, the hosts file is at C:\Windows\System32\drivers\etc\hosts. Add the same line. With this in place, your browser resolves yoursite.com to the new VPS IP. Confirm the site loads, check the admin dashboard is accessible at /wp-admin, and verify that images and uploaded files appear correctly. Remove the hosts file entry once you have confirmed everything is working. Once the site loads correctly and the admin is accessible, the new server is ready to receive live traffic.
With the site verified on the new server, update the A record for your domain to point to your VPS IP address. Log into your DNS provider (usually the registrar where your domain is registered, or a separate DNS host like Cloudflare) and update the A record. If you lowered the TTL before the migration, DNS propagation will be faster; at the default TTL of 3600 seconds, it can take up to an hour for changes to reach all resolvers.
After DNS has propagated and the live domain resolves to the new server, set up SSL. Follow the Let's Encrypt Nginx guide to obtain and install a free certificate.
Do not cancel the old hosting immediately after DNS cutover. Leave the old account active for at least 48 hours while DNS propagation completes across all nameservers. During this window, some visitors may still be hitting the old server and you want their requests to succeed. After 48 hours, confirm with your browser and at least one external DNS lookup tool that the domain consistently resolves to the new VPS IP before cancelling.
Before cancelling, ensure your SQL export and file backup are stored somewhere safe: locally, in cloud storage, or on the new server. Once the old account is cancelled, that copy of your data is gone.
MySQL connection errors after migration: The most common cause is mismatched credentials in wp-config.php. Confirm the database name, username, and password match exactly what you created in Step 3. The DB_HOST should be localhost unless you are using a remote database server.
File permissions errors: WordPress displays a warning if it cannot write to wp-content/. Re-run the chown and chmod commands from Step 7 if you see this after migration.
Emails not sending: The old host's mail configuration does not transfer with the files. Install a transactional email plugin (WP Mail SMTP is the standard choice) and configure it to send through an SMTP provider such as Mailgun, Postmark, or SendGrid.
Images not loading: Broken images after migration are usually caused by incorrect file permissions on wp-content/uploads/ or a mismatched siteurl in the database. Check both.
Not if the migration is done correctly. What Google cares about is the domain and the content it serves, not the server that hosts them. If the URLs, content, and canonical tags remain the same after migration, there is no ranking signal change. The risks are short-term: a DNS propagation window where some visitors briefly hit the old server, or a misconfigured WordPress URL that generates incorrect redirects. Following the steps in this guide, including verifying the site before DNS cutover and running wp search-replace to update database URLs, eliminates the most common causes of post-migration ranking drops.
DNS changes typically propagate within a few hours, but the full propagation window can take up to 48 hours at the default TTL of 3600 seconds. The actual experience for most users is faster: major resolvers like Google (8.8.8.8) and Cloudflare (1.1.1.1) pick up changes within minutes to an hour. If you manage your DNS through Cloudflare, you can lower the TTL to 60 seconds before the migration to speed up propagation significantly. The safest approach regardless is to leave the old hosting active for 48 hours after cutover, so any visitor still hitting the old server gets a working site.
Only if the domain changes. If you are moving the same domain to a new server, the WordPress URLs stored in the database are already correct and no search-replace is needed. The wp search-replace step in this guide is relevant when you are using a temporary hostname to preview the site before DNS cutover: you set the database URLs to the temporary hostname for testing, then switch them back to the real domain once DNS has propagated. If you skip the preview step and go straight from old host to new host on the same domain, no URL changes are required beyond what is already in wp-config.php.
Effectively yes, though there is a brief window during DNS propagation where some visitors may still be hitting the old server. The approach in this guide minimises this: you set up and fully verify the site on the new server before touching DNS, so when you do update the A record, the new server is already operational. During propagation, some visitors see the old server and some see the new one; both are serving the same content, so neither experience is broken. The only scenario that does not survive seamlessly is a form submission or order placed on the old server during the propagation window, since it will not appear in the new server's database. For a blog or brochure site this is not a concern; for a WooCommerce store, scheduling the cutover outside peak hours reduces the exposure.
You have moved a live WordPress installation from shared hosting to a self-managed VPS, including the database, files, and DNS cutover. From here, the next steps are setting up SSL with Let's Encrypt and Nginx, configuring object caching with Redis, and setting up automated backups to an off-server destination.
Step-by-step: configure a fresh Ubuntu 24.04 VPS from first SSH login to a secur...
How to configure UFW and Fail2Ban on Ubuntu to protect your VPS from port scans...
How to set up SSH key authentication on Ubuntu and disable password logins to se...