NoLimitNodes
PricingDocsBlogAbout
SupportContact
Log in
Blog/Engineering

Solana VPS vs VDS vs Bare Metal: What Should Your Trading Bot Run On?

CPU steal, network jitter, and disk I/O spikes kill trading bots silently. Measure all three on your current host and find out which server tier actually fixes them.

N
NoLimitNodes Engineering
Infrastructure Team
Jul 21, 202610 min read
On this page +
  • 01Three Things That Kill Trading Bots Before the Strategy Does
  • 02Bottleneck 1: CPU Steal
  • 03Bottleneck 2: Network Jitter
  • 04Bottleneck 3: Memory Contention and Disk I/O
  • 05Which Bot Type Belongs on Which Tier
  • 06Run This Before You Upgrade
  • 07Frequently Asked Questions

Your bot missed a fill. Same strategy. Same signal. The trade that worked in backtests executed 43ms late in production. No exception. No error log. The CPU was stolen mid-execution by another tenant on the same physical host.

Infrastructure failure is silent. It doesn't throw errors. It shows up as bad fills, missed exits, and strategies that look broken when they aren't.

Three things cause it. None of them are your code.

Three Things That Kill Trading Bots Before the Strategy Does#

Most server comparisons stop at specs: vCPUs, RAM, storage. None of those numbers tell you what you actually need to know: how much of your execution time the host is stealing back.

Three failure modes show up consistently across trading bot deployments:

CPU steal. Your VM's CPU cycles handed to another tenant's workload mid-execution. Your process is runnable. It just can't run. The hypervisor scheduled someone else first.

Network jitter. P99 latency spikes that don't appear in the average but fire at the worst possible moment. A 1ms median hides a 40ms spike. That spike is when your order goes out late.

Memory contention and disk I/O. Shared storage pressure from neighbouring tenants slowing your checkpoint writes, trade logs, and local RPC cache. Sub-millisecond operations become 8ms operations under host load.

Each section below diagnoses one of these. It tells you how to measure it, what numbers to expect per tier, and which tier actually fixes it. At the end, there's a single Python script that runs all three checks and prints a verdict.

01Bottleneck 1: CPU Steal#

The Linux kernel tracks CPU steal time in /proc/stat. It's the eighth field in the first line: the percentage of wall-clock time your process was runnable but couldn't execute because the hypervisor gave those cycles to another VM.

On a shared VPS, dozens of tenants sit on the same physical host. When a neighbour's database kicks off a vacuum, a backup job runs, or another trading bot hits peak load, the hypervisor rebalances. Your cycles go to them. Your execution stalls. The kernel records it as stolen time, not as an error.

A 20ms steal event doesn't look like anything in your application logs. Your bot just fires 20ms later than it was supposed to.

cpu_steal.py
python
import time

def read_cpu_stats():
    with open("/proc/stat") as f:
        fields = f.readline().split()
    # Fields: user, nice, system, idle, iowait, irq, softirq, steal
    return {
        "total": sum(int(x) for x in fields[1:]),
        "steal": int(fields[8]),
    }

def measure_cpu_steal(duration_seconds: int = 5) -> float:
    start = read_cpu_stats()
    time.sleep(duration_seconds)
    end = read_cpu_stats()
    total_delta = end["total"] - start["total"]
    steal_delta = end["steal"] - start["steal"]
    if total_delta == 0:
        return 0.0
    return (steal_delta / total_delta) * 100

steal_pct = measure_cpu_steal(5)
print(f"CPU steal: {steal_pct:.1f}%")
if steal_pct > 10:
    print("HIGH: unpredictable execution timing. Move to VDS or Bare Metal.")
elif steal_pct > 3:
    print("MODERATE: acceptable for low-frequency strategies. Borderline for arb.")
else:
    print("LOW: steal is not your bottleneck.")

Run this on your current host. The number it prints is your baseline.

What each tier gives you:

VPS. CPU steal commonly spikes 15–25% on busy hosts. Five-minute averages look fine. The individual events that matter, like a 200ms steal mid-order, don't show in the average. They show in your fills.

VDS. Reserved vCPUs reduce steal to 1–8%. Neighbours still exist but the hypervisor enforces CPU limits on their VMs. Steal events still happen; they're just smaller and less frequent.

Bare Metal. Zero CPU steal. No hypervisor. Your process owns the cores. There's nothing to schedule around.

02Bottleneck 2: Network Jitter#

A 1ms average ping to your RPC endpoint sounds fast. It's not the number that matters.

