Most load test results you see online are from localhost benchmarks or over-provisioned cloud instances. We wanted to know what a real .NET 10 API could handle on the kind of server a bootstrapped SaaS product would actually run on — a €20/month Hetzner CX22 (2 vCPU, 4GB RAM).
The setup
We used the Shopilent e-commerce backend as the test subject — it's open source and representative of a real production workload: PostgreSQL, EF Core, JWT auth, multi-tenancy, background jobs. The load was generated from a separate Hetzner instance in the same datacenter to eliminate network latency as a variable.
k6 load test configuration
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 100 }, // ramp up
{ duration: '5m', target: 400 }, // sustained load
{ duration: '2m', target: 600 }, // push to find ceiling
{ duration: '1m', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};What we optimised
- —Response caching on read-heavy endpoints with L1/L2 cache (in-memory + Redis)
- —EF Core query optimisation — compiled queries, split queries for N+1 elimination
- —Connection pool tuning — Npgsql default pool size increased to match concurrency
- —Background job queue kept isolated from the API process
The result
458 req/s sustained, p95 latency under 180ms, error rate 0.0%. The ceiling was memory — at 600 req/s the process started swapping. The fix would be more RAM, not more code.
For context: 458 req/s is roughly 1.6 million requests per hour. Most SaaS products never get close to that on a €20/month server. The point isn't that you'll need this throughput — it's that the foundation won't be your bottleneck when you do.