Server management

VPS bandwidth vs monthly traffic: how to measure and reduce it

VPS bandwidth: a terminal card comparing the network port's instantaneous capacity with the monthly traffic quota, showing how vnStat counts inbound and outbound usage and why it differs slightly from the provider's bill

The seller says "1 Gbps port with 5 TB of monthly traffic", and you can't tell which of the two numbers runs out first; then your host's panel shows a figure that doesn't match the one on the server itself. This guide explains how VPS bandwidth differs from monthly traffic, where your provider takes its measurement, how to get the right number with vnstat and the kernel counters, and which measures really cut your usage in half.

VPS bandwidth vs traffic: two completely different things

Almost every billing misunderstanding starts with one word being used for two different concepts:

  • Bandwidth is a capacity, not a reservoir. It is measured in bits per second and tells you the maximum amount of data that can pass through the network port each second. Think of the diameter of a water pipe.
  • Traffic, or data transfer, is a volume. It is measured in gigabytes or terabytes per month and tells you how many bytes moved in total during a billing period. Think of the water meter.

A wide pipe isn't expensive; the water that flows through it is. That is why a provider gives you a 1 Gbps port for free but is strict about monthly volume. For a sense of scale, picture a 1 Gbps port that stays saturated without a single second's pause:

1 Gbps  = 1000 Mbit/s / 8 = 125 MB/s
125 MB/s x 86400 s        = 10,800,000 MB  = 10.8 TB  per day
10.8 TB x 30              = 324 TB         per month

Now put those 324 TB next to a typical quota: a plan with 5 TB of monthly traffic means you may use about 1.5 percent of that port's theoretical capacity. The reverse is just as telling: the same 5 TB, spread evenly over the month, is a sustained average of about 15 Mbps. For you, a gigabit port is an instantaneous capacity, not a standing promise.

ConceptUnitWhat it limitsWhat happens when it runs out
Bandwidth / port speedMbps or GbpsInstantaneous download and upload speedNothing runs out; you just get slower
Traffic / monthly quotaGB or TB per monthTotal bytes transferredEither the port is slowed down or you pay overage charges

And what about "unlimited" or "unmetered" bandwidth? In practice, one of two models sits behind it. Either there is an unwritten quota called fair usage, after which the port is throttled from 1 Gbps down to 100 or even 10 Mbps, or the port is capped at a low speed from the start and "unlimited" only means nobody meters the volume. Ask explicitly which one it is before you buy; that question is also one of the main criteria in our VPS buying guide.

