Most instance-sizing mistakes come from starting with the price list. Engineers scan the plans, pick something that looks reasonable and discover months later that the server is either idle or swapping. A better approach starts with the workload and ends with the plan. This guide describes that method without relying on any vendor’s benchmark numbers, using Hetzner and UpCloud as examples of how plan families are organized.
Step 1: find the binding resource
Every workload is limited by one resource before the others. A web application with a small database is usually memory-bound. A build server is CPU-bound. A database with many small writes is bound by storage IOPS. A media pipeline is bound by disk throughput or network. Identify which one binds first, because that is the dimension to size generously; everything else can be modest.
- CPU-bound: compilers, video encoding, request-heavy APIs with little state
- Memory-bound: caches, in-memory databases, JVM services, most web stacks
- IOPS-bound: relational databases, message queues, search indexes
- Throughput-bound: backups, log shipping, large file transfers
Step 2: choose shared or dedicated CPU
Providers split virtual machine families by whether CPU time is shared with neighbors or reserved. Hetzner offers shared-resource and dedicated-resource cloud instances plus ARM-based options. UpCloud organizes its Cloud Servers into Starter, Premium and Cloud Native plan families. Kamatera, for comparison, labels the same idea as CPU types A, B, D and T.
The rule of thumb: shared CPU suits development, staging and any production service that tolerates variable performance. Dedicated CPU suits databases, latency-sensitive APIs and anything that runs hot for long periods. If you cannot decide, start shared, measure steal time, and move up if it is consistently high.
Step 3: size storage by behavior, not capacity
Disk capacity is the easy number. What actually decides performance is how the disk is accessed. UpCloud tiers its block storage into MaxIOPS, Standard and Archive, which maps directly onto the question of how often data is read and written. Hetzner cloud instances use local NVMe with the option to attach additional volumes.
| Access pattern | Storage choice | Example |
|---|---|---|
| Constant small random I/O | Highest IOPS tier or local NVMe | Primary database |
| Occasional reads, steady growth | Standard block storage | Application logs, uploads |
| Rarely read | Archive tier or object storage | Old backups, exports |
Keep the operating system and application on fast storage and push everything cold to a cheaper tier. This is also how the storage options guide recommends thinking about block, object and backup storage.
Step 4: start one size smaller than instinct says
Because cloud instances resize, the cost of starting small is a few minutes of downtime later. The cost of starting large is paying for idle capacity every month. Deploy one size below your estimate, run a representative load for a week, and look at three numbers: peak CPU utilization, peak memory including cache, and disk wait time. Resize only the binding dimension.
Account limits shape the plan
New accounts often cap the number of servers rather than their size. The marketplace lists the Hetzner 5 Server Limits Account and Hetzner 10 Server Limits Account on the Buy Hetzner Account page; with a five-server cap, a few well-sized machines beat many small ones. The UpCloud $500 Credit Account listing gives room to test a couple of plan families against real traffic before settling. Read vCPU limits and service quotas for how caps are counted.
Step 5: revisit quarterly
Workloads drift. A service that was memory-bound at launch becomes CPU-bound after a caching layer is added. Put a recurring reminder to review utilization every quarter and to move instances between families as the profile changes. Vertical resizing is cheap on both Hetzner and UpCloud; leaving a wrong size in place for a year is not.
A worked example
A small SaaS backend: a Node.js API, a Postgres database and a Redis cache. The API is CPU-light and memory-moderate, so a shared-CPU plan with generous memory fits. Postgres is IOPS-bound, so it gets a dedicated-CPU plan on the fastest storage tier. Redis is memory-bound and small, so it shares the API host. Three services, two machines, and every dollar is spent on the dimension that binds.



