Supermicro Fan Speed Script

2U Supermicro servers are my go-to. These are much quieter than 1U servers, but the fans spin at 8800RPM. The IPMI fan modes available are Full (9000RPM), Heavy IO (6000RPM), and Optimal (supposed to auto-adjust). Unfortunately, setting the Fan Mode to Optimal seems to have a floor speed of 4500RPM, which is too loud. Even though my servers are in the garage, I sometimes work on projects there and can’t have it that noisy!

In the past, I’ve replaced Supermicro fans with low RPM Antec or Noctua fans, but since I moved my homelab to the garage I don’t need it to be that quiet.

Here’s my script, which tries to control the fan speed such that the noise is below that of a jet engine and the CPU temperature is below 50C. To avoid hunting, I set a minimum and maximum CPU range between 40-50C. The logic is simple:

  1. Set FAN speed mode to Full (this allows manual control of the fans).
  2. If the CPU is above 50C, bump the speed up by 10%
  3. If the CPU is below 40C, drop fan speed by 1%
  4. Repeat steps 2 and 3 every minute

    ⚠️ WARNING 1: Using this script could overheat and damage your CPU and other components; your server may release the magic smoke, burn through crystals like you’re traveling at Warp 10, or catch fire due to using this script. I just wrote it yesterday, so there may be bugs. I advise you not to use this script. If you go against my advice, keep a close eye on your temperatures.

    ⚠️ WARNING 2: This script was designed for a Supermicro X10 Motherboard. Yours may have a different fan configuration, in which case you will want to add or change the ipmitool commands.
Supermicro 2U Server with Four Fans

# apt install python3
# apt install ipmitools

# vim /usr/local/bin/fan_control.py

#!/usr/bin/env python3
import os
import subprocess
import time
import syslog
import re

# Set your desired temperature range and minimum fan speed
MIN_TEMP = 40
MAX_TEMP = 50
MIN_FAN_SPEED = 5  # Sets an initial fan speed of 5% 
current_fan_speed = MIN_FAN_SPEED

# IPMI tool command to set the fan control mode to manual (Full)
os.system("ipmitool raw 0x30 0x45 0x01 0x01")
time.sleep(2)

# Get the current CPU temperature
def get_cpu_temperature():
    temp_output = subprocess.check_output("ipmitool sdr type temperature", shell=True).decode()
    cpu_temp_lines = [line for line in temp_output.split("\n") if "CPU" in line and "degrees" in line]

    if cpu_temp_lines:
        cpu_temps = [int(re.search(r'\d+(?= degrees)', line).group()) for line in cpu_temp_lines if re.search(r'\d+(?= degrees)', line)]
        avg_cpu_temp = sum(cpu_temps) // len(cpu_temps)
        return avg_cpu_temp
    else:
        print("Failed to retrieve CPU temperature.")
        return None

