Why a 500GB disk shows 465GB in Linux
A 500GB VPS disk shows 465GB because plans count in powers of ten and Linux counts in powers of two. Check the arithmetic on your own server.
Why a 500GB disk shows 465GB
A 500GB disk shows 465GB because both numbers describe the same bytes in two different units. Your hosting plan and the drive label count in decimal, where 1 GB (gigabyte) is 1,000,000,000 bytes. Linux counts in binary, where the unit df prints as G is 1,073,741,824 bytes, which is 1024 x 1024 x 1024. The divisor is larger, so the printed number is smaller. Nothing was taken from you.
The byte count is the only thing that is real. Every size you read on screen is a rendering of that one number. This guide does the arithmetic first, then makes you run it on your own server, then covers the second and smaller reason that df reports less than you expected.
The two units, and why both are correct
Two families of prefixes exist for the same words. SI (the International System of Units) defines kilo, mega, giga and tera as powers of 1000, so 1 GB is 1,000,000,000 bytes. IEC (the International Electrotechnical Commission) defines kibi, mebi, gibi and tebi as powers of 1024, so 1 GiB (gibibyte) is 1,073,741,824 bytes. Storage vendors and hosting plans use the first family. The Linux kernel, df, ls -lh and du -h use the second.
The confusion is a labelling problem. Most Linux tools print a bare G and mean GiB. Nothing on screen tells you which unit you are looking at, so a number that is about 7 percent lower reads as a shortage instead of a conversion.
The gap is a fixed fraction of the plan, so it grows as the plan grows. Here is the same arithmetic across six common plan sizes.
The data behind this chart
[
{
"plan": "25 GB",
"advertised_gb": 25,
"binary_gib": 23.29
},
{
"plan": "50 GB",
"advertised_gb": 50,
"binary_gib": 46.57
},
{
"plan": "100 GB",
"advertised_gb": 100,
"binary_gib": 93.14
},
{
"plan": "200 GB",
"advertised_gb": 200,
"binary_gib": 186.27
},
{
"plan": "500 GB",
"advertised_gb": 500,
"binary_gib": 465.67
},
{
"plan": "1 TB",
"advertised_gb": 1000,
"binary_gib": 931.33
}
]The binary_gib column is not an estimate. Each value is what numfmt --to=iec --format=%.2f printed for that plan's byte count when I ran it. A 500 GB plan holds 500,000,000,000 bytes, and in the units Linux prints that is 465.67 GiB. All 6 rows follow the same ratio, so the largest one, 1 TB, lands at 931.33 GiB.
Check the arithmetic with numfmt
numfmt ships with GNU coreutils, so it is already on your server. It converts a byte count into either unit family without touching a disk, which makes it the cleanest way to see the conversion on its own.
$ numfmt --to=si 500000000000
500G
$ numfmt --to=iec 500000000000
466G
$ numfmt --to=iec --format=%.2f 500000000000
465.67G--to=si divides by 1000, so it hands back the number on your invoice. --to=iec divides by 1024 and gives the number Linux shows you. One unchanged byte count, two renderings.
That last line also explains why some tools say 465 and others say 466. Ask for more decimal places:
$ numfmt --to=iec --format=%.6f 500000000000
465.661288GThe exact value is 465.661288, so rounding to two places to the nearest would give 465.66. numfmt printed 465.67, because its default rounding method is from-zero, which for a positive number means always upward. The same rule turns 465.661288 into 466G when you ask for no decimals at all. A tool that truncates instead of rounding prints 465. Every one of those answers comes from the same bytes.
Put df -h beside df -H on one filesystem
df will render the same filesystem in either family. -h is binary. -H, which can also be spelled --si, is decimal. -B1 prints raw bytes with no scaling and no rounding.
Here are all three against the root filesystem of the machine I wrote this on. Your device name and your numbers will be different.
$ df -h /
Filesystem Size Used Avail Use% Mounted on
overlay 2.0T 72G 2.0T 4% /
$ df -H /
Filesystem Size Used Avail Use% Mounted on
overlay 2.2T 78G 2.2T 4% /
$ df -B1 /
Filesystem 1B-blocks Used Available Use% Mounted on
overlay 2198952804352 77146370048 2121806434304 4% /One filesystem, one moment in time, three different answers. The -B1 line is the only one with no rounding in it, and it is the input the other two are computed from. 2,198,952,804,352 bytes is 2.00 TiB and 2.20 TB, so -h prints 2.0T and -H prints 2.2T.
df rounds upward, the same way numfmt does. The Used column shows it plainly. 77,146,370,048 bytes is 77.147 GB in decimal, and df -H printed 78G. The same bytes are 71.849 GiB in binary, and df -h printed 72G. Both were rounded up to the next whole unit, so a single missing decimal place can move the figure by most of a gigabyte. Use df -H when you are comparing against a plan, and df -h when you are comparing against anything else Linux tells you.
Read one file size in all four ways
Stop guessing and make a file whose length you chose yourself.
truncate -s 500000000000 /tmp/disk500.imgtruncate sets a file's length without writing any data into it. The result is a sparse file: it reports a length of 500,000,000,000 bytes while occupying almost no blocks, so this costs you nothing even on a small disk. Now ask four tools to describe that one length.
ls -l /tmp/disk500.img
ls -lh /tmp/disk500.img
ls -l --si /tmp/disk500.img
du -h --apparent-size /tmp/disk500.imgRead the size column in each of the four lines and compare them. ls -l prints the length in raw bytes, so it is the only one that gives back the number you typed into truncate. ls -lh scales that number by 1024. ls -l --si scales the same number by 1000, which is the vendor's unit, so that is the line which agrees with your plan. du --apparent-size reports the file's length rather than the blocks it occupies, and -h scales it by 1024 exactly as ls -lh does.
Drop --apparent-size and du answers a completely different question: how many blocks the file really occupies on disk. For a sparse file that has nothing to do with units. Remove the file when you are finished.
rm /tmp/disk500.imgWhy the Avail column is lower again
Unit conversion accounts for most of the gap. A second and smaller cause accounts for the rest, and you can see it as the Avail column sitting below Size minus Used.
df does not report a disk. It reports a filesystem, and a filesystem spends part of the disk on itself. mkfs writes a superblock, group descriptors, block bitmaps, a journal and an inode table before you store a single file. On ext4 the inode table is sized at format time from a bytes-per-inode ratio and cannot grow afterwards, so those bytes leave the data area for the life of the filesystem.
Count them on your own server, where the answer describes your filesystem rather than mine:
df --output=itotal,iused,iavail /On ext4 the first column is that fixed table, and it reads the same number after you delete every file you own. On xfs it is an estimated ceiling instead, because xfs allocates inodes on demand rather than reserving them at format time. Either way the blocks those inodes sit in were subtracted from the Size column before you copied anything in.
Then there is the reserve. By default mkfs.ext4 marks 5 percent of the blocks as reserved for the root user. There are two reasons for it. A full disk that still has room for root to log in and delete something is recoverable, and a block allocator that never runs completely dry produces less fragmentation. df fills its Available column from f_bavail, which excludes those reserved blocks, so on a default ext4 filesystem Size minus Used comes out larger than Available.
Do not take that on trust. Find your root device, then read the numbers off it on your own server:
findmnt -no SOURCE /
sudo tune2fs -l /dev/vda1 | grep -Ei 'block count|block size|reserved'Replace /dev/vda1 with whatever findmnt printed. tune2fs only understands ext2, ext3 and ext4; on xfs the equivalent is xfs_info /. Compare Reserved block count against Block count, multiply the reserved figure by Block size, and you have the exact number of bytes df is holding back from you.
The filesystem in the df -B1 output above keeps no reserve of that kind. Subtract its Used from its 1B-blocks and you get its Available exactly, with nothing left over. Do that same subtraction on a default ext4 root filesystem and it will not come out even, and now you know which blocks are missing.
On a large data volume that reserve is worth reducing, because 5 percent of a big volume is a lot of blocks held for a user who will never log in:
sudo tune2fs -m 1 /dev/vdb1That sets the reserve to 1 percent. Leave the root filesystem alone, since that is the one where running out of space stops you fixing anything. This is a separate problem from a disk that fills while df and du disagree about who holds the space, which is usually a deleted file still held open by a running process and needs a different fix entirely.
What a 200 GB plan should show
Work downward from the plan. A 200 GB plan gives you 200,000,000,000 bytes of block device. Ask the kernel for that in its own units and you get roughly 186.27 GiB, which is the first and largest drop. From there subtract the partition table, an EFI system partition if the image has one, a swap partition if the image has one, and the filesystem metadata described above. Subtract the ext4 reserve again from whatever df is willing to call Available. The number you finally see is well below the one on the invoice, and every step of the reduction is one you can measure yourself.
None of it is the provider taking a cut. The block device really does hold the bytes you paid for, and df -B1 will show you those bytes with no unit conversion standing in the way. Before you count them, confirming that the disk under your filesystem is genuinely NVMe takes one command and tells you what kind of device you are measuring.
Two questions are worth asking before you buy. Does the advertised figure describe the raw volume or the usable filesystem, since an image that ships with swap spends some of your allowance before you ever log in. And is the storage local or attached, because a block storage volume you attach after the fact arrives as a bare device you format yourself, which puts the metadata cost under your own control. If you are still choosing a size, working out how much storage a VPS actually needs beats picking the biggest number you can afford and then feeling short-changed by the rounding.
Redundancy is the other place capacity disappears, and that one is not arithmetic. RAID 10 across a set of drives returns half of their combined capacity by design, so a host quoting usable space has already done that subtraction on your behalf. Precise words doing quiet work in a plan description is a pattern worth recognising in the other columns too, which is the subject of what an unmetered bandwidth allowance actually promises.
FAQ
Why does df show 465G on a 500GB VPS disk?
Because df -h divides the byte count by 1,073,741,824 while your plan counts in units of 1,000,000,000. The disk holds 500,000,000,000 bytes under either label. Run numfmt --to=iec --format=%.2f 500000000000 to see the conversion on its own, then run df -H / to make df print the same filesystem in the decimal units your plan uses.
Is my provider taking a cut of the storage I paid for?
No. Check it with df -B1 /, which prints raw bytes with no scaling and no rounding, and compare that figure to your plan. What you are looking at is unit conversion first, then filesystem metadata and the ext4 reserved blocks second. Both causes are measurable with commands you can run right now, and neither one removes bytes from the device.
Why is the Avail column smaller than Size minus Used?
Because ext4 reserves blocks for the root user, 5 percent by default, and df excludes them from Available. Read the exact figures with sudo tune2fs -l /dev/vda1 and compare Reserved block count to Block count. On a data volume you can lower the reserve with sudo tune2fs -m 1 /dev/vda1. Keep some reserve on the root filesystem, since a completely full root filesystem is much harder to recover.
How do I make Linux report disk size in the same units as my plan?
Add -H to df, or --si, which is the same flag written out. Add --si to ls -l for files. Both switch the divisor from 1024 to 1000. For a single number with no file and no filesystem involved, numfmt --to=si converts a byte count directly. The bytes never change. Only the divisor does.