What actually breaks a small FastAPI deployment on a cheap VPS
At 2:13 a.m., the $5 VPS stops answering. SSH hangs, the provider console shows memory near 100%, and dmesg has an OOM-killer line naming uvicorn. The app did not “break” because FastAPI is slow; the box ran out of memory during a deploy, the kernel killed the process, and the reverse proxy returned 502s until you rebooted.
Why the first failure is usually memory
A cheap VPS gives you one small RAM budget shared by Python, your app, the reverse proxy, logs, package installs, and any database on the same host. Each Uvicorn worker loads a full copy of your app [link needed]. Docker builds, pip install, and imports of heavy libraries can spike above steady-state usage.
Check the evidence before changing code:
free -m
dmesg | grep -i -E 'oom|killed process'
journalctl -k | grep -i oom
If the OOM killer is active, reduce workers, move builds off the box, or add swap as a cushion. Swap is not a fix for a leak, but it can keep SSH alive during a deploy. Most fastapi vps deployment problems start here, not in the framework.
fastapi vps deployment problems that show up under concurrency
The next failures are quieter: database connection pools sized for a laptop, synchronous endpoints blocking the event loop, proxy timeouts shorter than a slow query, file descriptors exhausted by keep-alive connections, and /var/log filling the disk.
Check the actual limits:
ulimit -n
df -h
ss -s
systemctl status nginx
Set an explicit DB pool size below the database’s connection limit. Run blocking SDK calls in def endpoints or a thread pool. Set proxy proxy_read_timeout from measured latency, not guesswork. Add log rotation before logs fill the disk. These are fastapi vps deployment problems you can reproduce with a load tool and watch in top, ss, and proxy logs.
A numbered procedure to isolate and fix it
1. Reproduce one failure mode: deploy, a burst of requests, or a slow endpoint. Do one change at a time.
2. Capture free -m, dmesg, df -h, and proxy error logs before touching config.
3. If OOM is present, cut Uvicorn workers to 1–2 and move pip/Docker builds to CI or a larger machine [link needed].
4. Measure endpoint latency with curl -w or a load tool, then set proxy timeouts above the p95 you measured.
5. Set DB pool size and max overflow explicitly; confirm with SHOW PROCESSLIST or equivalent.
6. Configure log rotation and a disk alert.
7. Add a minimal health check that does not hit the database on every probe.
8. Re-run the same reproduction and compare the same metrics.
When NOT to do this
Do not add more workers just because CPU looks busy if each worker loads a large model or DataFrame; you may make OOM worse. Do not add swap on a tiny disk and call it capacity planning. Do not self-host Postgres on the same $5 VPS if you need backups, failover, or predictable IO. Do not tune pool sizes before you know the database’s connection ceiling. And do not chase fastapi vps deployment problems with framework changes if the logs show disk, proxy, or kernel limits.
Pre-deploy checklist
- [ ] OOM check run and no repeating OOM lines
- [ ] Worker count matched to RAM, not CPU cores
- [ ] DB pool size set and documented
- [ ] Proxy timeouts based on measured latency
- [ ] Log rotation enabled
- [ ] Disk and memory alerts configured
- [ ] Deploy builds do not run on the production VPS
- [ ] Health check does not hammer the database
- [ ] Rollback command tested