Why migrate to a VPS?
Shared hosting is a good fit for low-traffic sites and projects that do not need control over the server environment. Some needs go beyond that scope: a specific PHP version that is not available, root access required for a system dependency, performance affected by neighbors on the shared node, or the need to host several projects with isolated configurations. Migrating to a VPS answers those specific cases, without making shared hosting unsuitable for the uses it covers.
Step 1: Inventory before migrating
Complete the full inventory before touching anything. A failed migration almost always comes from an item forgotten at this stage.
Files and directories
- Main web directory (often
public_html/,www/orhtdocs/) - Configuration files outside the web root (
.envfiles, application configs) - Files uploaded by users (often in an
uploads/orstorage/subfolder) - Cron jobs configured on the shared hosting (list them via your current host's control panel)
Database
- Database name, username, password (available in the panel or in the application's config file)
- MySQL/MariaDB version used on the shared hosting (check compatibility with the target version)
- Mailboxes hosted with the shared hosting provider do not migrate automatically to the VPS
- List the active email addresses and decide: migrate them to the VPS (Postfix/Dovecot), move them to a third-party service, or leave them with the current provider with the MX records unchanged
DNS and TTL
Before any migration, lower the TTL of the domain's A record to 300 seconds (5 minutes). This reduces the propagation delay during the final switch.
Locate the A record in the domain's DNS zone (usually in the registrar's or current host's panel) and change the TTL value:
; Before migration: usual TTL (often 3600 or 86400)
example.com. 3600 IN A 1.2.3.4
; Change to 300 at least 24h before the switch
example.com. 300 IN A 1.2.3.4 Wait at least the duration of the old TTL after the change before proceeding with the switch, so propagation is effective.
Step 2: Prepare the VPS
Install and secure the VPS before transferring any data. Do not migrate to an unconfigured server.
- Basic hardening (SSH key, UFW, updates): VPS security checklist
- SSH key access: SSH key guide
- Web stack installation (Nginx + PHP-FPM + MariaDB): Nginx + PHP-FPM guide
- Create the destination directory:
sudo mkdir -p /var/www/example.com - Check that the PHP version installed on the VPS is compatible with the application
Step 3: Transfer the files
With rsync (SSH access available on the shared hosting)
rsync is the recommended method: it only transfers modified files, supports resuming after an interruption and preserves permissions. Source: man rsync
From the VPS (pulling the files from the shared hosting):
rsync -avz --progress \
[email protected]:/home/user/public_html/ \
/var/www/example.com/ Or from a local machine (if you have SSH access to both servers):
rsync -avz --progress \
[email protected]:/home/user/public_html/ \
[email protected]:/var/www/example.com/ Options used:
-a: archive mode (preserves permissions, timestamps, symbolic links)-v: verbose (shows transferred files)-z: compression during transfer--progress: shows progress
Without SSH access on the shared hosting (SFTP or manual download)
If the shared hosting does not offer SSH access, use an SFTP client (FileZilla, Cyberduck) to download the files locally, then send them to the VPS:
# From the local machine to the VPS
rsync -avz --progress /local/path/public_html/ \
user@vps-ip:/var/www/example.com/ Adjust permissions
sudo chown -R www-data:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \; Step 4: Export and import the database
Export from the shared hosting
If the shared hosting offers SSH access:
mysqldump -u db_user -p db_name > export-$(date +%F).sql Without SSH access, use phpMyAdmin (available on most shared hosting plans): Export > SQL format > Download.
Source: dev.mysql.com: mysqldump
Transfer the export file to the VPS
scp export-$(date +%F).sql user@vps-ip:/home/user/ Create the database on the VPS
sudo mariadb -u root CREATE DATABASE db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'db_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON db_name.* TO 'db_user'@'localhost';
FLUSH PRIVILEGES;
EXIT; Import the data
mariadb -u db_user -p db_name < /home/user/export-$(date +%F).sql Step 5: Reconfigure the application on the VPS
Application configuration file
Update the database connection settings in the application's configuration file (.env, wp-config.php, config.php depending on the framework):
- Database host:
localhost(the database is on the same server) - Database name, user and password: the ones created in the previous step
Absolute paths
Check and update all absolute paths hardcoded in the configuration. On shared hosting, paths often start with /home/user/public_html/; on the VPS, they start with /var/www/example.com/.
URLs stored in the database (if applicable)
Some applications (notably WordPress) store the site URL in the database. If the URL changes during the migration, update those values. For WordPress, use WP-CLI:
sudo -u www-data wp search-replace 'http://old-domain.com' 'https://example.com' \
--path=/var/www/example.com For other applications, check the relevant framework's documentation.
Nginx vhost
Create the Nginx vhost for the site. For a generic PHP site:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
} sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx For WordPress: WordPress on VPS guide (specific vhost with permalink handling).
Step 6: Test the site before the DNS switch
Never switch the DNS without having verified that the site works on the VPS. Edit the local hosts file to force resolution to the new IP, without touching the public DNS.
Edit the local hosts file
On Linux or macOS:
sudo nano /etc/hosts On Windows: C:\Windows\System32\drivers\etc\hosts (open as administrator)
Add the line:
vps-ip example.com www.example.com The browser now resolves example.com to the VPS, without affecting other users.
Points to check
- Home page and internal pages display correctly
- Contact forms work (email sending)
- Login to the admin area
- Uploaded files are accessible (images, documents)
- Redirects are active (301, 302)
- No errors in the Nginx logs:
sudo tail -f /var/log/nginx/error.log
Remove the hosts entry after testing
# Remove the line added to /etc/hosts
sudo nano /etc/hosts Step 7: Issue the SSL certificate
Issuing the Let's Encrypt certificate before the DNS switch is not possible (Certbot must resolve the domain to the VPS). Issue the certificate right after the switch, while the low TTL is still active.
sudo certbot --nginx -d example.com -d www.example.com Complete guide: OuiHeberg Certbot guide.
Step 8: Switch the DNS
Once the tests are validated, change the domain's A record to point to the VPS IP:
; Before
example.com. 300 IN A 1.2.3.4 (old shared hosting IP)
; After
example.com. 300 IN A 5.6.7.8 (new VPS IP) With the TTL at 300 seconds, propagation is effective within 5 to 10 minutes for most DNS resolvers. Watch the Nginx logs during this period to confirm that traffic is reaching the VPS:
sudo tail -f /var/log/nginx/access.log Once propagation is confirmed, raise the TTL back to a standard value (3600 or 86400):
example.com. 3600 IN A 5.6.7.8 Step 9: Overlap period
Keep the shared hosting active for 1 to 2 weeks after the switch. This period allows you to:
- Recover files forgotten during the initial inventory
- Check that emails hosted on the shared hosting keep arriving (if the MX records have not changed)
- Have a quick rollback point in case of a critical problem
Do not delete the data on the shared hosting before confirming that everything works correctly on the VPS.
Common pitfalls
Emails stay with the old provider
If the domain had mailboxes on the shared hosting, the MX records still point to the old provider after switching the A record. That is often intentional (keeping email with the current provider during the transition), but check that it is deliberate. Decide explicitly on the email strategy before the DNS switch.
Cron jobs no longer run
Scheduled tasks configured in the shared hosting panel do not migrate automatically. Recreate them in the VPS crontab:
crontab -e List the old host's cron jobs from its control panel before the migration (inventory step).
The SSL certificate is not issued immediately
Certbot cannot issue a certificate for a domain that does not yet point to the VPS. Issue the certificate right after the DNS switch, while the low TTL is still active. Between the switch and the certificate issuance, the site is accessible over HTTP only.
Absolute paths stored in the database
Some applications store absolute paths in the database (path to uploaded files, image URLs). These paths must be updated after the migration. Use the application's search/replace feature or an SQL script:
UPDATE wp_options SET option_value = REPLACE(option_value,
'/home/old/public_html',
'/var/www/example.com')
WHERE option_value LIKE '%/home/old/public_html%'; Frequently asked questions
How long does a migration take?
For a standard site (a few hundred MB, a reasonably sized database), the technical migration takes 1 to 3 hours. The preparation (inventory, lowering the TTL, configuring the VPS) represents most of the time. The DNS switch itself takes less than 10 minutes with a TTL of 300 seconds.
Can you migrate without downtime?
Yes, if the procedure is followed in order: the site stays live on the shared hosting throughout the preparation and testing. The only potential interruption is the window between the DNS switch and the SSL certificate issuance (a few minutes). With a TTL of 300 seconds, that window is short.
Should you notify your visitors?
For a low-traffic site, no. For an e-commerce site or an application with logged-in users (active sessions), schedule the migration outside peak hours and display a maintenance page during the switch.
What if the site does not work after the switch?
Set the A record back to the old shared hosting IP (the 300-second TTL allows a quick rollback). Diagnose the problem on the VPS without pressure, then run the switch again once the problem is solved. This is exactly why the shared hosting must stay active during the overlap period.
Not decided yet? Our article on VPS versus shared hosting covers the cases where migrating is worth it.
Migrating several sites to the same VPS? Set up the directory layout and isolation up front: multi-site setup with Nginx.
