How to move a website from shared hosting to a VPS
The move itself takes a couple of hours; the mistakes usually happen around it: a forgotten cron job, .htaccess rules that nginx does not read, an AAAA record with the old address, or mail that disappeared along with the IP change. Below is the order of steps with commands and checks for a PHP and MySQL site.
This article is about how to move. Whether to move at all and which server to get is covered in When to move from shared hosting to a VPS. Here we assume the VPS is already bought and the site runs on WordPress, Bitrix, Laravel or another PHP engine with a MySQL database. For other stacks the order is the same, only the commands change.
Timeline: a move in four stages
- Two days before: write down everything that runs on the hosting and lower the TTL of the DNS records.
- A day before: prepare the server, copy the files and the database, test the site at the new address.
- Moving day: close the site to writes, transfer the fresh data, change DNS.
- One to two weeks after: keep the hosting running as a fallback.
What to write down on the hosting
Almost all problems after a move are things that worked on the hosting by themselves and were forgotten. Go through the hosting control panel and write down:
- The PHP version and extensions. You can see them in the panel or over SSH with
php -vandphp -m. Install the same version on the VPS, and upgrade PHP separately, later. - The database version and size, and the table encoding.
- Cron jobs: what runs and how often. File paths on the VPS will be different.
- Rules in .htaccess: redirects, friendly URLs, folder access restrictions.
- Mail: mailboxes, forwards, where the MX records point.
- Subdomains and all DNS records, including TXT records for service verification.
- External services that know the site's IP. Payment systems, 1C data exchange, partner APIs and mail services often accept requests only from allowed addresses. The new IP has to be added there in advance.
A day or two before the move, lower the TTL of the domain's A and AAAA records to 300 seconds. Then on switch-over day the new address will propagate in minutes rather than a day.
Prepare the server
Start with basic protection: updates, key login, a firewall. All of this is collected in First 10 steps after buying a VPS: security. Then there are two paths.
With a control panel. ISPmanager, FASTPANEL or aaPanel install the web server, the right PHP version and the database in a few clicks and give you an interface familiar from shared hosting. Plans with them: VPS with ISPmanager, VPS with FASTPANEL, VPS with aaPanel.
Without a panel. On Ubuntu and Debian the minimal set is installed with one command; packages without a version number pull in the PHP that ships with the system:
apt install nginx mariadb-server php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl
If the hosting had an older or newer PHP version than the system's, install it from third-party repositories with PHP packages for Ubuntu and Debian.
Keep in mind the main difference from shared hosting: nginx does not read .htaccess. Redirects and friendly URLs have to be moved into the site configuration. For WordPress the main rule looks like this: try_files $uri $uri/ /index.php?$args;. If there are many rules, it is easier to put Apache behind nginx: that is how most panels work, and .htaccess keeps working.
Move the files
If the hosting has SSH access, the fastest way is to pull the files directly from the VPS with rsync:
rsync -avz user@old-host.example:~/public_html/ /var/www/example.com/
Running the same command again transfers only what has changed, which will come in handy on moving day. If there is no SSH, there are two options: build an archive in the panel's file manager and download it to the server with wget, or fetch the files over FTP with the lftp utility:
lftp -u user old-host.example -e "mirror --parallel=4 public_html /var/www/example.com; quit"
Plain FTP sends the password in clear text, so if the hosting supports SFTP or FTPS, use them. After copying, give the files to the web server user and set the usual permissions:
chown -R www-data:www-data /var/www/example.com
find /var/www/example.com -type d -exec chmod 755 {} +
find /var/www/example.com -type f -exec chmod 644 {} +
Move the database
Make a dump on the hosting. Over SSH:
mysqldump --single-transaction --default-character-set=utf8mb4 -u user -p dbname | gzip > db.sql.gz
Without SSH the dump is exported through phpMyAdmin, but it may not cope with large databases because of the hosting's time limits; in that case ask support for a dump or make it in parts. On the VPS, create the database and the user and load the dump:
mysql -e "CREATE DATABASE site CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'site'@'localhost' IDENTIFIED BY 'strong_password';
GRANT ALL PRIVILEGES ON site.* TO 'site'@'localhost';"
zcat db.sql.gz | mysql site
A common error during import is Unknown collation: 'utf8mb4_0900_ai_ci'. It means the dump was made in MySQL 8, while the VPS runs MariaDB, which does not know this collation. Either install the same DBMS on the VPS as on the hosting, or replace the collation in the dump with utf8mb4_unicode_ci.
Then put the new database credentials into the site settings: for WordPress that is wp-config.php, for Bitrix /bitrix/.settings.php and /bitrix/php_interface/dbconn.php, for Laravel the .env file. The database server is now localhost.
Move the cron jobs
Run jobs as the site user, not as root: crontab -u www-data -e. Check the paths: on the hosting the site lived in something like /home/user/public_html, on the VPS in /var/www/example.com, and the path to PHP may differ too (which php). Separately check the tasks the host ran for you: Bitrix agents on cron, 1C data exchange, sending emails from the queue.
Test the site before changing DNS
Visitors are still going to the hosting, while you can open the new server on your own machine. Map the domain to the server's IP in your computer's hosts file (/etc/hosts on Linux and macOS, C:\Windows\System32\drivers\etc\hosts on Windows):
203.0.113.10 example.com www.example.com
Go through the home page, catalog, product page, search, forms, cart and admin login, and upload an image. Watch the error log at the same time: tail -f /var/log/nginx/error.log. To quickly check the server's response without a browser: curl -I --resolve example.com:80:203.0.113.10 http://example.com/.
Typical problems at this step:
- 502 Bad Gateway: nginx cannot reach PHP-FPM, because the socket path in the site configuration does not match the one in the PHP pool settings.
- A blank page or error 500: a PHP extension is missing. Its exact name will be in the error log.
- Large files do not upload: by default nginx limits the request body to 1 MB (
client_max_body_size), and PHP limits it withupload_max_filesizeandpost_max_size. - Links lead to 404: the .htaccess rules were not moved.
TLS certificate
The usual domain validation for a free certificate goes over HTTP to the address DNS currently points to, that is, to the hosting. So before the switch there are three options:
- Get a certificate via DNS validation:
certbot certonly --manual --preferred-challenges dns -d example.com -d www.example.com. certbot will ask you to add a TXT record. Such a certificate does not renew itself, so after the move issue a regular one. - Temporarily move the current certificate and key from the hosting, if the panel lets you export them.
- Change DNS and immediately issue a certificate with
certbot --nginx -d example.com -d www.example.com. For a few minutes the site will show an error over HTTPS; for a small site this is acceptable.
After issuing, check automatic renewal: certbot renew --dry-run.
Moving day
- Close the site to writes: turn on maintenance mode or at least stop taking orders and comments. For a shop this is a few minutes.
- Run the same rsync command once more: it will transfer only the changes.
- Make a fresh database dump and load it on the VPS over the old one.
- Change the A record of the domain and www to the server's IP. If the domain has an AAAA record with the hosting's IPv6 address, replace it with the server's IPv6 or delete it, otherwise some visitors will keep landing on the old server.
- Check that the new records are visible:
dig +short example.com A @8.8.8.8anddig +short example.com AAAA @8.8.8.8. - Open the site for writes, now on the VPS.
Do not lose your mail
This is the most common trouble with a move. Check where MX points: dig +short example.com MX. If it is example.com or mail.example.com that resolves to the same IP as the site, changing the A record will send mail to the VPS, where there is no mail server. Before the switch, create a separate record for mail with the hosting's old IP and put it in MX, or better, move the domain's mail to a separate mail service in advance.
If the site sends emails (orders, password resets), check them after the move. Emails from a new IP without an SPF record will land in spam: either add the server's IP to SPF, or send emails through a mail service's SMTP. Your own mail server on a VPS is a separate task; plans for it are in VPS for a mail server.
After the move, and rollback
Do not shut down the hosting for one or two weeks. In the first hours, check the old database: if someone placed an order on the old server between the final dump and the DNS change, move it over by hand. After a couple of days, look at the hosting's access log: if people are still coming there, the old address is left somewhere, most often in the AAAA or www record.
Rollback is simple while the hosting is running: put the old IP back in DNS, and thanks to the short TTL visitors will return to the hosting within minutes. When everything is stable, set the TTL back to normal values, set up backups following VPS backups: strategies and tools, and add external monitoring. Plans for websites: VPS for a website, VPS for WordPress, VPS for an online store, VPS for Bitrix. For a first trial build, hourly billing is convenient.