VPS for trading bots: what actually matters
What a trading bot really needs from a VPS: restart discipline under systemd, a correct clock, safe API keys, heartbeats, and honest latency limits.
What a trading bot needs from a VPS
A VPS for trading bots is judged on four things: does the process come back after it dies, is the clock right, are the API (application programming interface) keys hard to steal, and do you find out when it stops. Raw speed is far down that list for a retail bot, because the slow part of your order path is your broker and the distance to it, not the host running your Python.
This is an engineering guide. Nothing here is financial advice, and no strategy is discussed.
Uptime is restart discipline, not a number on a sales page
Every host on earth advertises 99.9 percent uptime. That figure describes the hypervisor, not your bot. A bot dies from an unhandled exception, a websocket that never reconnects, or the OOM (out of memory) killer, and the server stays up the whole time. So the useful question is what happens in the ten seconds after your process exits.
Run the bot as a systemd service and let the init system own the restart. A unit file does this in six lines.
[Unit]
Description=Trading bot
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=0
[Service]
Type=simple
User=bot
WorkingDirectory=/opt/tradingbot
EnvironmentFile=/etc/tradingbot/api.env
ExecStart=/opt/tradingbot/venv/bin/python -m tradingbot
Restart=always
RestartSec=10
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/tradingbot
[Install]
WantedBy=multi-user.targetStartLimitIntervalSec=0 is the line people miss. By default systemd gives up after 5 restarts in 10 seconds and leaves the unit in failed state forever, which is exactly the behaviour you do not want at 03:00. Setting it to 0 disables the rate limit, so a bot that crash-loops keeps trying instead of going quiet. RestartSec=10 stops that loop from hammering the exchange with reconnects.
Check the file before you trust it, then start it:
sudo systemd-analyze verify /etc/systemd/system/tradingbot.service
sudo systemctl daemon-reload
sudo systemctl enable --now tradingbot
systemctl status tradingbotenable is the half that survives a reboot, and kernel updates mean reboots. To see whether the bot has been quietly dying, ask systemd for the restart counter:
systemctl show tradingbot -p NRestarts
journalctl -u tradingbot --since "24 hours ago" | tail -50NRestarts=0 after a week is a healthy bot. NRestarts=812 means you have been trading on a process that reconnects all night. The full unit file anatomy, including timers for scheduled jobs like a daily report, is covered in running a program as a systemd service.
Set the clock to UTC and prove it is synchronised
Exchange APIs sign requests with a timestamp and reject anything outside a window, often 5 seconds or less. A drifting clock produces errors that look like authentication failures, so people rotate keys for hours before checking the time. On Binance-style APIs the message is literal: Timestamp for this request was 1000ms ahead of the server's time.
Set the server to UTC. Local time zones introduce a daylight saving jump that will land in the middle of a trading session.
sudo timedatectl set-timezone UTC
timedatectlUbuntu ships systemd-timesyncd, which is an SNTP (simple network time protocol) client. It is fine for logs and weak for anything that needs to stay inside a few milliseconds, because it polls one server and does not discipline the clock continuously. Use chrony instead:
sudo apt update && sudo apt install -y chrony
sudo systemctl enable --now chrony
chronyc tracking
chronyc sources -vThe line to read from chronyc tracking is System time, for example System time : 0.000031415 seconds fast of NTP time. Anything under a few milliseconds is healthy. If it reads Leap status : Not synchronised, chrony has not reached a server yet, usually because outbound UDP 123 is blocked. Wait a minute, then check again before touching firewall rules.
Keep API keys out of the places you copy
A leaked exchange key is worse than a leaked SSH key, because withdrawal permission turns it into money instantly. Two habits cover most of the risk.
First, never grant withdrawal permission to a bot key, and where the exchange supports it, bind the key to your server's IP address. This is the single control that makes a stolen key close to useless.
Second, keep the secret out of the code directory. Anything inside /opt/tradingbot ends up in a git repository or a backup archive sooner or later. Put it in a root-owned file that only systemd reads:
sudo install -d -m 750 -o root -g bot /etc/tradingbot
sudo install -m 640 -o root -g bot /dev/null /etc/tradingbot/api.env
sudo nano /etc/tradingbot/api.envThe file holds plain KEY=value lines with no quotes and no export. Mode 640 with group bot means the service user can read it and nobody else can. Verify with sudo -u bot cat /etc/tradingbot/api.env and then with any other user, where it must fail with Permission denied.
The bot itself should not run as root or as your login user. Create a system account with no shell and no home directory to log into:
sudo useradd --system --shell /usr/sbin/nologin --home-dir /var/lib/tradingbot --create-home botThe reasoning behind each of those flags, and how far ProtectSystem=strict actually goes, is in running services as an unprivileged user. The rest of the server baseline, SSH keys and a firewall, belongs in the first ten minutes on a new VPS.
Find out it is down before your broker does
systemctl status says the process is running. It does not say the bot is doing anything. A process stuck in a retry loop against a dead websocket passes every check systemd can make.
Use a heartbeat instead. Uptime Kuma has push monitors: it expects your bot to call a URL on a schedule, and alerts when the call stops arriving. Put the call at the end of your main loop, after the part that proves the bot is alive, such as a successful market data read.
curl -fsS "http://monitor.example.com:3001/api/push/YOUR_TOKEN?status=up&msg=loop_ok"Set the monitor interval to roughly twice your loop time so normal jitter does not page you. Run the monitor on a different server from the bot, because a monitor that dies with the thing it watches reports nothing. Setup is covered in self-hosted status monitoring with Uptime Kuma.
Add a disk alert too. A bot writing verbose logs will fill the root filesystem in weeks, and a full disk stops the database write, not the network call, so the symptoms are strange. journalctl --vacuum-time=14d and a SystemMaxUse= line in /etc/systemd/journald.conf keep the journal bounded.
The honest part: latency is mostly not your host
This is where the market for trading VPS products stops being technical. Marketing pages quote sub-millisecond figures and imply the host is what stands between you and a fill. For almost every retail bot, it is not.
Your order travels from the bot to the exchange or broker endpoint over the public internet. That path is dominated by physical distance and by the peering between your provider and theirs. A server in Frankfurt talking to an endpoint in Tokyo pays roughly 250 milliseconds round trip no matter how fast the CPU is. Then the broker's own systems add their queue, their risk checks and their rate limits, which for a retail account are usually measured in tens or hundreds of milliseconds.
Measure it rather than guessing. curl reports the connection and first-byte times for a real endpoint:
curl -s -o /dev/null -w 'dns=%{time_namelookup}s connect=%{time_connect}s ttfb=%{time_starttransfer}s\n' \
https://api.kraken.com/0/public/Time
mtr -rwzc 100 api.kraken.comRun that from a candidate server before you commit. If connect is 0.180 seconds, you are on the wrong continent, and that is worth fixing. If connect is 0.004 seconds and ttfb is 0.140 seconds, the remaining delay is the broker's processing, and no host change will touch it.
So when does the host matter? When you are colocated or cross-connected to the venue and competing on queue position, which is a different business with a different budget. And when your own code is the bottleneck: a bot that recomputes indicators over a full history every tick can burn 200 milliseconds of CPU per loop, which is real latency you control for free. Profile the loop before you shop for a faster server.
What does matter in the host you pick is geography, stable networking, and enough memory that the OOM killer never gets a vote. As of July 2026, a single-strategy Python bot with a few hundred symbols in memory runs comfortably in 2 GB of RAM and 2 vCPU. Add memory if you keep tick history in a local database.
A short pre-live checklist
systemctl is-enabled tradingbotprintsenabled, and the service survivessudo reboot.chronyc trackingreports a system time offset under a few milliseconds.- The API key has trading permission, no withdrawal permission, and an IP allowlist if the exchange offers one.
- Killing the process with
sudo systemctl kill -s SIGKILL tradingbotbrings it back withinRestartSec. - The heartbeat monitor pages you within one interval when you stop the bot deliberately.
- Logs are bounded and the root filesystem has headroom in
df -h.
Run the whole thing in the exchange's sandbox or in paper mode for a week before real funds. Every item above fails at least once in that week, which is the point of the week.
FAQ
Does a trading bot need a low-latency or bare metal server?
Only if you are competing on execution speed against other automated participants at the same venue, which normally means colocation rather than a general purpose VPS. For a retail bot the round trip is dominated by geography and by the broker's own processing, so pick a server close to the API endpoint and measure with curl and mtr before paying for anything faster.
How much RAM and CPU does a trading bot need?
Most single-strategy bots are network-bound and idle between events. As of July 2026, 2 vCPU and 2 GB of RAM handle a Python bot tracking a few hundred instruments. Memory becomes the constraint when you hold tick history in process or run a local database, so watch free -h and the journal for OOM kill messages rather than guessing.
Why does my exchange API reject requests with a timestamp error?
The server clock has drifted outside the exchange's signing window, usually a few seconds. Install chrony, confirm chronyc tracking shows a small System time offset and a synchronised leap status, and set the machine to UTC so a daylight saving change never shifts it. Rotating the API key does not fix a clock problem.
How do I stop my bot from dying overnight without me knowing?
Run it under systemd with Restart=always and StartLimitIntervalSec=0 so a crash loop keeps retrying instead of stopping permanently, then add a heartbeat that the bot sends at the end of each successful loop. The restart handles the process. The heartbeat catches the case where the process is alive but stuck.
Can I run the bot and my monitoring on the same VPS?
You can, and the monitoring will lie to you the day it matters, because an outage that takes down the bot takes the monitor with it. Keep the alerting on a separate machine, ideally with a different provider or region, and use the bot's server only for the bot and its logs.