Detect a server cupboard cooling fault early
A cupboard can heat slowly for hours after a fan stops, or spike after someone closes a door that is normally left ajar. You can catch both patterns earlier by comparing cupboard temperature with the room, the rate of rise, equipment load and whether ventilation should be running.
The fault this project detects
This project detects inadequate heat removal around an NBN modem, router, switch, NAS or Home Assistant host. It looks for a rising cupboard-to-room temperature difference while the equipment is producing heat, then reserves Critical for a site-specific last-line temperature or corroborated cooling failure.
The worked structure assumes a ventilated indoor communications cupboard with one cupboard probe, one nearby room probe, a monitored equipment power feed and a fan status entity. Replace every example entity with the names used by your installation.
The evidence model
A useful alarm does not promote one noisy reading straight to an emergency. It combines measurements that fail in different ways:
- Value: cupboard temperature provides the final safety limit.
- Direction: a positive derivative shows heat accumulating rather than merely being warm.
- Agreement: the cupboard-to-room delta confirms local heat build-up.
- Context and time: load and fan state explain whether heat and ventilation should be present, while persistence rejects brief CPU bursts.
A single probe cannot tell the difference between a hot house and failed cupboard airflow. The second temperature probe and operating-state signals make that distinction visible.
Freshness is separate evidence
An entity can keep a plausible old value without becoming unavailable. Inspect last_changed on a source that should genuinely change, and work out the longest gap you'd expect between meaningful state changes on a normal day by reading that entity's own History graph over a few representative days. If the measured process can legitimately stay flat, add a changing source heartbeat or timestamp; do not treat an unchanged temperature, level or power value as proof that the transport is alive.
Rate maths: never copy a gradient
The Trend integration expresses min_gradient in units per second. The arithmetic example from the project brief is 4 °C/hour ÷ 3600 = 0.00111 °C/s. That is an example conversion, not a threshold for this equipment. Calculate the rate from your own timestamped readings: pull two points from the cupboard probe's History graph, take the temperature change between them in °C, divide by the number of seconds between those two timestamps, and that gives you the site-specific gradient for your equipment. A Derivative helper can instead display a friendlier per-hour unit when configured that way.
Collect this data before choosing a threshold
Record several normal daily load cycles, including a hot afternoon and any backup, scan or media workload that raises NAS or server power.
| Measurement | Why it matters | Required value |
|---|---|---|
sensor.server_cupboard_temperature | Actual cupboard curve | Read the min, max and average straight off this sensor's 24-hour History graph |
sensor.study_temperature | Ambient comparison | Pull matching timestamped readings from the room sensor's History graph for the same 24 hours |
sensor.server_cupboard_temperature_rate | Speed of heat accumulation | Note the normal °C/h range on a healthy day, then compare it with a day when airflow was known to be reduced or blocked |
sensor.server_rack_power | Heat-load proxy | Record the idle and busy wattage range from this sensor's History across a normal day and a heavy workload |
binary_sensor.server_cupboard_fan_running | Cooling context | Capture the on/off trace from History, and confirm with power, tachometer or airflow feedback that "on" really means air is moving |
The experiment that makes this article defensible
- Record a normal low-load cycle and a normal high-load cycle with the cupboard in its usual state.
- Compare cupboard temperature, room temperature, rate, fan state and power on one History timeline.
- If safe for the equipment, test the ordinary door positions users actually leave it in; do not block vents or stop cooling to manufacture a fault.
- After any genuine fan or airflow fault, preserve the trace and recovery time before changing thresholds.
Keep your own record as you go: save the timestamped readings from your test cycles, note the Home Assistant version you're running, note the exact entity IDs you used, and take one or two History or dashboard screenshots. Do not stage an unsafe electrical, refrigeration, pressure, battery or water fault merely to make a graph.
Create the helpers
Use Settings → Devices & services → Helpers. Create the named Number or Toggle helpers before loading the templates. Where this guide uses a Derivative, Statistics, History Stats, Integral, Schedule or Utility Meter helper, the current Home Assistant interface can create it; the source links below describe the current options.
| Helper or derived entity | Purpose | Setting |
|---|---|---|
sensor.server_cupboard_temperature_rate | Derivative helper in °C/h | Set the time window and healthy rise-rate range from the History data you measured above |
input_number.server_cupboard_min_load_w | Minimum load for performance checks | Set this from the idle/busy wattage range you recorded for the rack power sensor |
input_number.server_cupboard_warning_delta_c | Abnormal cupboard-minus-room delta | Set this above the largest cupboard-minus-room gap you saw on a normal day |
input_number.server_cupboard_warning_rate_c_per_hour | Abnormal positive rise rate | Set this above the healthy rise rate you measured for this cupboard |
input_number.server_cupboard_hold_minutes | Persistence before Warning | Set this long enough to reject the brief spikes you saw in your own History, short enough to still catch a real fault |
input_number.server_cupboard_critical_temperature_c | Last-line cupboard threshold | Set this to the manufacturer's or your own last-line safe limit for the equipment in this cupboard |
input_boolean.server_cupboard_monitoring_enabled | Master notification gate | Leave this off until you've validated every threshold above against your own data, then switch it on |
input_number.server_cupboard_critical_repeat_minutes | Critical repeat spacing | Choose any repeat interval greater than zero that matches how urgently you want reminders |
Current Home Assistant YAML
Merge top-level keys with your existing configuration instead of duplicating them. Replace the example entity IDs consistently. If you keep automations in automations.yaml, paste only the automation list items there, without a top-level automation: wrapper.
Combine thermal rise, ambient delta and operating context
Create the Derivative helper from the cupboard probe with °C per hour as its displayed time unit. The template then checks relationships rather than blindly copying a universal temperature.
template:
- sensor:
- name: "Server cupboard room delta"
unique_id: server_cupboard_room_delta
default_entity_id: sensor.server_cupboard_room_delta
device_class: temperature
state_class: measurement
unit_of_measurement: "°C"
availability: >
{{ has_value('sensor.server_cupboard_temperature')
and has_value('sensor.study_temperature') }}
state: >
{{ ((states('sensor.server_cupboard_temperature') | float)
- (states('sensor.study_temperature') | float)) | round(2) }}
- binary_sensor:
- name: "Server cupboard cooling warning"
unique_id: server_cupboard_cooling_warning
default_entity_id: binary_sensor.server_cupboard_cooling_warning
device_class: problem
delay_on:
minutes: >
{{ states('input_number.server_cupboard_hold_minutes') | int(0) }}
availability: >
{{ has_value('sensor.server_cupboard_room_delta')
and has_value('sensor.server_cupboard_temperature_rate')
and has_value('sensor.server_rack_power') }}
state: >
{% set loaded = (states('sensor.server_rack_power') | float)
>= (states('input_number.server_cupboard_min_load_w') | float) %}
{% set hot_delta = (states('sensor.server_cupboard_room_delta') | float)
>= (states('input_number.server_cupboard_warning_delta_c') | float) %}
{% set rising = (states('sensor.server_cupboard_temperature_rate') | float)
>= (states('input_number.server_cupboard_warning_rate_c_per_hour') | float) %}
{{ loaded and hot_delta and rising }}
attributes:
reason: >-
Cupboard-room delta is {{ states('sensor.server_cupboard_room_delta') }} °C,
rate is {{ states('sensor.server_cupboard_temperature_rate') }} °C/h,
load is {{ states('sensor.server_rack_power') }} W and fan is
{{ states('binary_sensor.server_cupboard_fan_running') }}.
- name: "Server cupboard cooling critical"
unique_id: server_cupboard_cooling_critical
default_entity_id: binary_sensor.server_cupboard_cooling_critical
device_class: problem
availability: >
{{ has_value('sensor.server_cupboard_temperature') }}
state: >
{{ (states('sensor.server_cupboard_temperature') | float)
>= (states('input_number.server_cupboard_critical_temperature_c') | float)
or (is_state('binary_sensor.server_cupboard_cooling_warning', 'on')
and is_state('binary_sensor.server_cupboard_fan_running', 'off')) }}Warning and Critical notification actions
Replace the mobile notification action and media-player entity with your own. Put critical-monitoring-alert.mp3 in /media, test it manually, and leave the monitoring Toggle off until calibration is complete.
automation:
- id: server_cupboard_warning_push
alias: "Server cupboard - Warning push"
mode: single
triggers:
- trigger: state
entity_id: binary_sensor.server_cupboard_cooling_warning
to: "on"
conditions:
- condition: state
entity_id: input_boolean.server_cupboard_monitoring_enabled
state: "on"
actions:
- action: notify.mobile_app_your_phone
data:
title: "Server cupboard warning"
message: >-
{{ state_attr('binary_sensor.server_cupboard_cooling_warning', 'reason')
or 'The warning condition is active. Check Home Assistant for evidence.' }}
- id: server_cupboard_critical_repeat
alias: "Server cupboard - Critical repeat"
mode: restart
triggers:
- trigger: state
entity_id: binary_sensor.server_cupboard_cooling_critical
to: "on"
conditions:
- condition: state
entity_id: input_boolean.server_cupboard_monitoring_enabled
state: "on"
actions:
- action: notify.mobile_app_your_phone
data:
title: "Server cupboard critical"
message: "The critical condition is active. Check the equipment and evidence now."
- action: media_player.play_media
target:
entity_id: media_player.alert_speaker
data:
media_content_id: "media-source://media_source/local/critical-monitoring-alert.mp3"
media_content_type: "audio/mpeg"
- repeat:
while:
- condition: state
entity_id: binary_sensor.server_cupboard_cooling_critical
state: "on"
- condition: template
value_template: >-
{{ states('input_number.server_cupboard_critical_repeat_minutes') | int(0) > 0 }}
sequence:
- delay:
minutes: >-
{{ states('input_number.server_cupboard_critical_repeat_minutes') | int(0) }}
- condition: state
entity_id: binary_sensor.server_cupboard_cooling_critical
state: "on"
- action: notify.mobile_app_your_phone
data:
title: "Server cupboard still critical"
message: "The critical condition remains active."
- action: media_player.play_media
target:
entity_id: media_player.alert_speaker
data:
media_content_id: "media-source://media_source/local/critical-monitoring-alert.mp3"
media_content_type: "audio/mpeg"
Why the logic works
The derivative spots accumulation, the room delta localises it to the cupboard, and power proves there is a meaningful heat load. Fan disagreement raises urgency, while the absolute threshold remains a last line rather than the only detector.
The design is intentionally diagnostic rather than controlling. It reports an impossible or abnormal relationship; it does not bypass equipment protection or decide that a single suspected cause is proven.
Severity tiers without alert fatigue
| Tier | Meaning | Action |
|---|---|---|
| Advisory | The thermal delta or rise rate is outside its recent healthy envelope. | Dashboard only |
| Warning | Load, local temperature difference and rising temperature persist together. | One push notification |
| Critical | The site-specific temperature limit is crossed, or the corroborated warning coincides with a stopped fan. | Push, repeat and audible alert |
A weak clue remains visible without interrupting you. A Warning requires persistence or corroboration. Critical is reserved for direct danger or several independent signals. That separation is what stops a useful monitoring system becoming notification wallpaper.
Troubleshooting
- Busy workloads trigger warnings
- Capture those workloads and either widen the healthy envelope or add a workload-state condition.
- The room sensor is too far away
- Place the reference in the same thermal zone but outside the cupboard exhaust plume.
- Fan says on but is physically stopped
- Use fan power, current, tachometer or airflow where possible; a commanded state is not proof.
- Rate is noisy
- Lengthen the Derivative helper time window and confirm probe updates are frequent enough.
- Warnings arrive after a Home Assistant restart
- Require fresh sensor timestamps and an appropriate persistence period before enabling notifications.
Safety and limits
Do not obstruct ventilation or deliberately overheat electronics. Keep mains work, fixed power monitoring and fan wiring with a licensed electrician where required. An alarm is not a substitute for equipment thermal protection or backups.
If a webhook is added later, treat its webhook_id like a password, keep local_only: true unless a reviewed design requires otherwise, and never use an unauthenticated webhook for locks, garage doors, power isolation or anything destructive.
Official sources
These are the primary documentation pages used for the design. Check them against the Home Assistant or ESPHome version you are actually running.
Related Smart Home Fix guides
- Fix unreliable entities before trusting alarms
- Separate smart-home devices without breaking discovery
- Reduce false alarms without hiding real faults
FAQ
What temperature is too hot for a server cupboard?
Use the limits for the actual equipment and probe location, then validate them against normal hot-day data. There is no trustworthy universal cupboard value.
Why compare the cupboard with the room?
The difference isolates local heat build-up. A heatwave may warm both sensors, while failed cupboard airflow usually increases the gap.
Does a fan entity prove that air is moving?
Not necessarily. A command or relay state can remain on with a stalled fan. Power, tachometer or airflow feedback gives stronger evidence.
How do I choose the temperature rate?
Measure normal rise rates in °C per hour, then set a threshold outside that envelope. Convert any per-second Trend value before comparing it.
Can this replace server monitoring and backups?
No. It adds environmental fault evidence. Keep equipment health monitoring, graceful shutdown, tested backups and manufacturer thermal protection.