The provisioning choice between bare metal and VPS matters less than what you do after the server boots. I've seen teams spend weeks debating hardware specs while leaving default kernel parameters and stock network configs untouched. That's where real ROI lives.
This isn't about baseline comparisons. You already know bare metal gives you dedicated resources and VPS offers flexible scaling. What follows are eight optimization paths that change the economics and performance profile of both deployment types once you move past the sales brochure.
Path 1: NUMA topology awareness
Modern multi-socket systems and some high-core-count VPS instances expose NUMA domains. Default Linux memory allocation is NUMA-aware but application placement often isn't.
Check your topology first:
numactl --hardware
lscpu | grep NUMA
If you see multiple nodes, pin memory-intensive processes to specific NUMA nodes where their memory resides. A database server accessing memory across NUMA boundaries pays a 40-60% latency penalty on every remote access.
For bare metal PostgreSQL:
numactl --cpunodebind=0 --membind=0 postgres -D /var/lib/postgresql/data
VPS platforms sometimes expose NUMA even on fractional CPU allocations. I've caught this on large Hetzner and OVH instances. Your numastat output will show imbalanced memory access patterns if you're hitting cross-node traffic.
Set vm.zone_reclaim_mode=0 in /etc/sysctl.conf to prefer remote memory over local page cache eviction. Sounds backwards but prevents thrashing when a node's memory fills.
Path 2: Interrupt affinity and RPS tuning
Network interrupt handling defaults are terrible. A single CPU handles all interrupts for a NIC queue, creating a bottleneck at 400-600K packets per second. Every support ticket I've handled for "the network is slow but bandwidth is fine" traces back to this.
Identify your NIC's IRQ numbers:
grep eth0 /proc/interrupts
Spread interrupts across cores manually. For a quad-core system with eth0 using IRQs 50-53:
echo 1 > /proc/irq/50/smp_affinity
echo 2 > /proc/irq/51/smp_affinity
echo 4 > /proc/irq/52/smp_affinity
echo 8 > /proc/irq/53/smp_affinity
Those are hexadecimal bitmasks. CPU 0 = 1, CPU 1 = 2, CPU 2 = 4, CPU 3 = 8.
Enable RPS (Receive Packet Steering) to distribute packet processing beyond interrupt handling:
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do
echo f > $f
done
Bare metal gives you full control here. VPS platforms occasionally block /proc/irq writes, but RPS configuration works universally because it's a software queue.
Path 3: Transparent Huge Pages — know when to disable
THP sounds great on paper. Automatic 2MB pages instead of 4KB. Except memory-intensive applications with sparse access patterns take a massive hit from THP defragmentation.
Redis, MongoDB, and most Java workloads perform worse with THP enabled. The kernel's defrag thread (khugepaged) stalls allocations.
Check current setting:
cat /sys/kernel/mm/transparent_hugepage/enabled
If you see [always], you're probably paying a tax. Disable it:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
Make it permanent via /etc/rc.local or a systemd unit. I've measured 15-20% latency improvements on Redis after disabling THP under write-heavy loads.
VPS platforms sometimes lock this setting. If /sys/kernel/mm/transparent_hugepage doesn't exist, you're on an older kernel or a heavily restricted VPS. That's a real constraint.
Path 4: Storage scheduler and queue depth
The default I/O scheduler (mq-deadline on modern kernels) optimizes for rotational disks. Your NVMe or SSD doesn't need that.
Check what's active:
cat /sys/block/nvme0n1/queue/scheduler
Switch to none for NVMe or kyber for SATA SSDs:
echo none > /sys/block/nvme0n1/queue/scheduler
Queue depth matters more than scheduler choice. Default nr_requests is 128, which starves high-IOPS devices.
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
Bare metal gives you direct NVMe access. VPS block devices are often virtio-blk with a hypervisor queue in between. You can still tune the guest-side queue, but you're optimizing one side of a two-layer system.
For VPS, test different noop vs mq-deadline settings under your actual workload. I've seen counterintuitive results where mq-deadline performed better on virtio-blk because the hypervisor's own scheduler expected batched requests.
Path 5: CPU governor and frequency scaling
Servers ship with powersave governor by default. Your CPU idles at minimum frequency until load spikes, then ramps up over 50-100ms. That ramp delay kills request latency.
Check current governor:
cpupower frequency-info
Switch to performance:
cpupower frequency-set -g performance
Disable C-states for the lowest-latency path:
for i in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable; do
echo 1 > $i
done
This costs you 10-15W per CPU at idle. For bare metal, that's real power draw. Calculate whether latency savings justify the electricity cost. At $0.10/kWh, 15W is about $13/year per server.
VPS instances sometimes expose governor controls, sometimes don't. If cpupower commands fail with permission errors, you're locked to the hypervisor's choice. That's a hidden architectural difference that impacts tail latency.
Path 6: Network stack buffer tuning
Default TCP buffers are sized for 1 Gbps links with moderate latency. If you're on 10 Gbps or higher, you're leaving bandwidth on the table.
Check current values:
sysctl net.ipv4.tcp_rmem
sysctl net.ipv4.tcp_wmem
Increase them:
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
Enable TCP window scaling and timestamps if they're off:
sysctl -w net.ipv4.tcp_window_scaling=1
sysctl -w net.ipv4.tcp_timestamps=1
Bare metal and VPS both respond to these settings, but VPS network performance has a ceiling imposed by the hypervisor's virtual NIC throughput. I've seen VPS instances advertised at 10 Gbps that couldn't push more than 4 Gbps in practice due to virtio-net limitations.
Test real throughput with iperf3 against a known-good target before assuming your tuning worked.
Path 7: Filesystem mount options and I/O patterns
Ext4 and XFS defaults prioritize data safety over performance. That's correct for most workloads, but if you're already replicating data or can tolerate a crash losing seconds of writes, you can trade safety for speed.
For ext4 on a database server with WAL-based durability:
mount -o remount,noatime,nodiratime,data=writeback /dev/nvme0n1p1 /var/lib/mysql
data=writeback skips ordered mode. Metadata updates don't wait for data writes. A crash can corrupt files, but your database's own WAL protects against logical corruption.
XFS users should try:
mount -o remount,noatime,nodiratime,logbufs=8,logbsize=256k /dev/nvme0n1p1 /data
Larger log buffers reduce write latency for metadata-heavy operations. I've measured 20-30% improvement on workloads creating thousands of small files per second.
VPS platforms sometimes restrict mount options via virtualized block devices. If you get permission errors remounting, check dmesg for capability restrictions.
Path 8: Real cost modeling with failure domains
The ROI calculation everyone skips: failure domain cost. Bare metal fails at the server level. VPS fails at the hypervisor level, taking down multiple guests.
If you're running three VPS instances for redundancy, check whether your provider isolates them across physical hosts. Most don't by default. I've seen entire customer stacks go dark when a single hypervisor died because all three VPS instances were on the same box.
For bare metal, your failure domain is the server. Calculate replacement cost, not just monthly fees:
- Server fails, you pay shipping and wait 24-48 hours for replacement in most DCs.
- VPS fails, provider migrates you to new hypervisor in 5-15 minutes.
But if your VPS provider has a hypervisor die and loses customer data (happened to OVH in 2021, others before), you don't get the hardware-level redundancy options bare metal gives you: RAID, dual PSUs, IPMI for remote recovery.
Build a real cost model:
Bare metal annual cost = (monthly fee × 12) + (failure_rate × replacement_cost × downtime_hours × hourly_revenue_loss)
VPS annual cost = (monthly fee × 12) + (hypervisor_failure_rate × data_loss_penalty)
Failure rates vary by provider, but typical numbers: bare metal server ~3-5% annual failure, VPS hypervisor ~1-2% annual failure. Your downtime cost and data loss penalty are specific to your business.
In support tickets where teams asked me to justify their infrastructure choice, the ones who'd actually modeled failure costs had made defensible decisions. The ones arguing about CPU benchmarks had not.
Start with observable bottlenecks
Don't tune blindly. Run your workload, watch mpstat, iostat, and sar -n DEV for 24 hours. Identify where your system actually spends time waiting.
If CPU is pegged, look at governors and interrupt affinity. If disk await times are high, check I/O scheduler and queue depth. Network drops in ethtool -S mean you need interrupt tuning.
The ROI of bare metal versus VPS isn't in the hardware spec sheet. It's in whether you extract what the hardware can actually deliver.