# Set the fan speed
def set_fan_speed(speed):
    global current_fan_speed
    current_fan_speed = speed

    # Convert the speed percentage to a hex value
    hex_speed = format(speed * 255 // 100, "02x")


    # Set the fan speed for all 4 zones
    os.system(f"ipmitool raw 0x30 0x70 0x66 0x01 0x00 0x{hex_speed}")
    time.sleep(2)
    os.system(f"ipmitool raw 0x30 0x70 0x66 0x01 0x01 0x{hex_speed}")
    time.sleep(2)


    # Log the fan speed change to syslog
    syslog.syslog(syslog.LOG_INFO, f"Fan speed adjusted to {speed}%")

    # Print the fan speed change to console
    print(f"Fan speed adjusted to {speed}% - {hex_speed}")

# Set initial minimum fan speed
set_fan_speed(MIN_FAN_SPEED)

while True:


    cpu_temp = get_cpu_temperature()

    # Print the current CPU temperature to console
    print(f"Current CPU temperature: {cpu_temp}°C")

    if cpu_temp > MAX_TEMP and current_fan_speed < 100:
        # Increase the fan speed by 10% to cool down the CPU
        new_fan_speed = min(current_fan_speed + 10, 100)
        set_fan_speed(new_fan_speed)
    elif cpu_temp < MIN_TEMP and current_fan_speed > MIN_FAN_SPEED:
        # Decrease the fan speed by 1% if the temperature is below the minimum threshold
        new_fan_speed = max(current_fan_speed - 1, MIN_FAN_SPEED)
        set_fan_speed(new_fan_speed)

    # Wait for 60 seconds before checking the temperature again
    time.sleep(60)

# vim /etc/systemd/system/fan_control.service :

[Unit]
Description=Fan Controller Service
After=network.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 /usr/local/bin/fan_control.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

Set file permissions:
# chmod 755 /usr/local/bin/fan_control.py
# systemctl daemon-reload
# systemctl enable fan_control.service
# systemctl start fan_control.service
# systemctl status fan_control.service

Bugs: so, I’ve come across one bug where the script won’t spin down both fan zones, but killing the script and running it a second or third time seems to work. I suspect this is from sending ipmi commands too fast, so I’ve added sleep delays, but because it’s an intermittent issue who knows if that fixed it.

Improvement ideas: It would be great to add temperature monitoring for the other components (HDD, etc.). But in my observation, if I can keep the CPU cool the rest of the components are okay.

To watch the status, check IPMI or tail syslog:

Temperature readings all green
Fan Speed readings around 3000RPM

# tail -F /var/log/syslog |grep -E ‘speed|temp’

Fan speed adjusted to 4% - 0a
Current CPU temperature: 38°C
Fan speed adjusted to 3% - 07
Current CPU temperature: 39°C
Fan speed adjusted to 2% - 05
Current CPU temperature: 39°C
Fan speed adjusted to 1% - 02
...
Current CPU temperature: 48°C
Current CPU temperature: 50°C
Current CPU temperature: 52°C
Fan speed adjusted to 11%
Current CPU temperature: 51°C
Current CPU temperature: 48°C
Current CPU temperature: 46°C

That was set up last night.

When I woke up this morning, the fans were running at a pleasant hum of 1800 RPM.

He who blesses his friend with a loud voice early in the morning, It will be counted as a curse to him. – Proverbs 27:14 LSB

2 thoughts on “Supermicro Fan Speed Script”

  1. I re-did this with a Windows powershell slant. This is known working on X11 Motherboards but should work on 9 and 10 as well (and likely 12)

    feel free to publish as an adder to what you did.
    # Function to get CPU temperature using IPMI
    function GetCpuTemperatureFromIPMI {
    $ipmiOutput = & “ipmicfg-win.exe” -sdr
    $cpuTemp = $ipmiOutput | Where-Object { $_ -match ‘\(4\) CPU Temp\s+\|\s+(\d+(?:\.\d+)?)C/’ } | ForEach-Object { $matches[1] }
    return [double]$cpuTemp

    }

    # Function to set fan mode to auto using IPMICFG-Win.exe
    function SetFanModeAuto() {
    Start-Process -FilePath “IPMICFG-Win.exe” -ArgumentList “-fan 0” -Wait
    }

    # Function to set fan mode to quiet using IPMICFG-Win.exe
    function SetFanModeQuiet() {
    Start-Process -FilePath “IPMICFG-Win.exe” -ArgumentList “-fan 1” -Wait
    Start-Process -FilePath “IPMICFG-Win.exe” -ArgumentList “-raw 0x30 0x70 0x66 0x01 0x00 0x06” -Wait
    }

    # Main script logic
    try {
    $currentFanMode = $null # Variable to store the current fan mode
    while ($true) {
    $cpuTemperature = GetCpuTemperatureFromIPMI

    if ($currentFanMode -ne 1 -and $cpuTemperature -lt 50) {
    SetFanModeQuiet
    $currentFanMode = 1 # Set the current fan mode to quiet (1)
    Write-Host “CPU temperature is below 50°C. Fan mode set to quiet.”
    } elseif ($currentFanMode -ne 0 -and $cpuTemperature -ge 53) {
    SetFanModeAuto
    $currentFanMode = 0 # Set the current fan mode to auto (0)
    Write-Host “CPU temperature is above 53°C. Fan mode set to auto.”
    }

    # Wait for 20 seconds before the next check
    Start-Sleep -Seconds 20
    }
    } catch {
    Write-Host “Error occurred: $_”
    }

    Reply

Leave a Comment