4 min read

Quieter fans on a Dell R710 with local IPMI

A Bash script and a systemd timer that keep a Proxmox host's fans at a fixed low speed while it's cool, and hand control back to Dell's automatic mode when it warms up.

The Dell PowerEdge R710 is loud. Dell’s default fan profile is conservative: the fans ramp up hard even when the machine is barely doing anything, and in automatic mode they run somewhere between 5,000 and 7,000 RPM.

I wanted them slower without switching off Dell’s built-in hardware protections. The result is dell_r710_local_fancontrol, a Bash script that runs on the Proxmox host every 30 seconds, holds the fans at a fixed low speed while temperatures are safe, and gives control back to Dell’s automatic mode as soon as they aren’t.

Two modes

The iDRAC6 on an R710 accepts a few raw IPMI commands for the fans, and the script uses three of them:

r710-fancontrol.sh
apply_auto() {
  ipmitool raw 0x30 0x30 0x01 0x01 >/dev/null 2>&1 || true
}

apply_manual() {
  ipmitool raw 0x30 0x30 0x01 0x00 >/dev/null 2>&1 || true
  ipmitool raw 0x30 0x30 0x02 0xff "$FAN_HEX" >/dev/null 2>&1 || true
}

0x01 0x01 turns Dell’s automatic fan control back on. 0x01 0x00 turns it off, after which 0x02 0xff sets every fan to the same speed: a percentage written in hex, from 0x00 to 0x64.

In MANUAL mode the fans run at the speed I picked. In AUTO mode the iDRAC is in charge again, with its own fan curve. The script doesn’t have a curve of its own. It only decides which of the two modes should be active.

Where the temperatures come from

The iDRAC on an R710 doesn’t expose CPU temperatures, but the host does. lm-sensors reads the coretemp sensors, and the script takes the hottest core across all CPUs:

r710-fancontrol.sh
read_cpu_max() {
  local raw max
  raw="$(sensors 2>/dev/null \
    | grep -oP 'Core.*?\+\K[0-9]+(\.[0-9]+)?' \
    | sort -nr | head -1 || true)"
  max="$(to_int "${raw%%.*}")"
  echo "$max"
}

The iDRAC does report an Ambient Temp sensor, and the script reads that, plus the RPM of each fan, with ipmitool.

All IPMI traffic goes through /dev/ipmi0 on the host itself. The script doesn’t need network access to the iDRAC or a login for it, and it runs directly on Proxmox rather than in a container. Setup is two packages, plus two kernel modules if /dev/ipmi0 doesn’t exist yet:

shell
apt install -y ipmitool lm-sensors
sensors-detect

modprobe ipmi_si
modprobe ipmi_devintf
echo ipmi_si > /etc/modules-load.d/ipmi.conf
echo ipmi_devintf >> /etc/modules-load.d/ipmi.conf

Hysteresis

With a single threshold, a CPU hovering right at it would flip the fans between quiet and loud on every run. So each sensor has two:

Setting Default Meaning
CPU_ON 55 switch to AUTO at or above this (°C)
CPU_OFF 50 allowed back to MANUAL at or below this
AMBIENT_ON 33 switch to AUTO at or above this (°C)
AMBIENT_OFF 30 allowed back to MANUAL at or below this

One sensor crossing its ON value is enough to switch to AUTO. Going back to MANUAL needs both sensors at or below their OFF values. If either temperature can’t be read, the script switches to AUTO as well and stays there until both readings are back, because a fixed low speed is only safe while it knows how hot things are.

In this excerpt from the logs, the CPU touches 55°C, the iDRAC takes over, and 31 seconds later the CPU is at 46°C and the fans drop back to their low speed:

11:34:12 STATE CHANGE → AUTO | CPU=55C Ambient=16C | FAN1=2280RPM ... | thresholds: CPU_ON=55 AMBIENT_ON=33
11:34:43 STATE CHANGE → MANUAL | CPU=46C Ambient=16C | FAN1=4200RPM FAN2=4080RPM FAN3=4440RPM ... | Fan=0x0A (~10%) ...

In those 31 seconds the fans went from 2,280 RPM to between 4,080 and 4,440.

The script also re-sends the current mode on every run, even when nothing changed, so if something else switches the fans back, the next run corrects it.

Running it

The script isn’t a daemon. Each run reads the sensors, picks a mode, applies it and exits, and the previous mode waits in /run/r710-fancontrol.state for the next run. A systemd oneshot service runs the script, and a timer starts that service every 30 seconds:

/etc/systemd/system/r710-fancontrol.timer
[Unit]
Description=Run R710 fan control every 30 seconds

[Timer]
OnBootSec=30
OnUnitActiveSec=30
AccuracySec=1s

[Install]
WantedBy=timers.target

The thresholds and the fan speed are environment variables, so you can tune them with systemctl edit r710-fancontrol.service instead of editing the script:

r710-fancontrol.service.d/override.conf
[Service]
Environment=FAN_HEX=0x0C
Environment=CPU_ON=55
Environment=CPU_OFF=50

Picking a fan speed

The default is 0x10. These are typical numbers for an R710; the actual RPM depends on the fan model and the airflow:

FAN_HEX Speed RPM
0x0A ~10% ~2,200
0x0C ~12% ~2,500
0x0E ~14% ~2,700
0x10 ~16% ~3,000
AUTO 5,000 to 7,000+

For a homelab I’d start at 0x0C. The logs in this post come from a host running at 0x0A.

Logs worth reading

At one run every 30 seconds, logging every run would flood the journal. The script logs mode changes, a status line on the hour, and the minimum, maximum and average temperatures over the last 24 hours:

shell
journalctl -t r710-fancontrol -f
08:00:22 HOURLY STATUS | MODE=MANUAL Fan=0x0A (~10%) | CPU=39C Ambient=16C | FAN1=2280RPM FAN2=2280RPM ...
08:00:22 STATS24H | CPU(min=28C max=55C avg=38.5C) Ambient(min=16C max=17C avg=16.3C) samples=2788

The 24-hour line shows how close the host came to the threshold. In this example the CPU averaged 38.5°C and peaked at 55°C, exactly CPU_ON.

Each sample goes into /var/lib/r710-fancontrol/stats.csv as epoch,cpu,ambient, and every run removes rows older than 24 hours, so the file never holds more than a day of data.

Before you try it

The script sends raw IPMI commands to your server. It’s written for the R710 with iDRAC6, and other Dell 11th-generation servers may use different fan commands. Dell’s hardware protections stay active and AUTO takes over whenever a threshold is crossed, but check the temperatures under real load before you trust it, and raise the fan speed in summer.

The code is MIT licensed and on GitHub.