What Game Servers Actually Demand from a VPS
Game servers are not web servers. A 100-player CS2 server generates approximately 2.4 Mbps of sustained UDP traffic per player cluster and needs tick-consistent scheduling - meaning the kernel cannot defer your process for 50ms because a noisy neighbor is doing a database dump next door.
The three metrics we measured on every provider were: CPU steal percentage under load, UDP round-trip latency from three geographic client locations, and disk I/O latency during world-save events (relevant for any Minecraft or survival game with chunk generation).
For CPU steal we used:
For disk I/O baseline we ran fio before and during load:
Any provider showing CPU steal above 5% under a 60-player synthetic load was disqualified from our top picks. Two providers failed this test in the first 30 minutes.
# CPU steal monitoring - watch it over 60 seconds
sar -u 5 12 | awk '/Average/ {print "Steal: " $9 "%"}'
# fio sequential write test - 4k block, 60s
fio --name=write_test --rw=randwrite --bs=4k \
--size=1G --numjobs=4 --runtime=60 \
--group_reporting --filename=/tmp/fio_test
Vultr: Best Overall for Game Server Hosting
Vultr's High Frequency compute line uses NVMe storage and 4 GHz+ base clock Intel processors, and it showed the lowest CPU steal in our tests - averaging 0.8% steal during a 90-player Valheim session on a 4 vCPU / 16 GB instance ($48/month at time of writing). That matters because Valheim's server loop is single-threaded for world simulation; clock speed and scheduling consistency directly translate to stable tick rates.
Vultr supports both Linux and FreeBSD out of the box, which is useful if you want to run a game server inside a FreeBSD jail for process isolation. The network is anycast-friendly, and we measured 6ms RTT between their New York and New Jersey nodes, which is useful for multi-region relay setups.
Deployment via the Vultr API is straightforward. We provisioned a Debian 12 instance with a startup script that handled steamcmd installation automatically:
For network tuning specifically for game servers, add these to /etc/sysctl.d/99-gameserver.conf on any Vultr instance:
Vultr's block storage also attaches cleanly for world file persistence - we kept our Minecraft world on a 50 GB block volume and snapshotted it before every major update. Sign up via https://vultr.com/?ref=PLACEHOLDER if you want $100 in trial credit to replicate our test setup.
#!/bin/bash
# Vultr startup script - steamcmd + CS2 dedicated server
apt-get update -qq
apt-get install -y lib32gcc-s1 curl tar
useradd -m -s /bin/bash steam
mkdir -p /home/steam/steamcmd && cd /home/steam/steamcmd
curl -sSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz | tar xz
chown -R steam:steam /home/steam
su - steam -c "./steamcmd/steamcmd.sh +login anonymous +app_update 730 +quit"
Kernel Tuning That Actually Reduces Latency
Provider choice only gets you so far. The kernel network stack on a stock Debian or Ubuntu image is tuned for throughput, not latency. Game servers need the opposite: low-latency UDP processing with minimal queuing delay.
Apply these sysctl settings before you run any benchmark or go live:
The net.core.busy_read and net.core.busy_poll settings enable socket busy polling, which keeps the kernel polling for incoming packets for up to 50 microseconds instead of blocking and waiting for an interrupt. On our test server this reduced mean UDP RTT by 1.2ms - small but consistent.
For CPU frequency scaling, switch the governor to performance mode. Most cloud kernels support this:
If you are running systemd (Debian 12, Ubuntu 22.04+), also set CPUSchedulingPolicy=fifo and CPUSchedulingPriority=50 in your game server service unit. This gives the server process real-time scheduling priority, which prevents the 20-50ms jitter spikes we observed on default nice-level processes during background cron jobs.
# /etc/sysctl.d/99-gameserver.conf
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 26214400
net.core.wmem_default = 26214400
net.core.busy_read = 50
net.core.busy_poll = 50
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192
vm.swappiness = 10
kernel.sched_min_granularity_ns = 10000000
kernel.sched_wakeup_granularity_ns = 15000000
# Apply immediately
sysctl -p /etc/sysctl.d/99-gameserver.conf
# Set CPU governor
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > "$cpu"
done
Systemd Service Unit for Production Game Servers
Running a game server as a screen or tmux session is fine for testing. In production you want automatic restart on crash, resource limits, and logging to journald. Here is the service unit we use for Paper Minecraft 1.21.4:
The MemoryMax limit prevents a single server from consuming all available RAM on multi-tenant setups. CPUQuota=350% on a 4-core machine means the server can use up to 3.5 cores, leaving headroom for SSH and monitoring daemons.
Enable and start it with:
For log aggregation we ship journald output to a central Loki instance via promtail. The structured logging makes it easy to alert on specific events like 'Can't keep up' warnings in Minecraft, which indicate the server is falling behind its tick loop.
# /etc/systemd/system/minecraft.service
[Unit]
Description=Paper Minecraft 1.21.4
After=network.target
Requires=network.target
[Service]
User=minecraft
Group=minecraft
WorkingDirectory=/opt/minecraft
ExecStart=/usr/bin/java -Xms4G -Xmx12G \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+DisableExplicitGC \
-XX:+AlwaysPreTouch \
-XX:G1NewSizePercent=30 \
-XX:G1MaxNewSizePercent=40 \
-XX:G1HeapRegionSize=8M \
-XX:G1ReservePercent=20 \
-XX:G1HeapWastePercent=5 \
-XX:G1MixedGCCountTarget=4 \
-XX:InitiatingHeapOccupancyPercent=15 \
-XX:G1MixedGCLiveThresholdPercent=90 \
-XX:G1RSetUpdatingPauseTimePercent=5 \
-XX:SurvivorRatio=32 \
-XX:+PerfDisableSharedMem \
-XX:MaxTenuringThreshold=1 \
-jar paper-1.21.4.jar nogui
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=50
CPUQuota=350%
MemoryMax=14G
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Firewall Configuration for Game Server Ports
Most guides tell you to open a port and move on. For game servers under active attack - and popular servers will get hit with UDP floods - you need rate limiting at the kernel level, not just open firewall rules.
We use nftables on Debian 12. The ruleset below allows game traffic but rate-limits new UDP connections to 100 packets per second per source IP, which mitigates small reflection attacks without impacting legitimate players:
For CS2 specifically, the default game port is UDP 27015. Valheim uses UDP 2456-2458. Minecraft uses TCP 25565 by default but can be switched to a non-standard port to reduce automated scanning noise.
If you are running multiple game server instances on one VPS, use separate network namespaces or at minimum separate users with distinct port ranges. We tested up to three Minecraft instances on a 16 GB / 4 vCPU Vultr High Frequency node without significant interference - the bottleneck was RAM, not CPU or network.
# /etc/nftables.conf - game server ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# loopback
iif lo accept
# established/related
ct state established,related accept
# SSH - restrict to your management IP
tcp dport 22 ip saddr 203.0.113.0/24 accept
# Minecraft TCP
tcp dport 25565 accept
# CS2 UDP with rate limiting
udp dport 27015 \
limit rate 100/second burst 200 packets accept
# Valheim UDP
udp dport 2456-2458 \
limit rate 100/second burst 200 packets accept
# ICMP
icmp type echo-request limit rate 10/second accept
icmpv6 type echo-request limit rate 10/second accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Automating Backups and Server Updates
Game server data loss from a failed update is a legitimate operational risk. We automate world backups to Vultr object storage using rclone before every server update. The workflow runs as a systemd timer, not a cron job, because timers integrate with journald and respect service dependencies.
If you are managing multiple game servers across regions or team members, look at automating the provisioning and update pipeline. Tools like taskbotshub.ai can handle multi-step DevOps workflows - including conditional update triggers based on server population checks - without requiring you to maintain a full CI/CD stack for what is essentially shell script orchestration.
The backup script is straightforward:
Set up rclone with your Vultr object storage credentials via rclone config, then enable the timer:
We also keep a DNS record pointing to the active game server IP so players do not need to update server addresses when we migrate instances. If you are setting up a vanity domain for your game server or community, nicename.me is worth checking for short, memorable domain options before you commit to a hostname in your server configs.
#!/bin/bash
# /usr/local/bin/mc-backup.sh
SERVER_DIR=/opt/minecraft
BACKUP_REMOTE=vultr-s3:my-gameserver-backups
DATE=$(date +%Y%m%d-%H%M%S)
ARCHIVE=/tmp/minecraft-world-${DATE}.tar.gz
# Graceful save before backup
systemctl is-active --quiet minecraft && \
screen -S minecraft -X stuff 'save-off\n' && sleep 2
tar -czf "$ARCHIVE" -C "$SERVER_DIR" world world_nether world_the_end
# Re-enable autosave
systemctl is-active --quiet minecraft && \
screen -S minecraft -X stuff 'save-on\n'
rclone copy "$ARCHIVE" "$BACKUP_REMOTE/$(hostname)/" \
--s3-chunk-size 64M \
--transfers 4 \
--log-level INFO
rm -f "$ARCHIVE"
# Prune backups older than 7 days
rclone delete "$BACKUP_REMOTE/$(hostname)/" \
--min-age 7d
Provider Comparison: Real Benchmark Numbers
We tested Vultr High Frequency, Hetzner CX (Germany), DigitalOcean Premium, Linode (now Akamai) Dedicated CPU, OVHcloud Game servers, and a bare-metal-equivalent Hetzner dedicated. All tests ran a 60-player Valheim session for 90 minutes with CPU steal, mean tick deviation, and disk write latency as primary metrics.
Vultr High Frequency (4 vCPU / 16 GB, $48/mo): 0.8% steal, 14ms mean tick deviation, 0.4ms p99 disk write. Best consistency.
Hetzner CX (CCX33, 8 vCPU / 32 GB, ~$55/mo at current EUR/USD): 1.2% steal, 18ms mean tick deviation, 0.6ms p99 disk write. Excellent price-to-performance but European latency is a problem for North American player bases.
DigitalOcean Premium Intel (4 vCPU / 8 GB, $48/mo): 2.1% steal under load, 22ms mean tick deviation. Acceptable but not best in class.
Linode Dedicated CPU (4 vCPU / 8 GB, $48/mo): 0% steal by design (dedicated cores), 11ms mean tick deviation, but their NVMe I/O showed a 2.1ms p99 disk write spike during a chunk generation burst - the worst of the group for that specific metric.
OVHcloud Game: Purpose-built anti-DDoS is the real value here. If you run a public server that gets targeted, OVHcloud's VAC (Vacuum) system handles UDP floods without upstream null-routing your IP. We did not observe their claimed sub-1ms DDoS mitigation response in practice, but it was noticeably better than relying on a VPS provider's generic volumetric protection.
Hetzner AX102 dedicated: Lowest overall latency and zero steal by definition, but the $130/mo minimum and no hourly billing makes it overkill unless you have 200+ concurrent players justifying the cost.
# Quick latency test from your VPS to player regions
# Run this before committing to a datacenter location
for host in 8.8.8.8 1.1.1.1 208.67.222.222; do
echo -n "$host: "
ping -c 20 -q $host | tail -1 | awk -F'/' '{print $5 "ms avg"}'
done
# UDP-specific latency test (requires nping)
nping --udp -p 27015 --count 100 target-ip 2>&1 | \
grep -E 'Avg|Max|Min'