P99 is.

If your bot sends 100 orders and 99 go out in 1ms but one takes 45ms because a burst of tenant traffic hit the shared NIC buffer, you lost that trade. The average stays at 1.4ms. The logs show no anomaly. The fill came back wrong.

Shared VPS network interfaces are virtualised and multiplexed across tenants. Every VM on the host uses the same physical NIC through a virtual switch. When someone else's process sends a burst of traffic, a database replication sync, a video transcode upload, another bot's stream, the switch queues your packets behind theirs. That queue shows up as jitter.

jitter.py
python
import socket
import time
import statistics

def measure_jitter(host: str, port: int, samples: int = 200) -> dict:
    rtts = []
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(2.0)
    sock.connect((host, port))
    for _ in range(samples):
        start = time.perf_counter()
        sock.send(b"\x00")
        try:
            sock.recv(1)
        except Exception:
            pass
        rtts.append((time.perf_counter() - start) * 1000)
        time.sleep(0.01)
    sock.close()
    sorted_rtts = sorted(rtts)
    p50 = sorted_rtts[len(sorted_rtts) // 2]
    p99 = sorted_rtts[int(len(sorted_rtts) * 0.99)]
    return {"p50_ms": round(p50, 2), "p99_ms": round(p99, 2), "ratio": round(p99 / p50, 1)}

# Replace with your RPC endpoint host and port
result = measure_jitter("your-rpc-host.example.com", 8899)
print(f"P50: {result['p50_ms']}ms  P99: {result['p99_ms']}ms  Ratio: {result['ratio']}x")
if result["ratio"] > 10:
    print("HIGH jitter: shared NIC causing spikes. Upgrade to VDS or Bare Metal.")
elif result["ratio"] > 4:
    print("MODERATE jitter: acceptable for slow strategies, not for arb.")
else:
    print("LOW jitter: network is not your bottleneck.")

The ratio is the signal. P99/P50 above 10 means your worst-case latency is an order of magnitude above your typical latency. That gap is where bots lose trades.

What each tier gives you:

VPS. P99 commonly runs 10–40× P50 under host load. The virtual switch has no per-tenant isolation. Your traffic competes equally with everyone else's.

VDS. A dedicated virtual NIC removes the shared switch bottleneck. Jitter ratio typically drops to 2–5×. Still virtualised, but isolated enough for most DEX arb strategies.

Bare Metal. Physical NIC, no virtual switch, no queue contention. P99 typically within 2× P50. The remaining jitter comes from the network path itself, not from the host.

03Bottleneck 3: Memory Contention and Disk I/O#

Trading bots touch disk more than most people expect. Checkpoint files. Trade logs. Position state. Local RPC cache. On VPS, storage is a shared pool. Every VM on the host writes to the same underlying storage controller. When a neighbour's process hammers I/O, a database flush, a backup snapshot, log rotation, your writes queue behind it.

A 4KB write that normally takes 0.1ms can take 8ms when the storage controller is saturated. If your bot writes a checkpoint after each order, that 8ms shows up directly in your decision loop.

disk_latency.py
python
import time
import os
import tempfile

def measure_disk_latency(iterations: int = 1000) -> dict:
    latencies = []
    tmp = tempfile.NamedTemporaryFile(delete=False, mode='wb')
    tmp.close()
    for _ in range(iterations):
        start = time.perf_counter()
        with open(tmp.name, 'wb') as f:
            f.write(b'\x00' * 4096)
            f.flush()
            os.fsync(f.fileno())
        latencies.append((time.perf_counter() - start) * 1000)
    os.unlink(tmp.name)
    sorted_l = sorted(latencies)
    return {
        "p50_ms": round(sorted_l[len(sorted_l) // 2], 2),
        "p99_ms": round(sorted_l[int(len(sorted_l) * 0.99)], 2),
    }

result = measure_disk_latency()
print(f"Disk write P50: {result['p50_ms']}ms  P99: {result['p99_ms']}ms")
if result["p99_ms"] > 5:
    print("HIGH: shared storage causing spikes. Move to Bare Metal NVMe.")
elif result["p99_ms"] > 2:
    print("MODERATE: acceptable if the bot does not write on the critical path.")
else:
    print("LOW: disk is not your bottleneck.")

The fsync call matters. Without it, writes land in the OS page cache but aren't committed to storage. The benchmark measures real durability latency, which is the same thing your bot sees when it writes state it can't afford to lose.

What each tier gives you:

VPS. Shared SAN or distributed storage pool. P99 write latency can spike 5–20ms when the controller is under load from other tenants. Median looks fine. Tail is the problem.

VDS. Semi-dedicated storage allocation. More predictable. P99 typically 1–5ms. Still shared infrastructure underneath, but with better isolation than VPS.

Bare Metal. Dedicated NVMe. Sub-millisecond P99 at 4KB write size. No shared controller. No tenant neighbours. The disk is yours.

Which Bot Type Belongs on Which Tier#

The three bottlenecks map directly to three categories of bot. The question isn't which tier is best. It's which tier your bot's latency tolerance requires.

Bot typeLatency toleranceBottleneck sensitivityRight tier
Macro, swing, long-only>100msLowVPS
DEX arb, moderate frequency20–100msMediumVDS
MEV, liquidators, snipers<20msHighBare Metal

VPS: for bots that don't compete on execution speed. If your bot fires once per minute or once per block and isn't racing against other bots, infrastructure noise doesn't change the outcome. A 20ms steal event on a VPS won't cost you a trade when your timing tolerance is 100ms+. The cost savings are real. Use them.

VDS: for bots that need reserved resources without bare metal pricing. DEX arb bots compete with other arb bots. The goal is to outrun the slowest competitor in your market, not to achieve physical latency limits. A VDS with reserved vCPUs keeps steal below 5%. A dedicated virtual NIC brings jitter ratio under 4×. That's enough to compete in most DEX arb environments. Not enough for MEV.

Bare Metal: for bots where a single stolen millisecond changes the outcome. MEV bots, liquidators, and sniper bots compete at the transaction level. A steal event means your transaction lands in a later block. A jitter spike means a competitor's order arrives at the validator first. There's no strategy adjustment that compensates for infrastructure noise at that resolution. Bare metal is the only option that removes all three bottlenecks.

NLN VPS covers macro and moderate-frequency strategies. NLN VDS is the right tier for DEX arb and strategies that need reserved resources without bare metal cost. For latency-critical bots (MEV, snipers, liquidators) NLN Bare Metal removes all three bottlenecks at the hardware level.

Latency waveform comparison across VPS, VDS, and Bare Metal showing signal noise and jitter spikes per tier

Run This Before You Upgrade#

Before you pay for an upgrade, run this. It takes under 60 seconds. It measures all three bottlenecks and prints a plain-English verdict. Don't guess which one is hurting you. Measure it.

benchmark.py
python
import time, os, socket, statistics, tempfile

def read_cpu_stats():
    with open("/proc/stat") as f:
        fields = f.readline().split()
    return {"total": sum(int(x) for x in fields[1:]), "steal": int(fields[8])}

def cpu_steal(seconds=5):
    a = read_cpu_stats()
    time.sleep(seconds)
    b = read_cpu_stats()
    d = b["total"] - a["total"]
    return 0.0 if d == 0 else (b["steal"] - a["steal"]) / d * 100

def disk_p99(iterations=500):
    times = []
    tmp = tempfile.NamedTemporaryFile(delete=False, mode='wb')
    tmp.close()
    for _ in range(iterations):
        t = time.perf_counter()
        with open(tmp.name, 'wb') as f:
            f.write(b'\x00' * 4096)
            f.flush()
            os.fsync(f.fileno())
        times.append((time.perf_counter() - t) * 1000)
    os.unlink(tmp.name)
    return sorted(times)[int(len(times) * 0.99)]

def jitter_ratio(host, port, samples=100):
    rtts = []
    s = socket.socket()
    s.settimeout(2)
    s.connect((host, port))
    for _ in range(samples):
        t = time.perf_counter()
        s.send(b"\x00")
        try:
            s.recv(1)
        except Exception:
            pass
        rtts.append((time.perf_counter() - t) * 1000)
        time.sleep(0.01)
    s.close()
    sr = sorted(rtts)
    p50 = sr[len(sr) // 2]
    p99 = sr[int(len(sr) * 0.99)]
    return p99 / p50 if p50 > 0 else 0

RPC_HOST = "your-rpc-host.example.com"
RPC_PORT = 8899

print("Running benchmark, takes ~60s...\n")
steal  = cpu_steal(5)
disk   = disk_p99(500)
jitter = jitter_ratio(RPC_HOST, RPC_PORT, 100)

print(f"CPU steal:    {steal:.1f}%")
print(f"Disk P99:     {disk:.1f}ms")
print(f"Jitter ratio: {jitter:.1f}x (P99/P50)\n")

issues = []
if steal  > 10: issues.append("CPU steal HIGH: move to VDS or Bare Metal")
if disk   > 5:  issues.append("Disk P99 HIGH: move to Bare Metal NVMe")
if jitter > 10: issues.append("Jitter HIGH: dedicated NIC needed, VDS or Bare Metal")

if not issues:
    print("Verdict: no infrastructure bottleneck detected. Check your strategy logic.")
else:
    for i in issues:
        print(f"  ! {i}")
    if steal > 10 and jitter > 10:
        print("\nVerdict: move to Bare Metal.")
    else:
        print("\nVerdict: move to VDS minimum.")

Run this on your current host. Then run it again after you move. If steal is under 3% and jitter ratio is under 4×, your infrastructure isn't the problem. If they're not, you know exactly what to fix, and which tier fixes it.

Server location matters here too. Solana's largest validator clusters run in Frankfurt. If your bot is in a different region, network hops add latency that no server tier eliminates. The benchmark measures host-level noise. Geographic latency is separate.

CPU steal gauge dials for VPS, VDS, and Bare Metal showing characteristic steal percentages per tier

Frequently Asked Questions#

What is CPU steal and why does it affect trading bots?

CPU steal is the percentage of time your VM's CPU was runnable but couldn't execute because the hypervisor gave those cycles to another tenant. For a trading bot, a steal event mid-execution means your order goes out late. The bot doesn't error. It just fires slower than it should. On shared VPS hosts, steal events are random and unscheduled.

What's the difference between VPS and VDS for a trading bot?

A VPS shares physical CPU cores with other tenants. A VDS gives you reserved vCPUs that aren't shared with anyone. CPU steal on a VDS is much lower, typically 1–8% versus 15–25% on a busy VPS. VDS usually comes with a dedicated virtual NIC too, which reduces network jitter. Both are still virtualised, so bare metal is the only option for zero steal.

When is bare metal overkill for a Solana bot?

If your bot fires once per block or less, or it doesn't compete with other bots on execution speed, bare metal is overkill. Macro strategies, long-only bots, and any strategy with latency tolerance above 100ms run fine on VPS. The cost savings are real. Bare metal is for MEV, liquidators, and sniper bots where a 20ms steal event changes the outcome.

How do I measure network jitter on my server?

Send 200 probes to your RPC endpoint and record each round-trip time. Compute P50 and P99. If P99 is more than 10× P50, you have a jitter problem. A 1ms median with 40ms P99 is worse for a bot than a 3ms median with 5ms P99. The benchmark script in this article does this automatically and prints a verdict.

What latency does a DEX arb bot actually need?

DEX arb bots compete with other arb bots, not with validators. The practical threshold is 20–100ms end-to-end from signal to fill confirmation. A VDS with reserved vCPUs and a dedicated NIC brings CPU steal below 5% and jitter ratio below 4×, enough to compete at this level. Below 20ms, you're in bare metal territory.

Does server location matter for Solana trading bots?

Yes. Solana's largest validator clusters are in Frankfurt, New York, and Tokyo. Hosting your bot in Frankfurt puts it physically close to a large share of the validator set, reducing hops between your order and the validators processing it. Co-location doesn't help if your server has high CPU steal, but it's a real edge when everything else is equal.

How do I run the benchmark from this article?

Copy the combined benchmark script, replace RPC_HOST and RPC_PORT with your actual endpoint, and run it with Python 3. It takes about 60 seconds. It measures CPU steal, disk write P99, and network jitter ratio, then prints a verdict telling you whether to stay on your current tier or upgrade.

How does VDS compare to Bare Metal on cost vs performance?

VDS costs significantly less than bare metal and eliminates CPU steal almost entirely. For most DEX arb and moderate-frequency strategies, VDS is the right call. The remaining gap, no hypervisor overhead, dedicated physical NIC, NVMe latency, only matters when your bot competes at millisecond resolution. If you're not sure which side of that line you're on, run the benchmark first.

Can a shared VPS ever work for MEV?

No. MEV bots compete at the transaction level. A single CPU steal event of 20ms means your transaction lands in a later block than a competitor's. There's no strategy adjustment that fixes infrastructure noise at that level. MEV requires bare metal.

How much throughput does a Yellowstone gRPC stream need?

A Yellowstone gRPC stream subscribed to all transactions runs at roughly 200–400 Mbps sustained. Filtered streams, meaning specific accounts or programs, are much lower, typically 10–50 Mbps. VPS plans usually come with 1 Gbps shared uplinks, sufficient for filtered streams. Bare metal with a dedicated NIC gives you more headroom for burst events.

What does a dedicated NIC mean in practice?

A dedicated NIC means your VM has exclusive access to a physical network interface rather than sharing one with other tenants. Shared NICs are managed by a virtual switch that queues competing traffic. A dedicated NIC removes that queue. VDS plans typically include dedicated virtual NICs. Bare metal has a physical NIC with no virtualisation layer at all.

How often should I re-run the benchmark after upgrading?

Run it immediately after moving to confirm the upgrade fixed the issue. Then run it monthly, or after any significant change to your bot's workload. CPU steal can increase on VPS hosts as the provider adds more tenants. A benchmark that passed three months ago might not pass today.

///

If you've run the benchmark and know which tier you need, NLN Bare Metal is the option for latency-critical bots: owned Frankfurt infrastructure, no virtualisation layer, dedicated NVMe. For DEX arb and moderate-frequency strategies, NLN VDS gives you reserved resources without bare metal pricing.

#solana#vps#vds#bare-metal#trading-bot#cpu-steal#network-jitter#mev#dex-arb#solana-infrastructure#yellowstone-grpc#server-benchmark
N
NoLimitNodes Engineering
Infrastructure Team

The team that runs our RPC, WebSocket, gRPC, and streaming fleet. We write about what we operate: validators, Geyser pipelines, and the request paths in between.

On this page
  • 01Three Things That Kill Trading Bots Before the Strategy Does
  • 02Bottleneck 1: CPU Steal
  • 03Bottleneck 2: Network Jitter
  • 04Bottleneck 3: Memory Contention and Disk I/O
  • 05Which Bot Type Belongs on Which Tier
  • 06Run This Before You Upgrade
  • 07Frequently Asked Questions
↑ back to top
///Read next
EngineeringJul 24, 2026

Solana Indexer Architecture: Backfill History and Switch to Live gRPC Without Missing Slots

Build a Solana indexer that backfills history and switches to live gRPC without missing slots. Covers the no-gap, no-duplicate, and monotonic invariants with full Python implementation.

#solana#indexer#backfill
11 min read
EngineeringJul 17, 2026

How to Monitor SOL and SPL Token Transfers for Thousands of Wallets

Track SOL and SPL transfers for thousands of Solana wallets. Covers the ATA gap, Yellowstone gRPC account filters, scale tiers to 10,000+, and fan-out delivery for multi-user trackers.

#solana#wallet-monitoring#spl-tokens
10 min read
← Older
How to Monitor SOL and SPL Token Transfers for Thousands of Wallets
Newer →
Solana Indexer Architecture: Backfill History and Switch to Live gRPC Without Missing Slots
Run it yourself

Every benchmark in this blog runs against our public endpoints.

Spin up an RPC, WebSocket, or gRPC endpoint in under a minute. Flat pricing, no request caps. Reproduce the numbers for your own workload.

See pricing

Ready to get started?

Choose a plan and start building in under 30 seconds.

Talk to Sales
NoLimitNodes

Solana RPC infrastructure built for performance and scale.

RPC Access
  • HTTP RPC
  • WebSocket
  • gRPC
  • Shredstream
Infrastructure
  • Compute Platform
  • VPS
  • VDS
  • Bare Metal
  • Geyser Plugin Hosting
Enhanced Streams
  • PumpFun
  • PumpSwap
  • Raydium
  • Orca
  • Meteora
  • System Events
  • Browse All →
Program Streams
  • PumpFun
  • PumpSwap
  • Raydium CLMM
  • Orca Whirlpool
  • Meteora DLMM
  • Jupiter Swap
  • Jupiter Perps
  • Kamino Lending
  • Browse All 37 →
Trading
  • EZWallet
Analytics
  • Historical Datasets
  • Historical Raw Blocks
Company
  • About
Resources
  • Pricing
  • Custom Development
  • Documentation
  • Blog
  • System Status
  • Support
  • Contact Sales
Compare
  • Yellowstone gRPC vs LaserStream
  • Triton vs Helius
  • Raydium API vs Helius
  • PumpSwap API vs Bitquery
  • QuickNode Streams vs NLN
  • All comparisons →
Legal
  • Terms & Conditions
  • Privacy Policy
© 2026 CLR3 Inc., operating as NoLimitNodes. Registered in Ontario, Canada. All rights reserved.solana mainnet