VPS lock-in: how portable is your server?
How interchangeable are VPS providers? Audit your current one on images, snapshot export, IPv6 and billing terms, then cut a move down to one evening.
How interchangeable are VPS providers?
VPS lock-in is real, but it sits somewhere most people never check. Two providers running KVM (kernel-based virtual machine) give you the same Ubuntu and the same systemd underneath, so the operating system is not what holds you in place. What holds you is the layer around the box: an image you cannot export, an IP address you cannot take with you, a control panel that also owns your DNS (domain name system) records, and a prepaid balance that does not refund.
Your provider decides who administers the server. It does not decide whether you can leave. The distance between those two facts is measured in hours of work, and you can shorten it long before you need to.
What follows is an audit. Run it against the provider you use now, at a calm moment. Each step is one question, with a command or a panel check behind it, and a note on what a bad answer costs you later.
What VPS lock-in actually looks like
Lock-in is not a clause in a contract. It is the total cost of moving: the hours spent rebuilding what nobody wrote down, the bandwidth to copy the data out, the downtime while resolvers still send visitors to the old address, and the money already paid for months you will not use.
Measured that way, a VPS is one of the least locked-in things you can rent. You have root on a general purpose kernel. A managed database or a serverless platform ties you to an API you cannot reimplement in an evening, while a VPS ties you to almost nothing at the operating system layer. That is why the few remaining ties are worth naming. Each one is cheap to fix in advance and expensive to discover during an outage.
Audit 1: which virtualisation are you on?
systemd-detect-virt
lscpu | grep -i "hypervisor vendor"
uname -m
ls /sys/firmware/efi >/dev/null 2>&1 && echo UEFI || echo BIOSsystemd-detect-virt prints one word. kvm or xen means full virtualisation: you boot your own kernel, and your disk is an image file that the host treats as a block device. lxc means a container that shares the host kernel, so you cannot load a kernel module of your own, and nested virtualisation is not available. Both attempts fail with Operation not supported, which is a planning problem rather than a command problem. How KVM, Xen and LXC differ on a VPS covers what each model can and cannot do.
The last two lines matter when you carry a disk between hosts. An image installed for BIOS boot keeps its bootloader in the disk's boot sector, while a UEFI-only host looks for an EFI system partition holding a .efi loader, finds nothing, and stops at No bootable device on the console. Architecture is the harder wall. An arm64 root filesystem will not boot on an x86_64 host at all, because every binary inside it is the wrong machine code. If you are weighing that jump, what really differs between ARM and x86 VPS plans covers the parts that are not the price.
Audit 2: can you bring an image in, and take one out?
Providers sit at different points on one line. Find yours in the panel and write it down.
- A fixed menu of provider images, and nothing else.
- ISO attach or upload, so you install from the console yourself.
- Disk image import, from an upload or from a URL you host.
- Disk image export, which is the only one of the four that decides whether you can leave with a disk.
The first three describe how you arrive. Only the fourth describes how you leave, and it is the one most panels omit. Import brings customers in and export lets them out, so the asymmetry is common. Ask support directly if the panel is silent, and record the answer with the date you asked.
Where export exists, convert the result to a format the next host accepts:
qemu-img info disk.qcow2
qemu-img convert -p -O raw disk.qcow2 disk.rawRaw is the most widely accepted input and the least convenient to move, because the file is the size of the whole disk. An 80 GB volume with 6 GB used is still an 80 GB raw file. Converting the other way, to qcow2, shrinks it back to roughly the used space, because qemu-img does not write the zero blocks. Even so, treat whole-disk transfer as the slow path. Rebuilding from configuration is usually faster than copying 80 GB across the internet, and it leaves you with a server you can rebuild again next time.
Audit 3: can a snapshot leave the provider?
Open the snapshot page and read the verbs next to a snapshot. If the only action is Restore, then the snapshot is a rollback feature. It lives on the provider's storage, it is deleted with your account, and no tool of yours can read it. What a snapshot does that a backup does not goes through that difference in full.
A second limit has nothing to do with lock-in. A snapshot of a running server is crash consistent, not application consistent: the disk is captured in the middle of writes, so PostgreSQL or MySQL replays its journal on restore and the last transactions may be gone. Dump databases with their own tools, then let the file-level backup collect the dump.
A portable backup is one you hold yourself. restic writes an encrypted, deduplicated repository to storage you choose:
sudo apt update && sudo apt install -y restic
sudo install -d -m 700 /etc/restic
sudo sh -c 'openssl rand -base64 32 > /etc/restic/password'
sudo chmod 600 /etc/restic/password
export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/srv/restic
export RESTIC_PASSWORD_FILE=/etc/restic/password
sudo -E restic init
sudo -E restic backup /etc /srv /home
sudo -E restic snapshotsKeep a copy of that passphrase somewhere other than the server, because a repository whose password is lost is unreadable and no support ticket recovers it. Then prove the backup, because a backup nobody has restored is only a guess:
sudo -E restic restore latest --target /tmp/restore-check
sudo -E restic checkrestic snapshots should list the run you just made, and /tmp/restore-check should hold real files you can open. Do that restore once a month and read one file with your own eyes.
Audit 4: the addresses you cannot take with you
IP addresses do not move between providers. This is the one piece of true lock-in on the list, and the answer is not to fight it. Put a name in front of every address, and keep that name at a company you do not buy servers from.
ip -6 addr show scope global
ip -6 route show default
ping -6 -c 3 example.com
dig -x 203.0.113.10 +shortTwo IPv6 questions decide how much rework a move needs. First, do you get a single address or a routed /64? One address is enough for a web server, and not enough to give every container its own. Second, does the address actually route? An address present in ip -6 addr with empty output from ip -6 route show default means the image configured the address and no gateway, so nothing outside the local segment answers and ping -6 times out while IPv4 works normally.
dig -x asks for the PTR record, which is reverse DNS: the name an address maps back to. Empty output means there is none. For a mail server that is a hard blocker, because many receivers reject or delay mail from an address whose reverse name does not match the sending host. Check whether the panel lets you set the PTR yourself or whether it needs a support ticket, since that single field is the difference between a two hour move and a two day one. Whether self-hosted mail is still worth the trouble covers the rest of that list.
Software licensed against an IP address moves badly for the same reason. A cPanel license is issued to the server's main IP, so a move needs a license transfer as well as a data copy, and the control panel alternatives worth knowing are easier to weigh before you are committed to one.
Audit 5: how much of the panel is in the API?
Go through the panel and ask, for each operation, whether the API can do it too: create, rebuild, resize, snapshot, firewall rules, reverse DNS, DNS records, and adding a second login. Anything that is panel-only becomes a person clicking a button inside your recovery plan, at the hour when clicking is hardest.
The account itself is a single point of failure worth testing on paper. If a payment fails or a login looks suspicious and the account is suspended, the server may still be running while you cannot manage it. Keep your credentials and one recent backup somewhere that does not depend on that login, and turn on two-factor authentication so a suspension is less likely to be yours to cause.
DNS hosting deserves its own line. If your zone lives inside the same panel as the server, then leaving that provider means moving DNS in the same week you move the machine, which is two risky changes at once. Host the zone at your registrar or at a dedicated DNS provider, and keep the records in a text file in git. A provider move is then a few A and AAAA record edits that you can revert in seconds.
Audit 6: the term, the balance and the renewal date
Contract detail is where a technically portable server turns into an expensive one to leave. Read your own invoice and write down four things: the minimum term, the notice period for cancellation, whether an unused prepaid balance is refundable, and the outbound transfer allowance.
The last one surprises people. Copying 300 GB of data out is 300 GB of egress, and on a metered plan the exit costs money in the same month you are already paying two providers. Check the allowance before you plan the move, not while rsync is running. What a VPS actually costs per month and how to read a cheap VPS offer both cover the parts of a plan that only show up later.
If you buy in yen from a provider in Japan, the first two items deserve the most attention. Minimum terms measured in months are normal, prepaid balances are normal, and staying with one provider for years is normal too, which means nobody has re-read the terms since the account was opened. Console conventions differ as well: the same operation sits under a different menu at each provider, so whoever does the move is learning a new panel while the clock runs. All of this reads better in a quiet week before renewal. Assume snapshots and backups are deleted when the account closes, and download anything you want to keep before the last day.
The portability recipe: two files and one backup
The audit tells you where you stand. This is what makes the answers stop mattering.
Configuration in a cloud-init file. cloud-init is the tool that configures a cloud instance on first boot. Almost every provider image includes it, and almost every panel has a user data field to paste into.
#cloud-config
hostname: web01
users:
- name: deploy
groups: [sudo]
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA deploy@workstation
package_update: true
packages:
- docker.io
- docker-compose-v2
- restic
runcmd:
- [systemctl, enable, --now, docker]Validate the file before it goes near a panel, then check the result on the running box:
cloud-init schema --config-file user-data.yaml
cloud-init status --long
sudo cloud-init schema --systemcloud-init status --long should report status: done. If it reports error, read /var/log/cloud-init.log from the bottom. The most common failure is a missing #cloud-config on the very first line, because without it cloud-init does not recognise the text as configuration and skips the whole file in silence. That is how the user goes missing and the login fails with no error shown anywhere in the panel. Writing cloud-init user data for a new VPS covers the fields worth setting.
One trap belongs here. User data runs once per instance, keyed by the instance id. Restore a snapshot and the instance id is unchanged, so nothing in the file runs again. The file builds a fresh server. It does not repair an old one.
Services in a Compose file. Keep docker-compose.yml in git with every image pinned to a version tag, and confirm that the file describes what is actually running:
docker compose config
docker volume lsdocker compose config prints the merged file with variables resolved. If that output does not match the running stack, something on this box was done by hand, and it is exactly what will be missing on the next one. Set restart: unless-stopped on every service so the stack returns after a reboot on the new host, and making a Compose stack start on boot covers the systemd side of that.
Named volumes are the part people forget, because they live outside the project directory:
docker run --rm -v app_data:/data -v "$PWD:/backup" alpine tar czf /backup/app_data.tar.gz -C /data .The same image restores it on the new server with tar xzf, into a volume you create first using docker volume create app_data.
Data in an off-box backup. That is the restic repository from Audit 3, written to storage that belongs to neither the old provider nor the new one.
A rehearsal. Build the whole thing once on the cheapest instance you can rent, using the two files and the last backup, and time it. That number is your real lock-in, measured in hours. The move itself is a separate job: moving a running server to a new VPS walks through the copy and the cutover.
Lower the DNS TTL before you need it
Every DNS record carries a TTL (time to live), the number of seconds a resolver may keep it cached. A record at 86400 is cached for a day, so some visitors keep reaching the old server for a day after you change the address. Lowering the TTL is not instant either, because caches hold the old, longer value until it expires.
Lower it first, then wait one full old TTL before the cutover:
dig +noall +answer example.com A
dig +noall +answer @ns1.example.com example.com AThe first query goes to your resolver, and the TTL it prints counts down as the cached copy ages. The second asks the authoritative name server, which always reports the value you configured. Set 300 while the move is in progress, and raise it again a week afterwards.
Keep the old server running for at least one old TTL after the cutover, because traffic still arrives there. If the application accepts writes, make the old copy read-only during that window. Two live copies both taking writes produce split data, and no restore fixes that.
What breaks after a clone: identity and the clock
A cloned or restored server carries the original's identity, and nothing warns you at boot.
/etc/machine-id comes first. systemd derives other identifiers from it, including the DHCP client identity used by systemd-networkd, so two clones can request the same lease and take turns holding one address. Reset it before the clone goes into service:
sudo rm -f /etc/machine-id /var/lib/dbus/machine-id
sudo systemd-machine-id-setup
sudo ln -s /etc/machine-id /var/lib/dbus/machine-id
sudo rebootThe full machine-id reset after cloning a VPS covers the other files that carry an identity, including the ones a monitoring agent keeps.
SSH host keys come second. The clone answers with the same host key as the original, so no client can tell the two servers apart, and anyone connecting to the old name at the new address sees REMOTE HOST IDENTIFICATION HAS CHANGED.
sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -A
sudo systemctl restart sshYour current session survives that restart. Open a second session and confirm the login works before you close the first one. On your own machine, clear the stale entry with ssh-keygen -R 203.0.113.10.
The clock comes third. A restored snapshot boots with the clock set to the moment of the snapshot, which can be weeks behind. Run timedatectl and read two lines. System clock synchronized: no together with NTP service: active means synchronisation has started and not finished. A clock that far behind rejects fresh TLS (transport layer security) certificates with certificate is not yet valid, because the certificate's start date is still in the future from the server's point of view, and time-based one-time password logins fail for the same reason.
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd
timedatectlInterface names come fourth. Netplan configuration names the interface, and providers do not agree on names: ens3 on one, enp1s0 on another. A file that names ens3 matches no device on the new host, so the interface never comes up and only the console works. Match on a pattern instead:
network:
version: 2
ethernets:
primary:
match:
name: "en*"
dhcp4: true
dhcp6: trueRun sudo netplan get on the old box to see what is really configured, and sudo netplan try on the new one, which reverts the change by itself if the connection drops.
Do the audit before renewal, not during an outage
Write the answers down next to your cloud-init file, with the date you checked them. Terms change between renewals, panels gain features, and an export button that did not exist last year may exist now.
The audit is an hour of reading. The recipe is one afternoon. After that, a provider move is an evening of work rather than a project, and the question of whether your provider is replaceable has an answer you measured instead of guessed.
FAQ
Is VPS lock-in real when every provider runs Linux?
Yes, but not at the operating system layer. Ubuntu on one KVM host behaves like Ubuntu on another, so the software you run is portable by default. The lock-in sits around it: snapshots you cannot export, IP addresses that stay behind, reverse DNS you cannot edit yourself, a DNS zone held in the same panel as the server, and a prepaid balance that does not refund. Every one of those is checkable in an hour, and each has a fix you can apply before you need it.
Can I export a VPS snapshot and boot it at another provider?
Only if the panel offers an export or download action, and many do not. Where the only verb next to a snapshot is Restore, that snapshot works inside one provider and nowhere else. Even with an export, the image still has to suit the destination: the same CPU architecture, and a boot mode the new host supports. A file-level backup written with a tool such as restic, to storage you control, restores onto any host without those conditions.
How far in advance should I lower the DNS TTL?
At least one full old TTL before the cutover. Resolvers keep the previous value until it expires, so a record at 86400 needs a day between the change and the move. Set 300 during the move, confirm the value at the authoritative server with dig +noall +answer @ns1.example.com example.com A, and keep the old server answering for another TTL after you switch the record.
What breaks after cloning a server onto a new provider?
Four things, all of them silent. The duplicate /etc/machine-id collides with the original, which can put two servers on one DHCP lease. The SSH host keys are duplicated, so clients cannot distinguish the two machines. The clock sits at the moment of the snapshot until NTP catches up, which makes new TLS certificates look not yet valid. And the network configuration names an interface that does not exist on the new host, so the box boots with no network and only the console reaches it.
Should I run this audit before renewal or while moving?
Before renewal. During a move you are learning a new panel, watching a data copy and waiting on DNS, and that is a bad moment to discover that your term has five months left or that the snapshot cannot be exported. A quiet hour a few weeks ahead of the renewal date costs nothing and changes what the renewal decision rests on.