💡 Note for servers in Iran: Iranian servers usually have two separate meters: domestic traffic (to IP addresses inside Iran) and international traffic (anything that crosses Iran's links to the rest of the internet), and their rates sometimes differ by a factor of 5 to 10. If your audience is inside Iran, this split alone can be your biggest cost factor. How traffic is metered on Mehran Host's Iran servers is covered in our guide to international access and traffic billing on an Iran VPS.

Inbound vs outbound traffic: which one you pay for

Every byte that passes through the server's port is one of two kinds:

  • Inbound / ingress (rx): data that arrives at the server: HTTP requests, file uploads, packages downloaded with apt, and attack packets.
  • Outbound / egress (tx): data the server sends: pages, images and videos, API responses, a backup going off-site.

For a web server the two are nowhere near the same size. A browser request is a few hundred bytes of headers, while the response can be 200 KB; even counting TCP ACKs, the ratio of outbound to inbound on an ordinary website comes out at 10 to 30 times. It is the other way round on a server that serves as a backup destination or where users upload files: there, inbound is the bigger number.

Now the money point: most large providers bill only outbound traffic and inbound is free, because their own outbound traffic to the upstream network is what actually costs them. Some count both directions combined, and some bill "whichever is higher". To find out which model you are on, don't read the contract; compare the panel's figure with the server's own:

vnstat -d -i eth0        # rx / tx, broken down by day

If the panel's figure is roughly equal to tx, they sell outbound only; if it is close to rx + tx, they count the total. If it matches neither, ask them directly.

Measuring traffic on the server itself, with real tools

First find out which network interface carries your default route out. Many servers have several interfaces (lo, docker0, tunnels), and measuring the wrong one gives you a meaningless result:

ip -o -4 route show to default | awk '{print $5}'
# eth0

vnStat: the only tool that keeps a history of monthly usage

vnstat is a lightweight daemon that reads the kernel counters every few seconds and stores them in an SQLite database. Its resource use is practically zero:

sudo apt install vnstat          # Debian / Ubuntu
sudo dnf install vnstat          # AlmaLinux / Rocky
sudo systemctl enable --now vnstat

vnstat                 # overall summary
vnstat -d              # daily breakdown
vnstat -m              # monthly breakdown: the one you compare with your bill
vnstat -h              # the last 24 hours, hour by hour
vnstat -t              # top days by usage
vnstat -l              # live view
vnstat --json m        # machine-readable output for scripts and alerts
 eth0  /  monthly

       month        rx      |     tx      |    total    |   avg. rate
    ------------+-----------+-------------+-------------+---------------
      2026-06     94.16 GiB |   1.42 TiB  |   1.51 TiB  |    4.79 Mbit/s
      2026-07    118.03 GiB |   2.06 TiB  |   2.18 TiB  |    7.21 Mbit/s

Two points mislead a lot of people. First, vnstat only counts from the moment it is installed, so install it today to have independent data next month. Second, its default output is in GiB (a factor of 1024), but providers usually invoice in GB (a factor of 1000); that alone creates a 7 percent difference, and you can change it in /etc/vnstat.conf.

Live view: what is using traffic right now

sudo apt install iftop nload nethogs

sudo iftop -i eth0 -nNP    # busiest connections, with IP addresses and ports
nload eth0                 # simple inbound/outbound graph
sudo nethogs eth0          # traffic per process: unbeatable for finding the culprit

The -nNP switches mean: don't resolve host or service names, and show port numbers. Without them the output is both slow and useless for troubleshooting. nethogs is the only one of these tools that answers the question "which process?", and that is usually where it turns out the culprit is a PHP script or a forgotten rsync.

Raw kernel counters

Underneath all of these tools is something simple: the kernel keeps a cumulative counter for each interface.

cat /sys/class/net/eth0/statistics/rx_bytes
cat /sys/class/net/eth0/statistics/tx_bytes
cat /proc/net/dev

They are always available and need nothing installed, but they hide a serious trap: they start again from zero after every reboot, and even after an ip link set eth0 down/up. If your script blindly computes "current value minus stored value", after a reboot it produces a negative number or, in unsigned arithmetic, an astronomical figure in the petabytes. Spotting it is easy: if the current value is lower than the stored one, a reset has happened, and the current value itself is the delta:

#!/bin/bash
# NIC counters restart at zero after a reboot or an `ip link` down/up, so a
# blind (current - stored) subtraction invents petabytes of traffic. A reading
# lower than the stored one is the only reliable signature of that reset.
IF=${1:-eth0}
DB=/var/lib/bwsnap-$IF

rx=$(cat /sys/class/net/$IF/statistics/rx_bytes)
tx=$(cat /sys/class/net/$IF/statistics/tx_bytes)

prev_rx=0; prev_tx=0; acc_rx=0; acc_tx=0
[ -r "$DB" ] && read -r prev_rx prev_tx acc_rx acc_tx < "$DB"

if [ "$rx" -lt "$prev_rx" ] || [ "$tx" -lt "$prev_tx" ]; then
    acc_rx=$((acc_rx + rx));            acc_tx=$((acc_tx + tx))
else
    acc_rx=$((acc_rx + rx - prev_rx));  acc_tx=$((acc_tx + tx - prev_tx))
fi

echo "$rx $tx $acc_rx $acc_tx" > "$DB"
printf 'in: %d MB   out: %d MB\n' $((acc_rx / 1048576)) $((acc_tx / 1048576))
*/5 * * * * /usr/local/bin/bwsnap.sh eth0 >/dev/null 2>&1

Every five minutes is enough. A shorter interval adds no accuracy, because the counter is cumulative and nothing gets lost between two samples; it only catches a reset sooner. How to combine this script with a Telegram alert is covered in our Linux server monitoring guide.

Why your number never exactly matches your provider's

The gap is always there, and it isn't always cheating. It has five real causes:

  • Where it is measured. You count inside the guest operating system; the provider counts at the switch port or at the tap interface on the hypervisor side. The important consequence: a packet your firewall drops has already crossed the port, and its bytes have already been counted. A scan or a small attack shows up on the bill without a matching effect in vnstat.
  • Lower-layer overhead. The guest's counter counts Ethernet frames, but what goes on the wire also includes the preamble and the gap between frames. For 1,500-byte packets that makes a 2 to 3 percent difference, and more for small packets (APIs, games, DNS).
  • Sampling interval. Many providers sample with SNMP every five minutes and interpolate between samples; short peaks get smoothed out, and rounding errors add up over thousands of samples.
  • What gets counted at all. Some panels don't count IPv6 traffic; traffic on the private network and to the provider's internal mirrors is usually free as well, but vnstat counts it on the same interface.
  • The period boundary. Your server is probably on UTC, while the billing period closes on the provider's local time. For an Iranian provider that is Tehran time (UTC+3:30), and a 3.5-hour shift on the last day of the month carries a few gigabytes over into the next month.

The practical summary: a gap of 2 to 5 percent is completely normal, and up to 10 percent can still be explained. If a persistent gap goes past 15 percent, it is time to open a ticket, with the vnstat -m and vnstat -d output for the same period, not a dashboard screenshot.

⚠️ If the panel's figure is much larger, look for the reason before you open a ticket: the odds that your server is sending something you don't know about are far higher than the odds of a counting error.

What actually uses your traffic, in order of impact

Before you guess, ask the web server's log. In the combined format the tenth column is the size of the response body, so total bytes per path is one command away:

awk '{bytes[$7] += $10}
     END {for (u in bytes) printf "%9.1f MB  %s\n", bytes[u]/1048576, u}' \
    /var/log/nginx/access.log | sort -rn | head -20

And the same thing per browser and bot, which is usually even more surprising:

awk '{b = $10; split($0, q, "\""); ua[q[6]] += b}
     END {for (u in ua) printf "%9.1f MB  %s\n", ua[u]/1048576, substr(u, 1, 55)}' \
    /var/log/nginx/access.log | sort -rn | head -15

Now the list of culprits, from heaviest to lightest, based on what shows up on real sites:

  1. Unoptimized images. The undisputed number one. A photo straight from a camera or phone is 3 to 5 MB and 4,000 pixels wide, while the space it is displayed in is 800 pixels; the same image resized to 1,600 pixels and converted to WebP comes to about 150 KB, a saving of more than 95 percent. A page with 15 raw images means 45 MB per visit and 1.3 TB a month for 30,000 visits, just for the photos.
  2. No browser caching. Without a Cache-Control header, a returning visitor downloads all the CSS, JS and fonts again; on a site where 40 percent of users come back, about a third of your traffic is pure duplication.
  3. No compression. HTML, CSS, JS and JSON shrink by about 70 to 85 percent with gzip and a little more with brotli: a 300 KB JavaScript bundle comes down to 85 KB and 72 KB respectively.
  4. Video served from your own server. A 1080p video at 4.5 Mbps costs about 2 GB of outbound traffic per hour watched, and 500 complete views add up to 1 TB. Video should almost never be served from the origin.
  5. Full nightly backups sent off-site. A 20 GB full backup every night means 600 GB of outbound traffic a month; the same data with incremental backups (borg or restic) comes to 300 to 800 MB a day, about one thirtieth as much. The method is in our Linux backup guide.
  6. Bots and scrapers. AI crawlers and SEO tools generate 20 to 40 percent of requests, and because they don't use caches and fetch the full-size versions of images, their share of bytes is larger than their share of requests.
  7. A compromised server. We will say it plainly: a sudden, several-fold jump in outbound traffic is a security incident until proven otherwise. The most common cases are sending spam, taking part in a DDoS attack and hosting unauthorized files. The telltale sign is outbound traffic multiplying while page views stay flat, with the peak at 3 a.m.
ss -tunp | awk 'NR>1 {print $6}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head   # most frequent destinations
postqueue -p | tail -1        # Postfix: number of emails in the queue
exim -bpc                     # Exim: the same number
sudo nethogs eth0             # which process is sending

If the mail queue holds thousands of messages, or an unknown process sits at the top of nethogs, your problem isn't traffic, it's an intrusion. Take the server off the network and start with our Linux server security checklist.

How to reduce bandwidth usage: measures that actually work

1. Turn on compression

# /etc/nginx/conf.d/compression.conf
gzip              on;
gzip_comp_level   5;
gzip_min_length   1024;
gzip_vary         on;
gzip_proxied      any;
gzip_types        text/plain text/css text/xml application/json
                  application/javascript application/rss+xml image/svg+xml;

# ngx_brotli, if the module is installed
brotli            on;
brotli_comp_level 5;
brotli_types      text/plain text/css application/json
                  application/javascript image/svg+xml;

Set the compression level to 5, not 9: above 5, the size drops by only a few percent more while CPU use multiplies. And never put JPEG, PNG or MP4 in gzip_types; they are already compressed, and gzip only burns CPU on them. The rest of the basic configuration is in our Nginx installation guide.

2. Set cache headers correctly

location ~* \.(css|js|woff2|png|jpe?g|webp|avif|svg|ico)$ {
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
    access_log off;
}

location ~* \.html$ {
    add_header Cache-Control "public, max-age=0, must-revalidate";
}

The word immutable tells the browser not to send even a revalidation request, and it is only correct when your static file names are versioned (such as app.4f2a1c.css); otherwise users will keep seeing the old version for up to a year. HTML should never be cached for long.

3. Move images to WebP or AVIF

sudo apt install webp libavif-bin

cwebp -q 80 -resize 1600 0 photo.jpg -o photo.webp
avifenc --min 24 --max 32 photo.jpg photo.avif

# batch-convert the uploads folder
find /var/www/uploads -type f -name '*.jpg' \
     -exec sh -c 'cwebp -q 80 -resize 1600 0 "$1" -o "${1%.jpg}.webp"' _ {} \;

The -resize 1600 0 parameter matters more than the format conversion itself (the zero means the height is calculated automatically to keep the proportions): fix the size first, then the format. In WordPress, plugins do the same job automatically; the details are in our guide to speeding up WordPress.

4. Put a CDN in front of the site

This is the most effective single step: when static files are served from the edge, 60 to 90 percent of the bytes never reach your server and are never counted against your quota. For an audience in Iran, keep two points in mind: the round trip may get longer (discussed in our article on reducing ping for users in Iran), and traffic that reaches a user inside Iran from abroad counts as international.

5. Block greedy crawlers

robots.txt is only a polite request, and a badly behaved bot ignores it. So write one, and also set a limit that is really enforced:

# robots.txt
User-agent: GPTBot
Disallow: /

User-agent: SemrushBot
Disallow: /

User-agent: *
Disallow: /*?s=
Disallow: /wp-json/
# nginx.conf — a limit that is actually enforced
map $http_user_agent $bad_bot {
    default                                              0;
    "~*(Bytespider|SemrushBot|AhrefsBot|MJ12bot|DotBot)"  1;
}

limit_req_zone $binary_remote_addr zone=crawl:10m rate=2r/s;

server {
    if ($bad_bot) { return 429; }

    location / {
        limit_req zone=crawl burst=20 nodelay;
    }
}
⚠️ Never put Googlebot or Bingbot on this list: blocking them means gradually disappearing from search results. Blocking ?s= isn't arbitrary either: an internal search results page generates an endless number of unique URLs and can't be cached.

6. Move heavy jobs to off-peak hours

# cap at 5 MB/s so the nightly backup doesn't eat into your visitors' share
rsync -az --bwlimit=5000 --delete /var/www/ backup@storage:/srv/www/

# incremental, compressed backup
borg create --stats --compression zstd,10 \
     ssh://backup@storage/./repo::www-{now:%Y-%m-%d} /var/www
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true

Mind the server's time zone: 3:30 on a UTC clock is 7:00 a.m. in Tehran, which is no longer a quiet hour for an audience in Iran. Align it with timedatectl set-timezone Asia/Tehran, or with whatever zone your own audience lives in.

MeasureTypical traffic savingEffort
Resizing images + WebP40–70% of total site trafficModerate (one-off)
A CDN in front of the site60–90% of the bytes reaching the serverLow
gzip or brotli10–20% of total trafficLow
Correct cache headers20–35% for returning visitorsLow
Incremental instead of full backupsUp to 95% of backup trafficModerate
Limiting abusive crawlers5–20% of total trafficLow

Traffic economics: a fixed quota or pay as you go?

The real issue is the shape of your usage, not its average. Picture an online store that uses about 900 GB a month for ten months of the year and reaches 4.5 TB during two campaign months. With a fixed quota you have two bad choices. Take a 1 TB plan, and in the campaign month, exactly when you are making sales, either get cut off or pay penalties; or buy a 5 TB plan from the start and pay for unused capacity for ten months.

Before you choose a plan, ask one question explicitly: what exactly happens if the traffic quota is exceeded in the middle of the month? Three policies are common: throttling the port until the end of the period, an overage bill at a per-gigabyte rate, and, in the worst case, cutting the service off until the next top-up. For a store on a campaign day, the difference between these three is the difference between "slow" and "closed". So get the answer on the day you buy, not on the 25th of the month.

Hourly, usage-based billing is built for exactly this shape of usage: in ordinary months you pay for those 900 GB, and in the campaign month the server stays up without a change of plan. The full pricing logic is in our hourly cloud server guide.

The flip side is that this model has no natural ceiling, so a single mistake can be expensive: a download link shared in a big channel, or an endless loop that keeps pulling a large file. So put two safeguards in place. The first is an automatic alert on the percentage used:

#!/bin/bash
# Alert while there is still room to react — not after the bill is issued.
LIMIT_GB=5000
TOKEN=... ; CHAT=...

used=$(vnstat --json m -i eth0 \
       | jq '.interfaces[0].traffic.month[-1] | (.rx + .tx) / 1000000000 | floor')
pct=$(( 100 * used / LIMIT_GB ))

if [ "$pct" -ge 80 ]; then
    curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" \
         -d chat_id="$CHAT" \
         -d text="traffic ${used}GB / ${LIMIT_GB}GB (${pct}%)"
fi

The second is an emergency brake. This command caps the port at 20 Mbps; the site gets slower but stays up, and the meter stops racing:

sudo tc qdisc add dev eth0 root tbf rate 20mbit burst 32kbit latency 400ms
sudo tc qdisc del dev eth0 root      # remove the limit
💡 Tip: set the alert at 80 percent, not 100. If the 80 percent alert arrives on the 15th, you still have half a month to do something about it; a 100 percent alert is just bad news that can no longer be prevented.

Next step

The order of work is clear. Install vnstat on all your servers today, so you have independent data next month. Then use those two awk commands to see exactly where your bytes go, and start with the heaviest item, which is almost always images. Next, turn on compression and cache headers, and finally set an 80 percent alert so that no bill ever takes you by surprise.

And if you are choosing between plans, measure for a month instead of guessing: create an hourly cloud server, move your site onto it, watch vnstat -m and decide with a real number. You pay only for the hours the server exists.

Frequently asked questions

What is VPS bandwidth, and how is it different from monthly traffic?

VPS bandwidth is the instantaneous capacity of the network port, measured in bits per second, while monthly traffic is the total volume of bytes moved in a billing period, measured in gigabytes or terabytes. A wider port only raises the speed at any given moment and never runs out; a traffic quota does run out, after which you are either slowed down or charged extra. For scale, a 1 Gbps port saturated for a whole month moves about 324 TB.

How much monthly traffic does my website need?

Multiply the average size of one page view by your monthly page views, then add about 20 percent for bots, updates and backups. An optimized blog page is about 1 MB, so 50,000 page views come to roughly 60 GB a month; the same page with unoptimized images reaches 5 MB and multiplies that figure about fivefold. For a real number instead of an estimate, measure for a month with vnStat.

Does unlimited bandwidth really exist?

No; behind every "unlimited" offer is one of two things. Either an unwritten fair-usage cap, after which the port is throttled from 1 Gbps to 100 or even 10 Mbps, or a port that is slow from the start, so the seller simply doesn't meter volume. Before buying, ask two questions plainly: after how much traffic am I limited, and to what speed? Without a clear answer, choose a plan with a specific numeric quota.

What is the difference between domestic and international traffic in Iran?

Domestic traffic starts or ends inside Iran's own network, while international traffic crosses the country's external links. Because international capacity costs providers far more, Iranian servers usually meter the two on separate counters at different rates. For a site whose audience is in Iran, the balance between the two meters can be the biggest cost factor. Often forgotten: content that reaches a user in Iran from a server or CDN edge abroad counts as international.

Why doesn't my own usage figure match my provider's panel?

A gap of 2 to 5 percent is completely normal and has several real causes. You count inside the operating system while the provider counts at the switch port, so bytes from packets your firewall drops are already counted; link-layer overhead adds 2 to 3 percent; vnStat reports in GiB while invoices usually use GB; and the billing period's boundary doesn't follow your server's time zone. A persistent gap above 15 percent is worth a support ticket.

Related tutorials

Ready for some hands-on practice?

A Mehran Host hourly cloud server is ready in under 60 seconds. Practice as much as you like and pay only for the hours you use. Estimate the cost with the pricing calculator before you sign up.