Reserve enough CPU headroom for the worst repeatable workload to meet event-to-action and restart targets; there is no defensible universal percentage for every Home Assistant stack.
A four-core host can show a modest average while one core is saturated, or appear CPU-bound while storage wait, memory pressure, or thermal throttling is the real limit. Define the busiest realistic overlap, measure control latency and per-core behavior, then keep the smallest resource margin that passes repeatedly without stopping normal companion services.
Define the Peak and the User-Facing Limit
Build one workload that combines the busiest normal automation burst with dashboard use and planned background jobs such as backup, database maintenance, voice, or selected camera processing. Define acceptable event-to-action latency, dashboard response, and post-restart readiness before measuring utilization.
Do not use an artificial all-core stress test as the only peak. It measures hardware capacity but not the scheduling, database, integration, and add-on overlap that users actually experience.
A valid baseline runs the same device count, integrations, database state, and neighboring services three times. If the workload cannot be repeated, no percentage derived from it is a reliable reserve.
Capture a quiet-period run as a control. The difference between quiet and peak states reveals workload sensitivity; the peak percentage alone cannot show whether the system started close to saturation.
Read Per-Core Saturation and Wait Separately
Record per-core utilization, load, steal time for virtual machines, I/O wait, frequency, temperature, and the Home Assistant process alongside user-facing latency. Align all measurements to the same timestamps.
Because one saturated core can hide inside a much lower whole-system average, test process shape instead of assuming total utilization represents usable headroom.
If one core pins while latency rises, CPU single-thread capacity or blocking work is implicated. If I/O wait rises first, repair storage or database behavior. If frequency falls with temperature, correct cooling before reserving more nominal capacity.
Create Headroom With Scheduling and Isolation
Move optional jobs away from the busiest control window, limit noisy companion containers, and prevent camera, AI, or media tasks from consuming every runnable core. Keep Home Assistant and essential brokers able to make progress during those peaks.
Compare workload-driven processor headroom only after the current system's limiter is measured. A faster purchase does not correct unbounded jobs or storage wait.
Retest after each scheduling or limit change. If latency passes without a hardware change, the recovered margin is operational headroom; if the same core remains saturated, compare a stronger CPU only against the identical workload.
Set the Reserve From Repeated Passes
Use the highest observed peak across clean repeated runs, then retain additional capacity for expected integration growth and one maintenance overlap. Express the result as a tested service envelope, not a universal idle target.
The procedure in the resource-headroom benchmark provides the cross-metric baseline for CPU, memory, storage, and network.
Pass when the original peak meets latency and restart targets in consecutive runs without thermal throttling or forced service stops. Escalate or upgrade when the same CPU-specific saturation remains after scheduling, integration, and I/O causes are excluded.
Support & Tips
More to Read

How to Optimize Immich Database Connections for Concurrent Containers
Do not raise max_connections first. Measure Immich sessions, total every container's demand, preserve admin headroom, and tune only the proven bottleneck.

How to Prevent Duplicate Jobs or Imports in Immich
Separate repeated jobs from duplicate assets. Use one canonical ingestion path, control retries and path changes, then test re-entry on a small cohort.

How to Repair Immich After Its Database Volume Fills Up
Never delete PostgreSQL WAL to free space. Stop Immich writes, preserve database state, add safe capacity, recover PostgreSQL, then prevent recurrence.

