Hey everyone,
For the past few months, there has been a massive push in the indie hacking and self-hosted communities toward simplifying the database stack. Instead of spinning up an external managed PostgreSQL instance that costs $30 to $80 per month and adds 20-40ms of network hop latency on every single query, I decided to benchmark a single-binary PocketBase + SQLite setup under heavy load on a cheap $4/mo Hetzner CX22 instance (2 vCPU, 4GB RAM, Ubuntu 24.04).
Here is the exact setup, the benchmarks, and the production configs to run this safely with continuous disaster recovery.
The Concurrency Reality: Reads vs Writes in SaaS
The number one objection developers have to SQLite is that it locks the database on writes.
While true, SQLite in Write-Ahead Logging (WAL) mode allows concurrent readers while a write is occurring. Because typical SaaS products exhibit a 95:5 read-to-write ratio, tuning SQLite PRAGMAs handles immense concurrency without breaking a sweat:
PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000; PRAGMA cache_size = -64000; -- 64MB dedicated RAM page cache PRAGMA mmap_size = 268435456; -- 256MB memory-mapped file I/O PRAGMA temp_store = MEMORY;
With synchronous = NORMAL and busy_timeout = 5000, writer threads wait up to 5 seconds during transactions rather than throwing immediate SQLITE_BUSY errors.
Benchmarking Under Load (k6)
Running a 200 concurrent user load test hitting authenticated read endpoints:
status is 200: 100% latency < 10ms: 100%
http_req_duration: avg=2.84ms med=2.1ms p(95)=5.2ms p(99)=7.8ms http_reqs: 170,400 (1,420 rps)
P99 latency was under 8 milliseconds. Because SQLite is embedded inside the Go binary via in-process memory mapping (mmap), there is zero TCP serialization or network round-trip overhead.
Solving the Disaster Recovery Problem: Litestream + Cloudflare R2
The second objection is: What happens if the VPS drive physically dies?
We use Litestream to stream every SQLite WAL frame in real-time to Cloudflare R2 (which has zero egress fees).
Here is the /etc/litestream.yml config:
dbs:
With this, your Recovery Point Objective (RPO) is under 1 second. If the server vanishes, restoring on a brand new box takes 1 command: litestream restore -config /etc/litestream.yml /var/www/pocketbase/pb_data/data.db
Edge Proxying & Automated SSL with Caddy
Put Caddy in front of PocketBase on 127.0.0.1:8090 to handle automatic certificates, HTTP/3, and real-time SSE buffer flushing:
api.yourdomain.com { encode zstd gzip header { Strict-Transport-Security max-age=31536000; includeSubDomains; preload X-Content-Type-Options nosniff X-Frame-Options DENY } reverse_proxy 127.0.0.1:8090 { header_up Host {host} header_up X-Real-IP {remote_host} flush_interval -1 } }
Cost Breakdown:
Hetzner CX22 VPS: $4.15/month
Cloudflare R2 Storage (Backups): $0.00/month (Free tier covers 10GB storage + $0 egress)
Caddy SSL: $0.00/month
Total: $4.15/month (vs $50+/month for managed Postgres + serverless compute).
Hope this breakdown helps anyone looking to escape managed database pricing.
Drop any questions below about WAL concurrency, schema migrations, or backup restoration tests—happy to share our benchmark scripts or systemd service templates!