KVM vs Xen vs LXC: what your VPS runs
KVM, Xen and LXC decide whether your VPS has its own kernel, real swap, working nested virtualisation and honest steal time. Find out which one you bought.
What your VPS plan is really selling
KVM, Xen and LXC are the three virtualisation families a VPS plan is built on, and the choice is not a detail of the provider's rack. It decides whether you get your own kernel. Everything a buyer cares about follows from that one fact: loading modules, controlling swap, running nested virtualisation, whether /proc describes your server or somebody else's, and whether steal time is measurable at all.
Full virtualisation (KVM and Xen HVM) gives every tenant a kernel and a virtual machine. Paravirtualised Xen also gives you a kernel, but one that knows it is a guest and asks the hypervisor to do privileged work for it. A container plan (LXC, or the OpenVZ and Virtuozzo line) gives you a filesystem and a set of namespaces on the provider's kernel. All three are sold under the same three letters.
KVM vs Xen vs LXC: one kernel each, or one kernel shared
On KVM and on Xen, uname -r names your kernel. You can install a different one, load a module into it, and reboot into it. Nothing you do there touches another tenant. On a container plan, uname -r names the provider's kernel, running on the host, shared with every other container on that machine. You cannot change it, and apt install linux-image-generic will unpack files that never boot.
That single difference is worth more than any spec sheet. Read the rest of this guide as consequences of it.
Full virtualisation: KVM and Xen HVM
KVM (kernel-based virtual machine) is a module inside the Linux kernel that turns an ordinary Linux host into a hypervisor, using the Intel VT-x or AMD-V instructions built into the CPU. QEMU supplies the virtual hardware around it: disk, network card, serial console. Xen is a different design. Xen is its own hypervisor and boots before Linux does. A privileged control domain called dom0 runs the management stack, and each tenant is a domU. Xen HVM (hardware virtual machine) uses the same CPU extensions KVM does, usually with paravirtual drivers for disk and network because emulated hardware is slow. That combination is called PVHVM.
For a tenant, the two behave almost identically. You get a kernel, a bootloader, a real block device, a working modprobe, an honest /proc, swap you own, and a reboot that actually boots. If the provider lets you attach an ISO, you can install a distribution they never offered.
You pay for that in density. Your 4 GB is committed to your machine and cannot be lent to a neighbour while you sit idle, and every guest carries a QEMU process, its own page tables and its own page cache. That cost is why a KVM plan is priced above a container plan showing the same numbers.
Paravirtualised Xen, and how to recognise it
Xen PV came before CPUs had virtualisation instructions. Instead of trapping privileged instructions, the guest kernel is modified to call the hypervisor directly. It runs without VT-x, which was the entire point in 2005. The kernel is loaded from inside your own disk image by pygrub or pvgrub, so it is your kernel, but it must be built with PV guest support.
Signs you are on one: lscpu reports the virtualisation type as para rather than full, /sys/hypervisor/type exists and names Xen, and your disks are xvda rather than vda or sda. Tools that read the SMBIOS or DMI tables find nothing to read, because a PV guest has no firmware to publish them.
What it costs you is nested virtualisation, permanently. A PV guest is never shown the CPU virtualisation extensions, so no hypervisor can run inside it. Xen itself is not dead. Xen PV specifically is the part that has faded, and the project's own direction moved to PVH and HVM. If a plan says "Xen", ask which one. HVM is a normal modern VPS. PV is a plan you should price lower.
Container VPS: LXC and the OpenVZ line
A container VPS is a set of Linux namespaces (separate views of process IDs, mounts, network interfaces, hostname and users) plus cgroups (control groups, the kernel's resource limits) running on the provider's kernel. Your init is a process on the host. Your ls runs on the host's kernel directly, with no emulation and no second scheduler in the way. That is why containers are fast and dense.
The names on an order page are LXC, Proxmox VE containers (which are LXC), OpenVZ, and Virtuozzo. OpenVZ 7 and Virtuozzo are the commercial descendants of the same idea.
Four things change for you:
- Modules.
modprobewill not insert anything. If WireGuard, ZFS or a specific netfilter module is not already in the provider's kernel, you cannot have it. sysctl. Most of/proc/sysis read only. Networking is a real namespace, sonet.ipv4.ip_forwardand its neighbours usually are writable. Machine-wide knobs such asvm.swappinessorfs.file-maxbelong to the host.- Nested containers. Docker inside an LXC container works only when the provider enables nesting and the storage driver cooperates. Test it before you buy rather than assuming it.
- The kernel version. You inherit the provider's upgrade schedule, including the reboots.
How to tell which one you bought
Run these on the box and read the answers together. No single command settles it.
systemd-detect-virt
systemd-detect-virt -c
lscpu | grep -iE 'hypervisor|virtualization'
uname -r
ls /lib/modules/$(uname -r) 2>/dev/null | head -n 3
cat /sys/hypervisor/type 2>/dev/nullsystemd-detect-virt prints one short identifier from a fixed vocabulary. The machine side includes kvm, qemu, xen, amazon and vmware. The container side includes lxc, lxc-libvirt, openvz, docker and systemd-nspawn. When it detects nothing it prints none and exits non-zero. The -c form answers only for container technologies, so any answer other than none there settles the question, whatever the sales page said.
lscpu names the hypervisor vendor and states whether the virtualisation type is full or para, which is how you separate Xen HVM from Xen PV. /sys/hypervisor/type exists only under Xen.
The /lib/modules check is the one people skip, and it is the most direct. If the directory for the running kernel version is missing or empty while the system is plainly running that kernel, the kernel did not come from your filesystem. It came from the host, and its module tree was never installed in your image. That is a container.
For an independent second opinion, sudo apt install -y virt-what && sudo virt-what runs the detection tests as a dedicated tool. It needs root, and it prints nothing at all on bare metal.
Why /proc describes the wrong machine in a container
On a KVM or Xen guest, /proc/meminfo is your own kernel's accounting of the memory the hypervisor handed you. It is true about your machine, and it says nothing about the host. That is the point of a virtual machine.
In a container there is no second kernel doing that accounting, so /proc is the host's /proc. LXCFS is a small filesystem that rewrites some of those files to match your cgroup limits, and it covers /proc/cpuinfo, /proc/meminfo, /proc/stat, /proc/uptime, /proc/swaps, /proc/diskstats and /sys/devices/system/cpu/online. Proxmox mounts it by default. Plenty of smaller providers do not, and then free -m reports the host's total memory, nproc can report every core in the machine, and uptime reports how long the host has been running.
This is not cosmetic, because software sizes itself from those files. nginx with worker_processes auto counts the cores it can see. make -j$(nproc) on a 64 core host with a 2 core quota starts 64 compilers. A JVM or a database that picks a cache size from MemTotal picks a number your cgroup will refuse, and the kernel kills the process when it reaches the limit. That kill is logged to the host's kernel log, which you cannot read.
The authoritative numbers live in the cgroup, not in /proc:
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/cpu.maxThose are cgroup v2 paths, which is what current distributions use. memory.max reading max means no limit is set at that level. cpu.max prints a quota and a period in microseconds, so 200000 100000 is two cores of CPU time per period. On an older cgroup v1 host the same values sit under /sys/fs/cgroup/memory/memory.limit_in_bytes and /sys/fs/cgroup/cpu/cpu.cfs_quota_us.
Swap, and who actually owns it
On KVM and Xen, swap is yours. It is a file or a partition on your disk, and your kernel does the paging.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --showswapon --show should now list the file with its size and priority. If swapon refuses the file, build it with dd if=/dev/zero of=/swapfile bs=1M count=2048 instead, because a preallocated file with unwritten extents is rejected on some filesystems. Add /swapfile none swap sw 0 0 to /etc/fstab, or the swap is gone after the next reboot.
In a container none of that is yours. swapon needs a capability an unprivileged container does not hold, so creating your own swap file fails on the permission and never reaches the disk. What the plan calls swap is a cgroup setting on the host, memory.swap.max under cgroup v2, backed by the host's own swap devices. Older OpenVZ plans sold a "vswap" allowance that behaved closer to a burst credit than to a disk. You can read the ceiling. You do not control the device under it.
Nested virtualisation, and the CPU flag that lies
Nested virtualisation means running a hypervisor inside your VPS: a QEMU guest, a Vagrant box, a nested virtualisation lab with its own VMs. Two conditions must both hold. The provider must enable nesting on the host, and your guest must be shown the CPU's virtualisation extensions.
lscpu | grep -i virtualization
ls -l /dev/kvm
sudo apt install -y cpu-checker && sudo kvm-okOn a KVM guest with nesting enabled, /dev/kvm exists and kvm-ok reports plainly whether acceleration can be used. On Xen HVM it is technically possible and rarely offered. On Xen PV it cannot happen.
In a container the check fails in an instructive way. /proc/cpuinfo is the host's file, so the vmx or svm flag is present, and it is genuinely true: the physical CPU under you really does have those instructions. It is still not yours. There is no /dev/kvm in your namespace, you cannot load the kvm_intel module, and the flag you just read describes the machine you are a guest on rather than a machine you control. This is the clearest case of the general rule. In a container, /proc describes the namespace and the hardware around it, not a server you own.
AES-NI and the CPU features your plan exposes
AES-NI (advanced encryption standard new instructions) is a set of CPU instructions that make AES encryption several times faster than the same maths in software. TLS termination, disk encryption, SSH and backup pipelines all lean on it.
On KVM, what your guest sees is decided by the CPU model the provider configured for QEMU. With host passthrough you see the real flags. With a generic model such as qemu64, or a deliberately old baseline chosen so guests can migrate between mismatched hosts, the aes flag can be absent, and OpenSSL quietly falls back to its software path.
lscpu | grep -ow aes | head -n 1
openssl speed -evp aes-128-gcm
env OPENSSL_ia32cap='~0x200000000000000:~0x20000000000:~0x0:~0x0:~0x0' openssl speed -evp aes-128-gcmThe third command is the example from the OpenSSL manual for switching those instructions off inside the library: it clears the AES-NI bit and the VAES bit and leaves the rest alone. Compare the two throughput figures. If they land close together, the fast path was not being used in the first place, and checking AES-NI on a VPS properly is worth the five minutes before you commit to a plan.
A container has no CPU model in front of it, so the flags in /proc/cpuinfo are the host's real flags and they apply to you. That is a real advantage of container plans, and it is the one place in this guide where the shared kernel works in your favour.
Where steal time comes from, and why a container has none
Steal time is time your virtual CPU was ready to run and did not run, because the hypervisor was running someone else. It appears as st in top and vmstat, and as the eighth field of the cpu line in /proc/stat.
A guest cannot measure this by itself, because it is not executing while the time is being taken. The hypervisor has to tell it. KVM writes a running total into a page the guest registers through its paravirtual clock interface, and Xen keeps a per vCPU run state area for the same job. The number you read is the hypervisor's own confession, which is why it exists and why it is worth trusting.
High steal means the host is oversubscribed and your neighbours are busy at that moment. It is the visible face of the ratio between sold vCPUs and physical cores, and reading steal time to spot a noisy neighbour is the one measurement that tells you whether a plan is as large as its page claims.
vmstat 1 5
cat /sys/fs/cgroup/cpu.statIn a container that column will not move, because there is no hypervisor between you and the scheduler. Your processes queue alongside every other tenant's processes as ordinary tasks in the host's own CPU scheduler. Contention arrives as work simply taking longer, with no counter naming the cause. The nearest equivalent is quota throttling: when the provider sets cpu.max, then /sys/fs/cgroup/cpu.stat counts nr_throttled periods and throttled_usec microseconds spent waiting for the next quota window. That covers your own quota only, never competition from neighbours. One caution: if the provider's container host is itself a virtual machine, a steal figure can appear in /proc/stat, and it belongs to that host rather than to you.
Overselling, and why the container plan is cheaper
The honest answer is short. A container plan costs less because the provider is sharing more of the same machine with more people.
Memory is where the gap is widest. A KVM guest's RAM is committed to it, so a host with 256 GB sells roughly 256 GB of guests, minus overhead. A container's memory limit is a ceiling rather than a reservation, and memory a container is not using is immediately available to the others, so the provider can sell limits adding up to several times the physical RAM and be right almost all of the time. Nothing is faked. It works until enough tenants are busy at once, and then it stops working for everybody.
CPU is oversold on every plan type, KVM included, by selling more vCPUs than there are cores. Disk is thin provisioned nearly everywhere. Containers stack extra density on top of that: one kernel, one page cache, no per guest QEMU process, so a host holds several times more tenants.
What you trade away is isolation, and that is a real engineering trade rather than a scare story. You share a kernel, so a kernel bug is a shared problem, and a container escape lands directly on the host. Escaping a virtual machine requires a hypervisor bug, which is a far smaller and far harder target. You also inherit the provider's kernel upgrade and reboot schedule. If any of that matters to you, read how safe VPS hosting really is before choosing on price alone.
Which one to buy
Buy KVM when you need your own kernel: WireGuard or ZFS modules, a specific kernel version, nested virtualisation, real control over swap, or a boundary you can explain to an auditor. Buy a container plan when you are running ordinary services on a budget, the provider's kernel is current, and you have confirmed that the features you depend on are already compiled into it. Treat Xen HVM as equivalent to KVM for most purposes, and ask the question before buying anything still sold as Xen PV.
Two shapes sit outside this split. Firecracker microVMs give each tenant a real kernel with startup costs close to a container's, which is what serverless platforms run on. Incus system containers let you run the container model yourself on hardware you control, which is a different position from being sold one. If the vocabulary is the sticking point, what a VPS actually is and the difference between a VPS, a VM and a VPC cover the terms this guide assumes you already have.
FAQ
How do I tell if my VPS is KVM or a container?
Run systemd-detect-virt -c. Any answer other than none means you are inside a container, whichever product name the plan carries. Confirm it two more ways, because detection can be fooled. lscpu names a hypervisor vendor and states whether the virtualisation type is full or para. ls /lib/modules/$(uname -r) is missing or empty on a container, because the running kernel came from the host and its module tree was never installed in your filesystem. sudo virt-what gives an independent answer from a tool written only for this question.
Why does free -m show far more memory than my plan includes?
You are on a container plan without LXCFS mounted, so /proc/meminfo is the host's file and free is faithfully reporting the host's memory. Your real ceiling is the cgroup. Read /sys/fs/cgroup/memory.max for the limit and /sys/fs/cgroup/memory.current for current use, or /sys/fs/cgroup/memory/memory.limit_in_bytes on an older cgroup v1 host. Configure any service that sizes a cache or a worker pool from that number rather than from free.
Can I run Docker or WireGuard on an LXC VPS?
Sometimes, and never because of anything you install. Both depend on the provider's kernel, since you cannot load a module into it. WireGuard works when the module is already present on the host and exposed to you, and the userspace wireguard-go implementation is the fallback when it is not. Docker needs the provider to allow nesting and needs a storage driver that functions inside a container. Ask before you buy, or test on a term you can walk away from.
Why does my container VPS never report steal time?
Steal time exists only when a hypervisor is scheduling a virtual CPU, and it is reported because that hypervisor writes the figure into a page your kernel reads. A container has no hypervisor beneath it. Your processes are ordinary tasks in the host's scheduler, so contention shows up as everything taking longer with no counter to point at. Read /sys/fs/cgroup/cpu.stat instead: nr_throttled and throttled_usec count the time your cgroup spent waiting for its next CPU quota window, which is the closest thing to steal a container has.