Supermicro fan control for Linux (home) servers.
This is a systemd service running on Linux that can control fans with the help of IPMI on Supermicro X10-X13/H10-H14, some X9, and X14 (experimental) motherboards.
systemd and bashcoretemp kernel module for Intel CPUs or k10temp kernel module for AMD CPUsdrivetemp kernel module (kernel version 5.6+ required) for SATA HDDs/SSDsipmitoolsmartmontools for SAS/SCSI disks and standby guard featurenvidia-smi for Nvidia GPUsrocm-smi for AMD GPUsnpu-smi for Ascend NPUscoretemp/k10temp and drivetemp)smfc service or run it in docker (see chapter 9. for more details)/etc/smfc/smfc.conf and command line options in /etc/default/smfc (see chapter 10. for more details).smfc service (see chapter 11. for more details)Feel free to visit Discussions and raise your questions or share your experience on this project.
This service was designed for Supermicro motherboards with IPMI functionality. It implements fan controllers that control fan speed dynamically in one or more IPMI zones. The service operates the fans in IPMI FULL mode, where the fan rotation level can be adjusted with IPMI raw commands (read more details here).
Key features:
control_function=) for arbitrary piecewise-linear fan curves[CPU] + [CPU:1])nvidia-smi or rocm-sminpu-smiremote_parameters= for VM setups (e.g. TrueNAS on Proxmox with PCI passthrough)systemd service, Docker image, DEB/RPM/AUR package, or PyPI packagesmfc-client and enables Grafana dashboard integrationsmfc-client tool showing a live read-only snapshot of controllers, fan levels, IPMI zones, and standby stateexit_level=, 100% by default) at service terminationIPMI zone is a logical term, representing a cooling zone, where there are predefined fans having the same rotation speed. Please note that the fan assignment to an IPMI zone is predefined on the motherboard, it cannot be changed (Supermicro does not provide individual fan configuration features in IPMI, while other vendors do it). On a typical Supermicro motherboard, there are two IPMI zones:
On Supermicro server boards, there could be more IPMI zones with different fan names (see issue #31).
smfc v3.8.0and earlier versions implemented a feature (called Swapped Zones) to swap IPMI zone 0 and 1. Fromsmfc v4.0.0the IPMI zones can be assigned freely to fan controllers providing more freedom and convenience for the user (seeipmi_zone=parameter for more details).
In smfc, the following fan controllers are implemented:
| Fan controller | Temperature source | Configuration | Default IPMI zone |
|---|---|---|---|
| CPU | Intel/AMD CPU(s) | CPUs are identified automatically | 0 (CPU zone) |
| HD | SATA and SCSI HDDs/SSDs | Hard disks' names must be specified in [HD] hd_names= parameter | 1 (Peripheral zone) |
| NVME | NVMe SSDs | NVMe device names must be specified in [NVME] nvme_names= parameter | 1 (Peripheral zone) |
| GPU | Nvidia/AMD GPUs | GPU indices must be specified in [GPU] gpu_device_ids= parameter | 1 (Peripheral zone) |
| NPU | Ascend NPUs | NPU card IDs must be specified in [NPU] npu_device_ids= parameter | 1 (Peripheral zone) |
| PCI | PCI device HWMON | PCI device(s) must be specified in [PCI] pci_address=, pci_id= or pci_driver= | 1 (Peripheral zone) |
| CONST | None | Constant fan level can be specified in [CONST] level= parameter | 1 (Peripheral zone) |
These fan controllers can be enabled and disabled independently. They can be used in a free combination with one or more IPMI zones. Multiple fan controllers
can share the same IPMI zone -- smfc will automatically apply the highest fan level requested by any controller in that zone (see chapter 1.3 for details).
CONST fan controller is an exception here, it does not require a temperature source, it can provide a constant fan level for one or more IPMI zones.
In smfc configuration file each fan controller has an individual section.
In smfc, a temperature-driven fan controller implements the following control logic:
ipmitool)
If the temperature source has multiple instances (e.g. multiple CPUs, HDDs, NVMEs, GPUs, NPU cards or PCI devices) then the user can configure a calculation method (i.e. minimum, average, maximum) for the calculation of the final temperature value (see temp_calc= parameter).
Please note that smfc will set all fans back to 100% speed at service termination to avoid overheating (see
chapter 1.6)!
The chapters below describe each fan controller: which devices it drives, how it reads their temperature, and what you need to know before you enable it.
Every temperature-driven fan controller shares the following parameters, so the chapters below list only the controller-specific ones:
enabled= -- whether the fan controller runs at allipmi_zone= -- the IPMI zone(s) the controller drivestemp_calc= -- how the temperatures of several devices are reduced to one value (minimum, average, maximum)steps= -- the number of discrete steps in the temperature-to-level mappingsensitivity= -- the temperature change needed before the controller reactspolling= -- how often the temperature is readmin_temp= / max_temp= -- the temperature interval the controller steers inmin_level= / max_level= -- the fan level interval the temperature interval is mapped tocontrol_function= -- an arbitrary piecewise-linear curve that replaces the four parameters above (see chapter 2)smoothing= -- the moving average window size that damps temperature oscillationerror_tolerance= -- how many failed temperature reads per device are toleratedThe CONST fan controller reads no temperature, so it uses enabled=, ipmi_zone=, polling= and level= only.
| Target devices | Intel and AMD CPUs. On a multi-socket board every socket is a separate device, labelled cpu0, cpu1, and so on. |
| Temperature source | The Linux kernel HWMON interface, through the coretemp (Intel) or k10temp (AMD) kernel module. |
| Device identification | Automatic. smfc finds the CPUs in the udev database, so there is no device parameter. |
| Other parameters | None |
| Docker | Supported in every image. The coretemp or k10temp kernel module must be loaded on the host. |
| Need to know | • The matching kernel module must be loaded. smfc checks it at startup and stops with an error if neither coretemp nor k10temp is present.• The default IPMI zone is 0, the CPU zone. Every other controller defaults to zone 1. • CPU temperature moves fast, so the defaults are a wide window ( min_temp=30.0, max_temp=60.0) with a fast poll (polling=2) and a coarse sensitivity=3.0 that keeps the fans from chasing every spike. |
| Target devices | SATA and SAS/SCSI hard disks and SSDs. NVMe SSDs are not accepted here -- use the NVME fan controller for those. |
| Temperature source | The drivetemp kernel module through HWMON for SATA disks, and the smartctl command for SAS/SCSI disks. smfc decides per disk and falls back to smartctl automatically when a disk has no HWMON device. |
| Device identification | hd_names= -- use the stable /dev/disk/by-id/... names |
| Other parameters | smartctl_path=, standby_guard_enabled=, standby_hd_limit= |
| Docker | Supported in every image. All three ship smartctl, so SAS/SCSI disks work as they do on the host. SATA disks need the drivetemp kernel module loaded on the host; without it smfc falls back to smartctl for them too. |
| Need to know | • HWMON is the faster path, and it can read the temperature while the disk sleeps. See chapter 4 for the full compatibility table. • Disk types can be mixed in hd_names=, but the Standby guard feature is then not supported. See chapter 3.• smartctl usually needs root. Run the service as root, or start smfc with the -s command-line option to prefix the calls with sudo.• This is the slowest controller. Its polling=10 default exists because a smartctl call per disk is expensive, and startup time grows with the number of disks.• Disks are the narrowest temperature window of all controllers ( min_temp=32.0, max_temp=46.0), because a disk reaches its limit far below a CPU. |
| Target devices | NVMe SSDs. |
| Temperature source | The Linux kernel HWMON interface. It reads temp1_input, which the NVMe standard defines as the Composite temperature -- the value the drive itself uses for thermal protection. |
| Device identification | nvme_names= -- use the stable /dev/disk/by-id/nvme-... names |
| Other parameters | None |
| Docker | Supported in every image. |
| Need to know | • No kernel module has to be loaded. The kernel exposes the sensor as soon as the drive is present. • A drive can expose more sensors than Composite, but this controller always reads Composite.• NVMe drives run hotter than SATA disks, so the defaults are min_temp=35.0 and max_temp=70.0. |
| Target devices | Nvidia and AMD GPUs. |
| Temperature source | The nvidia-smi or the rocm-smi command. No kernel module and no udev lookup are involved. |
| Device identification | gpu_type= (nvidia or amd) and gpu_device_ids= -- the indices of the cards |
| Other parameters | nvidia_smi_path=, rocm_smi_path=, amd_temp_sensor= (0-junction, 1-edge, 2-memory) |
| Docker | The standard image does not support it. Use the -nvidia or the -amd image variant, which needs the NVIDIA Container Toolkit or the amdgpu driver on the host. See DOCKER.md. |
| Need to know | • The matching command must be installed. smfc checks it at startup.• One SMI call per polling window covers every index of the section, not one call per card. So a failed call affects all GPUs of the section at once, and their error_tolerance budgets advance in lockstep.• amd_temp_sensor= applies to AMD cards only. Nvidia cards report one temperature. |
| Target devices | Huawei Ascend NPU cards, e.g. the Atlas 300I Duo. |
| Temperature source | The npu-smi command. |
| Device identification | npu_device_ids= -- the -i card IDs reported by npu-smi info -m |
| Other parameters | npu_smi_path=, npu_smi_timeout= |
| Docker | Not supported in any image. npu-smi is part of the Ascend driver on the host and is not in the images, so an enabled [NPU] section stops the container at start-up. Run smfc as a systemd service on the host instead. See DOCKER.md. |
| Need to know | • The card IDs may not start from 0. Read them with npu-smi info -m instead of assuming.• The controlled unit is a card, not a chip. On a multi-chip card the hottest chip drives the card temperature, so a dual-chip card still counts as one unit. • The card also reports an MCU temperature. smfc excludes it on purpose: the MCU is a board sensor that barely follows the workload, and counting it would keep the fans up at idle.• npu-smi is slow, about 1.5 seconds per call, which is why polling=5 is the default. npu_smi_timeout= bounds a hung call.• Ascend cards tolerate high temperatures, so the default window is min_temp=40.0 to max_temp=85.0. |
| Target devices | Any PCI device that exposes a HWMON temperature and has no fan controller of its own, e.g. a 10 Gbit network card. |
| Temperature source | The Linux kernel HWMON interface. The devices are discovered in the udev database, and smfc searches the whole subtree of each card, so a HWMON device attached below the card is found too. |
| Device identification | Exactly one of pci_address= (a list of PCI slot addresses, e.g. 0000:05:00.0), pci_id= (one vendor:device ID, e.g. 1d6a:07b1) or pci_driver= (one driver name, e.g. atlantic) |
| Other parameters | temp_sensor= |
| Docker | Supported in every image. The driver of the card must be loaded on the host, like for the other HWMON-based controllers. |
| Need to know | • The controlled unit is a HWMON device, not a card. A card that exposes several HWMON devices counts as several units, and temp_calc= aggregates across all of them. A SATA controller with three disks behind it is one card but three units.• pci_driver= is the wildcard form: it selects every device the driver serves, so a second card of the same kind joins the section at the next smfc restart with no configuration change.• All addresses of a pci_address= list must be the same model. smfc stops at startup otherwise.• One section covers one kind of PCI device, because a section has one temperature window and one temp_sensor=. Two kinds, e.g. a network card and a storage controller, need two sections, and two sections of the same controller need two different IPMI zones (see chapter 1.4).• A card can expose several sensors, and temp_sensor= picks one by index. An Aquantia AQC107 reports the PHY temperature in temp1_input and the MAC temperature in temp2_input. List the labels of your machine with cat /sys/class/hwmon/hwmon*/temp*_label.• Not every PCI device has a HWMON temperature. The proprietary Nvidia driver exposes none (use the GPU fan controller), and some Wi-Fi cards report through the thermal subsystem instead, which is outside the PCI tree. smfc stops at startup and names the device in that case.• The default temp_calc=2 (maximum) differs from every other controller, so the hottest card of the section drives the fans. |
| Target devices | None. This controller has no temperature source. |
| Temperature source | None. It applies a fixed fan level to its IPMI zone(s). |
| Device identification | None. This controller drives no device. |
| Other parameters | level= -- the constant fan level |
| Docker | Supported in every image. It reads no sensor, but it drives the fans through ipmitool like every other controller, so the container still needs the /dev mount and admin privilege, or [Ipmi] remote_parameters=. |
| Need to know | • Its level acts as a guaranteed minimum on a shared zone. With [CONST] level=40 on zone 1, that zone never drops below 40%, even when every temperature-driven controller asks for less.• It ignores the parameters of the temperature-driven controllers ( temp_calc=, steps=, min_temp=, control_function= and the rest), because it never reads a temperature.• It is the only way to drive a zone that has no sensor at all, e.g. fans cooling a passive backplane. |
When multiple fan controllers are assigned to the same IPMI zone, smfc detects this at startup and automatically switches to a two-phase arbitration loop for those controllers. Controllers on non-shared zones are not affected -- they apply their fan levels directly.
At startup, smfc logs the detected shared zones at INFO level:
Shared IPMI zone 1: ['HD', 'NVME', 'CONST']
For shared zones, the control loop uses a two-phase approach in each iteration:
Controllers on non-shared zones skip the apply phase entirely -- they execute their own IPMI calls directly during the compute phase, just like they would if no sharing existed.
This means the highest requested fan level wins on a shared zone. For example, if the HD fan controller wants 45% on zone 1 and the NVME fan controller wants 70% on the same zone, smfc will set zone 1 to 70%. When the NVME cools down and its curve asks for less than 45%, the HD controller's level takes over again.
Usually this is also the hottest component, so "the hottest wins" is a good rule of thumb -- but "hottest" here means hottest relative to its own limits, not the highest number of degrees. Every controller compares its own temperature against its own [min_temp..max_temp] window, so degrees are not comparable between controllers: with the default windows a hard disk at 44 °C is nearly at its 46 °C ceiling and asks for close to max_level, while a CPU at 45 °C sits halfway through its 30..60 °C window and asks for roughly the middle of its range. The disk wins the zone even though it is the cooler device. Only the requested percentages are comparable, and those are what smfc compares.
The CONST fan controller also participates in the arbitration -- its constant level acts as a guaranteed minimum for its zone(s). For example, configuring [CONST] level=40 on zone 1 ensures that zone never drops below 40%, even if all temperature-driven controllers would request a lower value.
When a shared zone's level changes, the log output at INFO level shows the winning controller and lists all other controllers with their requested levels:
Shared IPMI zone [1]: new level = 70% (winner: NVME=70%/52.0C, losers: HD=45%/38.5C, CONST=40%)
For non-shared zones, only the applied level is logged:
IPMI zone [0]: new level = 60% (CPU=45.0C)
A single fan controller can only apply one temperature-to-level curve. When different IPMI zones need different curves for the same temperature source (e.g. quiet mid-plane fans vs. aggressive CPU coolers), you can create multiple instances of the same controller type by appending a colon and a number to the section name:
# Instance for zone 0 — conservative curve for noisy mid-plane fans
[CPU:0]
enabled=1
ipmi_zone=0
min_temp=55.0
max_temp=75.0
min_level=20
max_level=80
# Instance for zone 1 — aggressive curve for silent CPU coolers
[CPU:1]
enabled=1
ipmi_zone=1
min_temp=40.0
max_temp=80.0
min_level=20
max_level=100
Three naming styles are supported and can be freely mixed:
[CPU] — single instance, original format (unchanged behaviour)[CPU] + [CPU:1] — base section plus numbered extras[CPU:0] + [CPU:1] — all-numbered instancesThe suffix number after : is used only for ordering and logging — it has no relationship to the ipmi_zone= value inside the section. Each instance is a complete, independent fan controller with its own full set of parameters, sharing only the physical temperature source.
Two enabled instances of the same controller type must target different IPMI zones -- smfc rejects the configuration with an error otherwise. Instances of different controller types can share a zone, and those participate in the shared zone arbitration described in chapter 1.3.
While smfc is running, an external event (BMC web UI, a manual ipmitool command, a firmware quirk) can silently flip the BMC out of FULL mode. When that happens, smfc keeps sending per-zone level commands but the BMC ignores them and applies its own profile — fans run at unintended speeds with no error in the log. smfc detects this by checking the fan mode on every loop iteration. The [Ipmi] enforce_fan_mode= parameter controls the reaction: with 1 (default) the drift is logged and FULL mode plus all zone levels are re-asserted; with 0 the service exits with code 11 (add Restart=on-failure to the systemd unit if you want it restarted automatically in this mode).
On X14/H14 boards fan control is not acquired through FULL mode, so enforce_fan_mode= guards the per-zone manual latch instead. The reaction to a loss is the same, and a zone the BMC has forced to 100% after a fan failure counts as a loss too - see chapter 5.
See chapter 6 for the details of the FULL mode itself and the IPMI sensor thresholds it depends on.
While smfc is running the BMC stays in FULL mode, which means the BMC hands fan control over to smfc and does not
regulate anything itself. If the service stopped without touching the fans, the zones would stay frozen at the last
applied level with nothing regulating them, so smfc applies the [Ipmi] exit_level= value (default: 100, i.e. full
speed) to all configured zones before it exits. This happens on a normal systemctl stop (SIGTERM), on Ctrl-C
(SIGINT), and on every error exit. Note that no in-process handler can run on SIGKILL or when the OOM killer
terminates the service; use an ExecStopPost= drop-in in the systemd unit if you want to cover those cases too.
The exit_level= parameter accepts the [0-100] range plus the special value -1, which means "do not change the fan
levels" - smfc then exits without issuing any fan command and the zones stay at the last applied level, exactly like
the deprecated -ne command-line option did.
A lower value (e.g. exit_level=40) is a compromise for quiet systems: the fans keep running, but the machine does not
go to full speed every time the service is stopped or restarted.
[!IMPORTANT] X14/H14 motherboards behave differently. Instead of FULL mode they hold an explicit BMC fan control state (per-zone manual mode on OpenBMC, a global bypass flag on ATEN) that stays armed until
smfcreleases it. The exit is therefore two steps: (1)exit_levelis applied to all configured zones unless it is-1, then (2) the state is released and automatic BMC fan control is restored (on ATEN this also releases zonessmfcnever drove).Step (2) always runs, even with
exit_level=-1or after a failed step (1), because an armed state freezes every zone at its last duty with the BMC's thermal loop suspended.exit_levelitself has almost no effect: the BMC applies its own curve within about a second, so100and-1end up in the same place. That curve uses CPU and system sensors only - hard disk and NVMe temperatures are not part of it.
Fan controllers use user-defined control functions that map a temperature interval to a fan rotation level interval. Two forms are supported in each temperature-driven section: a simple linear mapping (chapter 2.1) or an advanced multi-segment piecewise-linear curve (chapter 2.2). When both are present in the same section, control_function= takes precedence and the min_temp/max_temp/min_level/max_level keys are ignored.
The simple form maps a single temperature interval [min_temp..max_temp] linearly to a single fan-level interval [min_level..max_level], divided into discrete plateaus by the steps= parameter:
steps=5
min_temp=30
max_temp=65
min_level=35
max_level=100
The dashed blue line shows the continuous linear ideal between (min_temp, min_level) and (max_temp, max_level); the solid red staircase is the digitalized output actually applied to the fan (here with steps=5, producing 6 plateaus: one pinned at each endpoint plus four in the interior).
Sample configurations using the linear form are available in
config/samples/— for examplesmfc-sample1.conf(CPU only),smfc-sample2.conf(HD with standby guard),smfc-sample4.conf(CPU + HD hybrid), andsmfc-sample8.conf(multiple fan curves per zone).
For more precise control, the control_function= parameter (available since smfc v6.0.0) lets you define a piecewise-linear fan curve directly as a list of temperature-level pairs:
steps=5
control_function = 30-35, 50-40, 60-90, 65-100
Each pair is written as T-L where T is a temperature in °C and L is a fan level in %. At least two pairs are required, temperatures must be strictly ascending, and all values must be in the range [0..100]. When control_function= is present in a section it takes precedence over min_temp=, max_temp=, min_level=, and max_level= — those keys are ignored (and not validated). The ignored state is reported at CONFIG log level.
The steps= parameter still applies: it controls how many discrete plateaus the interior of the curve is divided into before being sent to the fan. The two endpoint temperatures are always pinned exactly to their specified levels; the steps interior plateaus together with the 2 pinned endpoints produce steps + 2 plateaus in total.
The dashed blue line shows the continuous piecewise-linear ideal described by control_function=; the solid red staircase is the digitalized output actually applied to the fan (here with steps=5, producing 7 plateaus: one pinned at each endpoint plus five in the interior).
See
smfc-sample9.conffor a complete hybrid configuration usingcontrol_function=for both the CPU and HD fan controllers.
Changing fan rotational speed is a slow physical process — depending on the fan type and the magnitude of the change it can take several seconds. Frequent or unnecessary changes also cause audible oscillation. To keep the fans steady, each temperature-driven controller combines five mechanisms that act at different stages of the control loop:
| Stage | Mechanism | Parameter | Effect |
|---|---|---|---|
| Sample | Polling interval | polling= | Sets how often the controller reads the temperature. Larger values reduce the maximum rate of fan-level updates. |
| Smooth | Moving-average smoothing | smoothing= | Averages the last N temperature readings before they enter the control function. Suppresses brief spikes; 1 (default) disables smoothing. |
| Filter | Sensitivity threshold | sensitivity= | The controller does not react until the smoothed temperature has moved by at least this many °C since the last action. |
| Quantize | Discrete fan levels | steps= | The control function produces a fixed number of plateaus (linear: steps + 1, multi-segment: steps + 2) instead of a continuous curve, so small temperature drift inside a plateau yields the same fan level. |
| Apply | Post-change delay | [Ipmi] fan_level_delay= | After every fan-level change, the controller waits this many seconds before issuing another command, giving the fan time to reach the new speed physically. |
The mechanisms are independent and complementary: polling= and smoothing= work on the input side (how the temperature is measured), sensitivity= and steps= work on the decision side (whether and how a temperature maps to a fan level), and fan_level_delay= works on the output side (pacing the IPMI commands themselves).
The mechanisms above all decide whether a new temperature should move the fans. error_tolerance= answers a different question: what should happen when the temperature cannot be read at all?
Transient read errors have been reported in several different setups: with HBA/RAID controllers, with the kernel's drivetemp driver, and in docker environments. One well-understood case is drivetemp, which issues the SMART/ATA temperature command with a hard-coded 10-second timeout. While a disk is spinning up from STANDBY the command can exceed that timeout, and reading .../hwmon*/temp1_input returns EIO for a second or two (see issue #87). Note that the Standby guard feature is exactly what makes a disk array wake up in unison, so it correlates with this window.
Before version 6.1.0 a single failed read was fatal: smfc stopped and left the fans at 100%. Now the last known good temperature of that device is reused for up to error_tolerance= consecutive failed reads (int, default 3, 0 disables the tolerance), and the failure is logged at ERROR level with the current streak, the budget and the number of failed reads of that device since startup:
ERROR: HD: temperature read failed, reusing 33.0C (device=/dev/disk/by-id/..., 2/3, total=9): ...
INFO: HD: temperature read recovered after 2 failure(s) (device=/dev/disk/by-id/..., total=9)
A few properties worth knowing:
polling=: ~6 seconds for [CPU] (polling=2), ~30 seconds for [HD] (polling=10).smfc stop with the original error and the fans go to 100%.read_errors / read_errors_total fields in the HTTP exporter's snapshot and the smfc_device_temp_read_errors gauge / smfc_device_temp_read_errors_total counter in /metrics (see chapter 13.).For the HD fan controller, an additional optional feature was implemented, called Standby guard, with the following assumptions:
This feature monitors the power state of SATA hard disks (with the help of smartctl) and will put the whole array into standby mode if a few members have already stepped into that state. With this feature, the situation can be avoided where the array is partially in standby mode while other members are still active.
SCSI disks are not compatible with this feature.
The following table summarizes how the temperature is read for different disk types:
| Disk type | Temperature source | Kernel module | Command |
|---|---|---|---|
SATA | Linux kernel (HWMON) | drivetemp | - |
NVME | Linux kernel (HWMON) | - | - |
SAS/SCSI | smartctl | - | smartctl |
The
smfcservice was originally designed forSATAhard drives, but fromsmfc v3.0.0, it is also compatible withNVMEandSAS/SCSIdisks.smfc v5.0.0introduced a new NVME fan controller and separated the use of SATA and SAS/SCSI HDDs/SSDs and NVME SSD disks.
Some additional notes:
SATA disks the drivetemp kernel module should be loaded. This is the fastest way to read disk temperature, and the kernel module can report the temperature while hard disks are in sleep mode!SAS/SCSI disks the smartctl command will be used to read disk temperature.drivetemp module is not loaded or an HDD is not compatible with drivetemp module then smfc will use smartctl automatically.hd_names= configuration parameter but the Standby guard feature will not be supported in this case.NVME SSDs, no kernel driver needs to be loaded; the kernel can handle this disk type automaticallyOriginally, this software was designed to work with Supermicro X10-X12/H10-H12 motherboards with IPMI functionality, where the BMC chip is ASPEED AST2400 or AST2500. Most motherboards in this set are compatible with the Supermicro IPMI raw commands used here and are supported out of the box.
Newer X13/H13 motherboards with AST2600 chips are also compatible with smfc (see more details in issue #33 about
an X13SAE-F motherboard). The only difference is in the implementation of thresholds: the AST2600 chip implements only the
Lower Critical threshold, so setting up thresholds is different.
Some motherboards require platform-specific IPMI raw commands for fan control. smfc implements a platform abstraction
(since smfc v5.1.0) that handles these differences. The platform is auto-detected from BMC product name, or can be overridden with the
platform_name= configuration parameter (see chapter 10.2). Currently supported platforms:
platform_name= parameter | Platform | Notes |
|---|---|---|
auto | automatic discovery based on BMC information | Reads BMC product name; selects generic_x14 if it starts with X14 or H14, X10QBi if it starts with X10QBi, generic_x9 if it starts with X9, otherwise falls back to generic. If the BMC reports no usable product name (e.g. Unknown), no prefix matches and the fallback is generic -- set platform_name= by hand then. |
generic | Generic X10-X13/H10-H13 Supermicro boards | Uses standard Supermicro IPMI raw commands |
generic_x9 | Generic Supermicro X9 boards | 4 fan zones (0x10-0x13), duty cycle 0-255 scale |
generic_x14 | Supermicro X14 and H14 boards | A platform family covering both 14th generation BMC firmware stacks; the stack is detected at startup, not guessed from the board name (see doc/X14H14_MANUAL_FANCONTROL.md). Up to 5 fan zones (0-4) — smfc discovers how many the board really has — duty cycle 0-100%, and neither stack uses FULL fan mode — see the notes below. Experimental, see issue #98, discussion #106 |
X10QBi | Supermicro X10QBi motherboard | Nuvoton NCT7904D fan controller, 4 fan zones (0x10-0x13), duty cycle 0-255 scale, see PR #97 and discussion #110 |
With this abstraction layer, new Supermicro motherboards can also be added to smfc with a good understanding of their IPMI raw commands and fan control logic.
Some X9 motherboards are supported (since smfc v5.2.0) via the generic_x9 platform, provided they support the specific IPMI raw commands used for fan control. X9 boards are auto-detected when the BMC product name starts with X9; you can also force the platform by setting platform_name=generic_x9. The X10QBi platform is auto-detected when the BMC product name starts with X10QBi.
X14/H14 motherboard support (generic_x14) was introduced in smfc v6.0.0 and is currently in testing phase. The 14th generation ships two BMC firmware stacks, OpenBMC and ATEN; smfc detects the stack at startup and it cannot be forced from the configuration file. doc/X14H14_MANUAL_FANCONTROL.md covers both stacks, the board-to-stack table, and every raw command involved. What the split means for your configuration:
platform_name=auto fails if the BMC reports no product name. Auto-detection matches the X14/H14 prefix of the BMC product name. Some boards answer ipmitool bmc info with Product Name : Unknown, so no prefix matches, smfc falls back to generic, and it then drives the fans with commands these boards do not use -- it reports that it took control while the BMC keeps running its own curve. Check the product name with ipmitool bmc info; if it is Unknown or empty, set platform_name=generic_x14 by hand.FULL fan mode is not used. X14/H14 boards have their own manual fan control mode, and smfc holds that mode instead. enforce_fan_mode= therefore counts the drift of that manual mode, not of the fan mode.exit_level= behaves differently on these boards. See chapter 1.6.If you own an X14 or H14 board and test smfc, please share your experience in discussion #106.
The earlier X8 motherboards are NOT compatible with this software. They do not implement IPMI FULL mode, and they cannot control fan levels with IPMI raw commands.
Feel free to create a short feedback in discussion #55 on your compatibility experience.
This chapter describes the RPM sensor-threshold/assertion model of the older AST2400/2500/2600-based BMCs (
generic,generic_x9,X10QBiplatforms). Whether and how it applies to the X14/H14 boards (generic_x14platform) is not yet confirmed — see chapter 5 and share your findings in discussion #106.
On Supermicro X10-X11 motherboards IPMI uses six sensor thresholds to specify the safe and unsafe fan rotational speed intervals (these are RPM values rounded to the nearest hundreds, defined for each fan separately):
Lower Non-Recoverable
Lower Critical
Lower Non-Critical
Upper Non-Critical
Upper Critical
Upper Non-Recoverable
but newer Supermicro X13 motherboards (with AST2600 BMC chip) have only one sensor threshold:
Lower Critical
Originally, this chapter was created for Supermicro X10-X11 motherboards, but can be easily adapted to X13 motherboards as well (see more details in #33).
Like many other utilities (created by NAS and home server community), smfc also uses IPMI FULL mode for fan control, where all fans in the zone:
[Lower Critical, Upper Critical] intervalLower Critical or Upper Critical threshold then IPMI will generate an assertion event and will set all fan speeds back to 100% in the zone
smfckeeps the BMC in FULL mode while it runs and restores a level of your choice when it exits — see chapter 1.5 and chapter 1.6 for those two topics.
Please also consider the fact that fans are mechanical devices, their rotational speed is not stable (it could be fluctuating). To avoid IPMI's assertion mechanism described here please follow the next steps:
min_level/max_level values for smfc respecting the variance of all fans in the IPMI zone (it could take several iterations and adjustments)
Here is a real-life example for a Noctua NF-F12 PWM fan:
Upper Non-Recoverable = 1800 rpm
Upper Critical = 1700 rpm
Upper Non-Critical = 1600 rpm
Lower Non-Critical = 200 rpm
Lower Critical = 100 rpm
Lower Non-Recoverable = 0 rpm
Max RPM = 1500 rpm
Min RPM = 300 rpm
max_level = 100 (i.e. 1500 rpm)
min_level = 35 (i.e. 500 rpm)
Notes:
Use the following ipmitool command to display the current IPMI sensor thresholds for fans:
root@home:~# ipmitool sensor|grep FAN
FAN1 | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FAN2 | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FAN3 | na | | na | na | na | na | na | na | na
FAN4 | 400.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FANA | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FANB | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
Use the following ipmitool command to list assertion events:
root@home:~# ipmitool sel list
1 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
2 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Asserted
3 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Deasserted
4 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Critical going low | Deasserted
5 | 10/19/2023 | 05:20:59 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
Use the following ipmitool commands to specify all six sensor thresholds for FAN1:
root@home:~# ipmitool sensor thresh FAN1 lower 0 100 200
root@home:~# ipmitool sensor thresh FAN1 upper 1600 1700 1800
You can also edit and run ipmi/set_ipmi_threshold.sh to configure all IPMI sensor thresholds
If you install new BMC firmware on your Supermicro motherboard, you have to configure IPMI thresholds again
If you do not see fans when executing ipmitool sensors, you may want to reset the BMC to factory default using the Web UI or using ipmitool mc reset cold
Noctua specifies the variance of minimum and maximum fan rotational speeds (e.g. see the specification of Noctua NF-F12 PWM). For example:
Rotational speed (+/- 10%) 1500 RPM: 1350-1650 RPM intervalMin. rotational speed @ 20% PWM (+/-20%) 300 RPM: 240-360 RPM intervalPlease note that LNA/ULNA cables or Y-cables can modify the rotational speed calculations here and the required IPMI sensor thresholds too.
You can read more about:
If low noise and low heat generation are important attributes of your Linux box, then you may consider the following chapters.
Most modern CPUs have multiple energy-saving features. You can check your BIOS and enable them to minimize the heat generation.
Intel(R) CPUs:
AMD(R) CPUs:
With this setup the CPU will change its base frequency and power consumption dynamically based on the load.
In case of SATA hard disks, you may enable:
With the help of command hdparm you can enable advanced power management and specify a spin down timer (read more here):
hdparm -B 127 /dev/sda
hdparm -S 240 /dev/sda
In file /etc/hdparm.conf you can specify all parameters persistently:
quiet
/dev/sda {
apm = 127
spindown_time = 240
}
/dev/sdb {
apm = 127
spindown_time = 240
}
...
Important notes:
[HD] polling= to at least twice as big as the spindown_time specified here./etc/hdparm.conf you must define HD names in /dev/disk/by-id/... form to avoid inconsistency.One or more of the following Linux kernel modules need to be loaded for smfc:
coretemp: temperature report for Intel(R) CPUsk10temp: temperature report for AMD(R) CPUsdrivetemp: temperature report for SATA hard disks (available from kernel 5.6+ version)Use /etc/modules file for persistent loading of these modules.
Notes:
drivetemp module is the fastest way to get the temperature of the hard disks, and it can read temperature of the SATA hard disks even if they are in standby mode.For the installation and uninstallation, you need root privileges. There are several ways to install and uninstall smfc, this chapter will show them.
Pre-built .deb packages are available from the smfc-deb APT repository, hosted on GitHub Pages and signed with a dedicated GPG key.
The repository can be added in two ways.
a) deb822 format (recommended)
sudo curl -fsSL https://petersulyok.github.io/smfc-deb/smfc.sources \
-o /etc/apt/sources.list.d/smfc.sources
sudo apt update && sudo apt install smfc
The smfc.sources file contains the repository signing key, so it is a single, self-contained file and no separate keyring is needed. Embedded keys require apt 2.4 or newer, available on all supported distributions (Debian 12+, Ubuntu 22.04+).
b) One-line format with a separate key file
curl -fsSL https://petersulyok.github.io/smfc-deb/smfc-repo.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/smfc-repo.gpg
echo "deb [arch=amd64,arm64 signed-by=/etc/apt/keyrings/smfc-repo.gpg] https://petersulyok.github.io/smfc-deb stable main" \
| sudo tee /etc/apt/sources.list.d/smfc.list
sudo apt update && sudo apt install smfc
To update: sudo apt update && sudo apt upgrade smfc.
To remove:
sudo apt remove smfc
sudo rm -f /etc/apt/sources.list.d/smfc.sources # a)
sudo rm -f /etc/apt/sources.list.d/smfc.list /etc/apt/keyrings/smfc-repo.gpg # b)
Note: if you added the repository with b) earlier and switch to a), delete the files of the old format (smfc.list and smfc-repo.gpg), otherwise the repository is configured twice.
Compatible with Debian 12+, Ubuntu 22.04+. See the smfc-deb README for the full distribution list. The package installs the same files as the manual installation (service unit, configuration, man page, sample configs). Configuration files under /etc/ are preserved on upgrade. See PACKAGES.md for build-from-source instructions.
The DEB package enables the smfc service but does not start it on installation. First review your configuration (see chapter 10.), then start the service manually (see chapter 11.); from then on it starts automatically on every boot. On upgrade the service is restarted if it was running, and left stopped if you had stopped it.
Pre-built .rpm packages are available from the smfc-rpm DNF repository, hosted on GitHub Pages and signed with a dedicated GPG key.
Add the repository and install:
sudo dnf config-manager addrepo --from-repofile=https://petersulyok.github.io/smfc-rpm/smfc.repo
sudo dnf install smfc
(On older dnf versions: sudo dnf config-manager --add-repo=https://petersulyok.github.io/smfc-rpm/smfc.repo.)
To update: sudo dnf upgrade smfc.
To remove:
sudo dnf remove smfc
sudo rm /etc/yum.repos.d/smfc.repo
Compatible with Fedora 39+, RHEL/Rocky/AlmaLinux 9+ (with EPEL), CentOS Stream 9+, openSUSE Leap 15.5+. See the smfc-rpm README for the full distribution list. The package installs the same files as the manual installation. Configuration files are preserved on upgrade. See PACKAGES.md for build-from-source instructions.
The RPM package enables the smfc service but does not start it on installation. First review your configuration (see chapter 10.), then start the service manually (see chapter 11.); from then on it starts automatically on every boot. On upgrade the service is restarted if it was running, and left stopped if you had stopped it.
An smfc package is available in the AUR. It is created and maintained by
an Arch Linux community member (urirocky), and it is currently based on the v6.0.0 release.
Install it with your preferred AUR helper:
yay -S smfc
or manually:
git clone https://aur.archlinux.org/smfc.git
cd smfc
makepkg -si
Please report package-specific issues (PKGBUILD, dependencies, file locations) on the AUR package page.
smfc is also available as a docker image, see more details in DOCKER.md. In this case, your job is only to provide your configuration file on the host computer, smfc will be executed automatically when the container is starting.
There is an installation script (bin/install.sh) which can install smfc in two different ways:
The installation script requires curl, pip, gzip, and mandb commands, and it has the following
command line parameters:
user@host:~$ ./install.sh --help
usage: install.sh [-h|--help] [-k|--keep-config] [-l|--local] [-v|--verbose]
-h, --help help text
-k, --keep-config keep original configuration file
-l, --local installation from a local git repository
-v, --verbose verbose output
The default location of the installed files:
| Files | Installation folder | Description |
|---|---|---|
smfc.service | /etc/systemd/system | systemd service definition file |
smfc | /etc/default | service command line options |
smfc.conf | /etc/smfc | service configuration file |
smfc.1.gz | /usr/local/share/man/man1 | smfc manual page |
smfc-client.1.gz | /usr/local/share/man/man1 | smfc-client manual page |
smfc | /usr/local/bin or /usr/bin | smfc command |
smfc-client | /usr/local/bin or /usr/bin | smfc-client command |
smfc service | /usr/local/lib/python3.xx or /usr/lib/python3.xx | smfc python package |
Notes for the script:
--local parameter installs from the current folder (the GitHub repository must be cloned first)hd_names= configuration parameter is pre-filled with the list of existing hard disks for the user's convenience
(please check/edit this parameter!)--keep-config parameter, the original configuration file is preserved--verbose parameter, the installation phases are displayedFor remote installation the script can be executed (as root user) this way:
curl --silent https://raw.githubusercontent.com/petersulyok/smfc/refs/heads/main/bin/install.sh|bash /dev/stdin --verbose
or if you want to preserve your existing configuration file:
curl --silent https://raw.githubusercontent.com/petersulyok/smfc/refs/heads/main/bin/install.sh|bash /dev/stdin --verbose --keep-config
For local installation, follow these steps (the uv command is also required):
git clone https://github.com/petersulyok/smfc.git
cd smfc
uv sync
uv build
./bin/install.sh --local --verbose --keep-config
This clones the GitHub repository and builds the smfc package locally before installation.
There is also an uninstallation script (bin/uninstall.sh) for removing smfc.
It has the following command line parameters:
user@host:~$ ./uninstall.sh --help
usage: uninstall.sh [-h|--help] [-k|--keep-config] [-v|--verbose]
-h, --help help text
-k, --keep-config keep original configuration files
-v, --verbose verbose output
It can be executed locally or remotely, just like the installation script. Example of remote execution:
curl --silent https://raw.githubusercontent.com/petersulyok/smfc/refs/heads/main/bin/uninstall.sh|bash /dev/stdin --verbose
The script removes the installed smfc files and the Python package. With the --keep-config parameter, the original
configuration files (/etc/smfc/smfc.conf and /etc/default/smfc) are preserved.
After successful installation, create/edit your new configuration file. Its default location is /etc/smfc/smfc.conf (and command line options live in /etc/default/smfc). If you just upgraded to a new smfc version, you can preserve the existing one.
You have to think over and answer the following questions:
smfc?s-tui, fio, iozone) if you don't have their track records.The configuration file contains sections. The first one for IPMI configuration, the rest for fan controllers.
Edit /etc/smfc/smfc.conf and specify your configuration parameters here:
#
# smfc.conf (C) 2020-2026, Peter Sulyok
# smfc 6.x service configuration parameters
#
# Please read the documentation here: https://github.com/petersulyok/smfc
#
# Ipmi specific parameters.
[Ipmi]
# Path for ipmitool (str, default=/usr/bin/ipmitool)
command=/usr/bin/ipmitool
# Delay time after changing IPMI fan mode (int, seconds, default=10)
fan_mode_delay=10
# Delay time after changing IPMI fan level (int, seconds, default=2)
fan_level_delay=2
# IPMI parameters for remote access (string, default='')
#remote_parameters=-U USERNAME -P PASSWORD -H HOST
# Supermicro platform (string, default='auto')
# Valid platform values:
# auto - automatic discovery based on BMC information
# (an X14/H14 board reporting an unknown product name needs generic_x14 explicitly)
# generic - Generic Supermicro X10-X13/H10-H13 platform
# generic_x9 - Generic Supermicro X9 platform
# generic_x14 - Supermicro X14/H14 platform family (the BMC firmware stack -
# OpenBMC or ATEN - is detected at startup, see README chapter 5)
# X10QBi - Supermicro X10QBi platform
platform_name=auto
# Re-assert FULL fan mode (bool, default=1/true)
enforce_fan_mode=1
# Fan level applied to all configured zones at service termination (int, [-1..100]%, default=100)
# Use -1 to leave the fan levels untouched. On X14/H14 boards this level is only a transition: at exit
# the BMC takes back fan control and rewrites fan levels.
exit_level=100
# Timeout of a single ipmitool execution (int, seconds, default=10, 0 = wait indefinitely)
# It stops a wedged /dev/ipmi0 from parking the control loop; a timed-out call counts as an IPMI error.
#ipmitool_timeout=10
# CPU fan controller: works based on CPU(s) temperature.
[CPU]
# Fan controller enabled (bool, default=0/false)
enabled=1
# IPMI zone(s) (comma- or space-separated list of int, default=0)
ipmi_zone=0
# Calculation method for CPU temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=3.0)
sensitivity=3.0
# Polling time interval for reading temperature (int, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=6)
steps=6
# Minimum CPU temperature (float, C, default=30.0)
min_temp=30.0
# Maximum CPU temperature (float, C, default=60.0)
max_temp=60.0
# Minimum CPU fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum CPU fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# HD fan controller: works based on SATA or SAS HDDs/SSDs temperature.
[HD]
# Fan controller enabled (bool, default=0/false)
enabled=1
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of HD temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (int, sec, default=10)
polling=10
# Discrete steps in mapping of temperatures to fan level (int, default=4)
steps=4
# Minimum HD temperature (float, C, default=32.0)
min_temp=32.0
# Maximum HD temperature (float, C, default=46.0)
max_temp=46.0
# Minimum HD fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum HD fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# Names of the HDs (str multi-line list, default=)
# MUST BE specified in '/dev/disk/by-id/...' form, for example:
# hd_names=/dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_8CH7T91E
# /dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_8CH7T813
# /dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_8CHUFFRE
hd_names=
# Path for 'smartctl' command (str, default=/usr/sbin/smartctl).
smartctl_path=/usr/sbin/smartctl
# Standby guard feature for RAID arrays (bool, default=0/false)
standby_guard_enabled=0
# Number of HDs already in STANDBY state before the full RAID array will be forced to it (int, default=1)
standby_hd_limit=1
# NVME fan controller: works based on NVMe SSD(s) temperature.
[NVME]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of NVMe temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (int, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=4)
steps=4
# Minimum NVMe temperature (float, C, default=35.0)
min_temp=35.0
# Maximum NVMe temperature (float, C, default=70.0)
max_temp=70.0
# Minimum NVMe fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum NVMe fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# Names of the NVMe devices (str multi-line list, default=)
# MUST BE specified in '/dev/disk/by-id/...' form, for example:
# nvme_names=/dev/disk/by-id/nvme-ADATA_LEGEND_650_2OFF29AO8DKR
# /dev/disk/by-id/nvme-CT4000P3PSSD7_2446E89408FA
nvme_names=
# GPU fan controller: works based on Nvidia or AMD GPU(s) temperature.
[GPU]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# GPU type (str, ['nvidia', 'amd'], default=nvidia)
gpu_type=nvidia
# AMD GPU temperature sensor (int, 0-junction, 1-edge, 2-memory, default=0)
amd_temp_sensor=0
# Calculation of GPU temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (int, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=5)
steps=5
# Minimum GPU temperature (float, C, default=40.0)
min_temp=40.0
# Maximum GPU temperature (float, C, default=70.0)
max_temp=70.0
# Minimum GPU fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum GPU fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# GPU device IDs (comma- or space-separated list of int, default=0)
# These are indices in nvidia-smi temperature report.
gpu_device_ids=0
# Path for 'nvidia-smi' command (str, default=/usr/bin/nvidia-smi).
nvidia_smi_path=/usr/bin/nvidia-smi
# Path for 'rocm-smi' command (str, default=/usr/bin/rocm-smi)
rocm_smi_path=/usr/bin/rocm-smi
# NPU fan controller: works based on Ascend NPU(s) temperature (npu-smi), e.g. Atlas 300I Duo.
# A device is an NPU card (npu-smi -i id); for multi-chip cards the hottest chip is used.
[NPU]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of NPU temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (float, sec, default=5; npu-smi is slow, ~1.5s/call)
polling=5
# Discrete steps in mapping of temperatures to fan level (int, default=5)
steps=5
# Minimum NPU temperature (float, C, default=40.0)
min_temp=40.0
# Maximum NPU temperature (float, C, default=85.0)
max_temp=85.0
# Minimum NPU fan level (int, %, default=35)
min_level=35
# Maximum NPU fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=50-40, 70-70, 85-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# NPU card IDs (comma- or space-separated list of int, default=0)
# These are the '-i' card IDs reported by `npu-smi info -m` (may not start from 0).
npu_device_ids=0
# Path for 'npu-smi' command (str, default=npu-smi). May be a bare command name (resolved via PATH)
# or a full path (e.g. /usr/local/Ascend/ascend-toolkit/latest/bin/npu-smi).
npu_smi_path=npu-smi
# Timeout for a single npu-smi call (float, sec, default=15.0)
npu_smi_timeout=15.0
# PCI fan controller: works based on the HWMON temperature of PCI device(s).
[PCI]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of PCI temperatures (int, [0-minimum, 1-average, 2-maximum], default=2)
temp_calc=2
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (float, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=6)
steps=6
# Minimum PCI temperature (float, C, default=30.0)
min_temp=30.0
# Maximum PCI temperature (float, C, default=60.0)
max_temp=60.0
# Minimum PCI fan level (int, %, default=35)
min_level=35
# Maximum PCI fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=45-40, 65-70, 80-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# The PCI device(s) of this section. Specify exactly one of the three parameters below.
# PCI slot address(es) (comma- or space-separated list of str, default=empty)
# All of them must be the same model, and a card that is not present is an error.
#pci_address=0000:05:00.0, 0000:06:00.0
# PCI vendor:device ID (str, four hexadecimal digits on each side, default=empty)
# Selects every card of that model.
#pci_id=1d6a:07b1
# PCI driver name (str, default=empty)
# Selects every PCI device the driver serves.
#pci_driver=atlantic
# HWMON sensor index to read (int, default=1, reads temp1_input)
# A card can expose several sensors. Read the labels with `cat /sys/class/hwmon/hwmon*/temp*_label`.
temp_sensor=1
# CONST fan controller: sets constant fan level (without any heat source) for IPMI zones(s).
[CONST]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Polling interval for checking/resetting level if needed (int, sec, default=30)
polling=30
# Constant fan level (int, %, default=50)
level=50
# HTTP exporter: serves /snapshot (JSON for smfc-client) and /metrics (Prometheus text format).
[Exporter]
# Enable the HTTP exporter (bool, default=0/false)
enabled=0
# IP to bind on (str, default=127.0.0.1)
# Use 127.0.0.1 for local-only access; use 0.0.0.0 or a specific LAN IP for remote Prometheus.
bind_address=127.0.0.1
# TCP port (int, 1..65535, default=9099)
port=9099
Important notes:
[Ipmi] remote_parameters=-U USERNAME -P PASSWORD -H HOST parameter can be used for remote access for the IPMI interface. It could be useful for a VM setup where the hard disks are configured with PCI passthrough (e.g. a TrueNAS running in a VM on Proxmox), but IPMI needs to be accessed "remotely". Please note that the HOST is the BMC network address (not the VM host address).[HD] hd_names= is a compulsory parameter for HD fan controller, and it must be specified in /dev/disk/by-id/... form. Please note that the /dev/sda form is not persistent and could change after a reboot![NVME] nvme_names= is a compulsory parameter for NVME fan controller, and it must be specified in /dev/disk/by-id/... form. Please note that the /dev/nvme0n1 form is not persistent and could change after a reboot![CPU] / [HD] / [NVME] min_level= / max_level= should be configured in alignment with threshold configuration (see more details in this chapter). Be patient, several refinement cycles could happen.[CPU] / [HD] / [NVME] / [GPU] / [NPU] control_function= defines an advanced multi-segment user-defined control function as a list of temp-level value pairs (at least 2 pairs, temperatures strictly ascending). When specified, it overrides the linear min_temp/max_temp/min_level/max_level form. See chapter 2.2 for details.[CPU:0], [CPU:1]). Each instance has its own full set of parameters and can be assigned to a different IPMI zone with a different fan curve. Two enabled instances of the same type must not share the same IPMI zone. See chapter 1.4 for details../config/samples folder.smfc.smfc?After manual installation, smfc can be started and stopped as a standard systemd service. Remember to reload systemd configuration after a new installation or if you changed the service definition file:
systemctl daemon-reload
systemctl start smfc.service
systemctl stop smfc.service
systemctl restart smfc.service
systemctl status smfc.service
● smfc.service - Supermicro Fan Control
Loaded: loaded (/etc/systemd/system/smfc.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-03-03 21:32:22 CET; 7min ago
Invocation: b613d841e1cf43f4ace80d472623ed4c
Main PID: 82521 (smfc)
Tasks: 1 (limit: 154231)
Memory: 12M (peak: 19.5M)
CPU: 1.271s
CGroup: /system.slice/smfc.service
└─82521 /usr/bin/python3 /usr/local/bin/smfc -c /etc/smfc/smfc.conf -l 3
Mar 03 21:37:27 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 67% (winner: CPU=67%/51.0C, losers: NVME=35%/37.9C)
Mar 03 21:37:30 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 35% (winner: CPU=35%/31.0C, losers: NVME=35%/37.9C)
Mar 03 21:38:46 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 67% (winner: CPU=67%/49.0C, losers: NVME=35%/37.9C)
Mar 03 21:38:49 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 35% (winner: CPU=35%/31.0C, losers: NVME=35%/37.9C)
The smfc program has the following parameters:
root@nas$ smfc --help
usage: smfc [-h] [-c CONFIG_FILE] [-v] [-l {0,1,2,3,4}] [-o {0,1,2}] [-nd] [-s] [-ne]
options:
-h, --help show this help message and exit
-c CONFIG_FILE configuration file (default is /etc/smfc/smfc.conf)
-v, --version show program's version number and exit
-l {0,1,2,3,4} set log level: 0-NONE, 1-ERROR(default), 2-CONFIG, 3-INFO, 4-DEBUG
-o {0,1,2} set log output: 0-stdout, 1-stderr, 2-syslog(default)
-nd no dependency checking at start
-s use sudo command
-ne deprecated, use [Ipmi] exit_level=-1 instead
[!NOTE] The
-neoption is deprecated. It is still accepted and still means "no fan level change at exit", but it is equivalent to the[Ipmi] exit_level=-1configuration parameter and it will be removed in a future release.
smfc command-line options can be specified in /etc/default/smfc file if you run smfc as a systemd service.
If you are testing your configuration, you can start smfc directly in a terminal (logging to the standard output on debug log level):
smfc -o 0 -l 3
In case of Docker installation, smfc will be executed automatically when the container is started. Its command-line parameters can be specified in the docker-compose file.
All messages will be logged to the specific output and the specific level.
With the help of command journalctl you can check logs easily. For example:
listing service logs of the last two hours:
journalctl -u smfc --since "2 hours ago"
listing service logs from the last boot:
journalctl -b -u smfc
When enabled, smfc exposes live fan and temperature data over HTTP. This powers two use cases: smfc-client reads it to give you an instant status overview from the command line, and Prometheus can scrape it to feed a Grafana dashboard with historical charts and alerts.
The exporter is disabled by default. Enable it in smfc.conf:
[Exporter]
enabled=1
bind_address=127.0.0.1 # change to 0.0.0.0 (or a specific LAN IP) for remote access
port=9099
The exporter implements three endpoints:
/metrics — for Prometheus and Grafana: feeds dashboards and alerting rules with live fan and temperature data./snapshot — for smfc-client and ad-hoc inspection: delivers the same data as a structured JSON object./healthz — for monitoring and orchestration: confirms the service is up and responding.Verify locally:
curl -s http://127.0.0.1:9099/metrics
curl -s http://127.0.0.1:9099/snapshot | jq .
curl -s http://127.0.0.1:9099/healthz
All data is served from the daemon's already-cached state — no ipmitool or smartctl subprocesses are spawned per request, so querying the exporter can never wake disks that smfc has put to sleep. A bind failure (e.g. port already in use) is logged but does not stop the fan-control loop.
For Grafana integration with a ready-to-import dashboard and a full monitoring stack setup, see grafana/GRAFANA.md.
smfc-client gives you an instant read-only view of what smfc is currently doing: temperatures, fan levels, IPMI zone states, and standby status — all in one command. Use it to confirm your configuration is working as expected without digging through system logs. It is safe to run at any time and never changes any fan state.
smfc-clientis shipped withsmfc v6.0.0and later
It reads the same configuration file as the service (/etc/smfc/smfc.conf by default), so it always reports on the same controllers and zones the service manages. There are two data sources, selected automatically:
[Exporter] section is enabled in the configuration (see chapter 13.), smfc-client fetches the /snapshot JSON from the running service. This is dramatically faster because it serves already-cached state and spawns no ipmitool/smartctl subprocesses (so it can never wake disks the daemon has put to sleep).--standalone is given, smfc-client reads the BMC and disks directly via ipmitool/hwmon/smartctl. This path typically needs root, so run it with sudo smfc-client -s.The first lines of the output state which source was used, and say only what was established: source: smfc service (live snapshot) online; source: ipmitool (smfc service is not reachable) when the exporter was asked and did not answer; and source: ipmitool (smfc service not queried) when the BMC was read directly without asking at all, which is what --standalone and a configuration without an exporter give - smfc may well be running in that case. The online report additionally shows the service uptime (verbose only) and annotates the fan-mode line with how many times fan control was re-asserted and how old the reading is. It colours that line by the control state the service reported, not by the fan mode value, and prints the reason when control was lost; the offline report reads the IPMI fan mode live and warns if the BMC is not in FULL mode. On X14/H14 boards the fan-mode line reports what is actually driving the fans instead - see chapter 5.
The report has two modes:
--verbose (-V) unfolds the full picture: the complete BMC fingerprint, the service uptime, and a per-controller block below the Fan controllers table showing the steering window ([temp_min..temp_max]C → [level_min..level_max]%), the active LUT (a Curve: line for controllers configured with control_function=...), the per-device temperatures, and the Standby Guard status folded into the HD block.When stdout is a terminal and --no-color is not set, the report is colourised:
BMC, Fan controllers, [CPU]/[HD]/[NVME]/[GPU]/[NPU], IPMI zones (live)) render in bold bright-blue.Temp and Level cell is banded against the controller's own steering window: DIM below the floor (idle), GREEN in the lower 70 % (working), YELLOW in the upper 30 % (warm — fans ramping), RED at or above the ceiling (curve maxed out). The per-device temperatures inside the verbose Device list use the same banding against the parent controller's window — handy for spotting a single hot disk dragging the aggregate up.STANDBY in dim grey and ACTIVE in green.FULL in green and any other mode in red, accompanied by a warning.| Flag | Long form | Argument | Default | Description |
|---|---|---|---|---|
-c FILE | --config FILE | path | /etc/smfc/smfc.conf | Configuration file to read (same format the service uses). |
-s | --sudo | — | off | Run ipmitool and smartctl via sudo. Required on the standalone path as non-root. |
-V | --verbose | — | off | Expand each enabled fan controller into a per-controller block with window, curve, devices, standby state, and per-device read error counts. The read error counts are shown only in this mode — the default summary has no per-device rows. |
-nc | --no-color | — | auto | Disable ANSI colors. Colors auto-disable when stdout is not a terminal. |
-sa | --standalone | — | off | Bypass the service exporter and read sensors directly. |
-v | --version | — | — | Print smfc-client X.Y.Z and exit. |
-h | --help | — | — | Show help and exit. |
Exit codes: 0 = readings printed (per-controller errors are non-fatal), 6 = configuration file missing or invalid, 8 = IPMI/BMC error (e.g. ipmitool not found or permission denied), 9 = disk detection unavailable (udev missing).
Default (non-verbose) — the at-a-glance summary:
smfc-client 6.0.0
config: /etc/smfc/smfc.conf
source: smfc service (live snapshot)
BMC
Product : X11SCH-LN4F (6929)
Fan mode : FULL (1) (enforced 0x, read 0.3s ago)
Fan controllers
Section Type Zones Devices Temp Level
------- ----- ----- ------- ------ -----
CPU cpu [0] 1 51.0 C 47 %
HD hd [1] 4 39.0 C 45 %
CONST const [2] - - 50 %
IPMI zones (live)
Zone Level
---- -----
0 47 %
1 45 %
2 50 %
The Level column shows what each controller asks for, while the IPMI zones (live) table shows what each zone actually runs at. The two agree here, but on a shared zone only the winner's request is applied, so an overruled controller's Level stays below its zone. Run with -V to see which controller won.
With --verbose (-V) the full report expands the BMC fingerprint, adds the service uptime, and emits one block per enabled fan controller with its steering window, active curve (when a control_function is configured), and per-device temperatures. The HD controller's Standby Guard line is folded into its block; CONST controllers stay in the Fan controllers table but don't get their own block (no devices, no curve):
smfc-client 6.0.0
config: /etc/smfc/smfc.conf
source: smfc service (live snapshot)
uptime: 1d 00:00:00
BMC
Manufacturer : Super Micro Computer Inc. (10876)
Product : X11SCH-LN4F (6929)
Firmware : 1.74
IPMI version : 2.0
Platform : GenericPlatform
Fan mode : FULL (1) (enforced 0x, read 0.3s ago)
Fan controllers
Section Type Zones Devices Temp Level
------- ----- ----- ------- ------ -----
CPU cpu [0] 1 51.0 C 47 %
HD hd [1] 4 39.0 C 45 %
CONST const [2] - - 50 %
[CPU] cpu zone(s)=[0] shared=no polling=2.0s
Window: T=[35..75]C → L=[35..100]%
Curve: 35→35, 55→50, 70→80, 75→100
Temp: 51.0 C → Level: 47 %
Device Temp
------ ------
cpu0 51.0 C
[HD] hd zone(s)=[1] shared=no polling=960.0s
Window: T=[35..48]C → L=[35..100]%
Temp: 39.0 C → Level: 45 %
Standby Guard: enabled (limit=2) Array: SAAA (1/4 standby)
Device Temp State Errors
---------------------------------- ------ ------- ------
ata-WDC_WD120EFAX-68UNTN0_99GMFQVW 36.0 C STANDBY 9
ata-WDC_WD120EFAX-68UNTN0_ASWRX1X8 38.0 C ACTIVE 0
ata-WDC_WD120EFAX-68UNTN0_F9ZAPZG7 39.0 C ACTIVE 0
ata-WDC_WD120EFAX-68UNTN0_MPZ04PTK 39.0 C ACTIVE 0
IPMI zones (live)
Zone Level
---- -----
0 47 %
1 45 %
2 50 %
On a shared IPMI zone (see chapter 1.3) the report keeps what a controller asked for separate from what its zone actually got. Below, CPU and NVME both drive zone 0: the CPU wins the arbitration with 74%, while the NVME -- whose window maps 39.9 °C to its 35% floor -- is overruled. The Level column and the Temp: → Level: line always show the controller's own request, so they never contradict the Window:/Curve: lines; the level that actually reached the BMC is appended to the overruled controller's block as (zone 0 applied: 74 %). Only the banner and BMC block are omitted here:
Fan controllers
Section Type Zones Devices Temp Level
------- ---- ----- ------- ------ -----
CPU cpu [0] 1 65.4 C 74 %
NVME nvme [0] 3 39.9 C 35 %
[CPU] cpu zone(s)=[0] shared=yes polling=2.0s
Window: T=[40..75]C → L=[35..100]%
Curve: 40→35, 55→50, 70→80, 75→100
Temp: 65.4 C → Level: 74 %
Device Temp
------ ------
cpu0 65.4 C
[NVME] nvme zone(s)=[0] shared=yes polling=2.0s
Window: T=[38..65]C → L=[35..100]%
Temp: 39.9 C → Level: 35 % (zone 0 applied: 74 %)
Device Temp
---------------------------------- ------
nvme-CT4000P3PSSD8_2412E7B1C4A9 39.9 C
nvme-CT4000P3PSSD8_2412E7B1D5F2 37.9 C
nvme-ADATA_LEGEND_800_2Q7714KX3ZTM 38.9 C
IPMI zones (live)
Zone Level
---- -----
0 74 %
A few things to notice in the verbose block:
shared=yes/no tells you whether this controller shares its IPMI zone with another controller and therefore goes through zone arbitration. It is reported for every participant on a shared zone, including the one currently winning it -- it is not a winner/loser flag. To see who is actually driving the zone, compare the block's Level: with the (zone N applied: ...) note described below.Window: and Curve: describe the active steering curve. When a control_function=... is configured, Window: shows the curve's actual [temp_min..temp_max] → [level_min..level_max] envelope (not the legacy min_temp/max_temp keys, which the runtime ignores in this mode), and Curve: lists the breakpoint pairs directly. Controllers without a control_function (legacy linear mode) skip the Curve: line — the Window: already says everything.Temp: X → Level: Y is the aggregated temperature the curve was evaluated against and the level this controller requested as a result -- so it always agrees with the Window:/Curve: lines above it. On a shared zone the arbiter applies the highest request across all contributors, so a losing controller's request is not what reaches the BMC; in that case the applied zone level is appended as (zone N applied: Z %). The note is omitted when the two agree (non-shared zones, and the winner of a shared zone). The applied level of every zone is also listed in the IPMI zones (live) table at the end of the report. --standalone mode cannot show who won the zone: it reports the current state only, so every controller simply displays its zone's level and the (zone N applied: ...) note never appears. With colours on, both cells carry the band colour against the same window — at a glance you see whether the controller is idle, working, ramping, or maxed out.Device names for HD and NVMe controllers are shown as the path basename (e.g. ata-WDC_WD120EFAX-68UNTN0_99GMFQVW instead of /dev/disk/by-id/ata-WDC_WD120EFAX-68UNTN0_99GMFQVW) so per-disk rows stay scannable. The snapshot JSON and Prometheus labels still carry the full stable-id paths.Standby Guard appears as a single line inside the [HD] block when the feature is enabled; the per-disk STANDBY/ACTIVE annotation lives in the right-most column of that block's device table. Disks in standby render in dim grey because the temperature reading is stale (smartctl is skipped while a disk sleeps).Errors is a conditional column of the verbose blocks, so it needs -V: the default summary has no per-device rows at all. It only appears when at least one device of that controller has failed a temperature read since smfc started (see chapter 2.4). It then shows the lifetime failure count of every device of the controller, so the failing one stands out against its healthy neighbours, and non-zero values are highlighted. On a healthy machine the column is not rendered at all. Note it is only available in online mode: in --standalone mode the client builds its own controllers and has no history, so a failing read shows up as an ERROR temperature cell instead.Each fan controller is constructed independently, so a single failing controller (e.g. a missing GPU tool or a non-existent disk) shows an ERROR row in the Fan controllers table while the rest of the report still renders.
Most probably, there was an assertion (i.e., the rotational speed of a fan went above or below an IPMI threshold) and IPMI switched back that zone to full rotational speed. You can check the current fan rotational speeds:
ipmitool sdr
and you can also check IPMI event log and list assertion events:
root@home:~# ipmitool sel list
1 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
2 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Asserted
3 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Deasserted
4 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Critical going low | Deasserted
5 | 10/19/2023 | 05:20:59 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
If the problematic fan (causing the alert) is identified, then you must adjust its threshold. This process could take several adjustment cycles. Be patient :) You may read this chapter for more details.
The configuration is the following:
128 GB ECC DDR4-2666MHz RAM
Fractal Design Node 804 case, with separate chambers for the motherboard and the hard disks:
Proxmox 9
8 x WD Red 12TB (WD120EFAX) hard disks in ZFS RAID
4 x Noctua NF-F12 PWM fans (FAN1, FAN2, FAN3, FAN4) in IPMI CPU zone
2 x Noctua NF-F12 PWM on an Y-adapter + Noctua NF-A14 PWM fans (FANA, FANB) in IPMI HD zone
Further readings:
smartctl)Written with StackEdit.
Python
97.8%
Shell
2.2%
Supermicro fan control for Linux (home) servers.
This is a systemd service running on Linux that can control fans with the help of IPMI on Supermicro X10-X13/H10-H14, some X9, and X14 (experimental) motherboards.
systemd and bashcoretemp kernel module for Intel CPUs or k10temp kernel module for AMD CPUsdrivetemp kernel module (kernel version 5.6+ required) for SATA HDDs/SSDsipmitoolsmartmontools for SAS/SCSI disks and standby guard featurenvidia-smi for Nvidia GPUsrocm-smi for AMD GPUsnpu-smi for Ascend NPUscoretemp/k10temp and drivetemp)smfc service or run it in docker (see chapter 9. for more details)/etc/smfc/smfc.conf and command line options in /etc/default/smfc (see chapter 10. for more details).smfc service (see chapter 11. for more details)Feel free to visit Discussions and raise your questions or share your experience on this project.
This service was designed for Supermicro motherboards with IPMI functionality. It implements fan controllers that control fan speed dynamically in one or more IPMI zones. The service operates the fans in IPMI FULL mode, where the fan rotation level can be adjusted with IPMI raw commands (read more details here).
Key features:
control_function=) for arbitrary piecewise-linear fan curves[CPU] + [CPU:1])nvidia-smi or rocm-sminpu-smiremote_parameters= for VM setups (e.g. TrueNAS on Proxmox with PCI passthrough)systemd service, Docker image, DEB/RPM/AUR package, or PyPI packagesmfc-client and enables Grafana dashboard integrationsmfc-client tool showing a live read-only snapshot of controllers, fan levels, IPMI zones, and standby stateexit_level=, 100% by default) at service terminationIPMI zone is a logical term, representing a cooling zone, where there are predefined fans having the same rotation speed. Please note that the fan assignment to an IPMI zone is predefined on the motherboard, it cannot be changed (Supermicro does not provide individual fan configuration features in IPMI, while other vendors do it). On a typical Supermicro motherboard, there are two IPMI zones:
On Supermicro server boards, there could be more IPMI zones with different fan names (see issue #31).
smfc v3.8.0and earlier versions implemented a feature (called Swapped Zones) to swap IPMI zone 0 and 1. Fromsmfc v4.0.0the IPMI zones can be assigned freely to fan controllers providing more freedom and convenience for the user (seeipmi_zone=parameter for more details).
In smfc, the following fan controllers are implemented:
| Fan controller | Temperature source | Configuration | Default IPMI zone |
|---|---|---|---|
| CPU | Intel/AMD CPU(s) | CPUs are identified automatically | 0 (CPU zone) |
| HD | SATA and SCSI HDDs/SSDs | Hard disks' names must be specified in [HD] hd_names= parameter | 1 (Peripheral zone) |
| NVME | NVMe SSDs | NVMe device names must be specified in [NVME] nvme_names= parameter | 1 (Peripheral zone) |
| GPU | Nvidia/AMD GPUs | GPU indices must be specified in [GPU] gpu_device_ids= parameter | 1 (Peripheral zone) |
| NPU | Ascend NPUs | NPU card IDs must be specified in [NPU] npu_device_ids= parameter | 1 (Peripheral zone) |
| PCI | PCI device HWMON | PCI device(s) must be specified in [PCI] pci_address=, pci_id= or pci_driver= | 1 (Peripheral zone) |
| CONST | None | Constant fan level can be specified in [CONST] level= parameter | 1 (Peripheral zone) |
These fan controllers can be enabled and disabled independently. They can be used in a free combination with one or more IPMI zones. Multiple fan controllers
can share the same IPMI zone -- smfc will automatically apply the highest fan level requested by any controller in that zone (see chapter 1.3 for details).
CONST fan controller is an exception here, it does not require a temperature source, it can provide a constant fan level for one or more IPMI zones.
In smfc configuration file each fan controller has an individual section.
In smfc, a temperature-driven fan controller implements the following control logic:
ipmitool)
If the temperature source has multiple instances (e.g. multiple CPUs, HDDs, NVMEs, GPUs, NPU cards or PCI devices) then the user can configure a calculation method (i.e. minimum, average, maximum) for the calculation of the final temperature value (see temp_calc= parameter).
Please note that smfc will set all fans back to 100% speed at service termination to avoid overheating (see
chapter 1.6)!
The chapters below describe each fan controller: which devices it drives, how it reads their temperature, and what you need to know before you enable it.
Every temperature-driven fan controller shares the following parameters, so the chapters below list only the controller-specific ones:
enabled= -- whether the fan controller runs at allipmi_zone= -- the IPMI zone(s) the controller drivestemp_calc= -- how the temperatures of several devices are reduced to one value (minimum, average, maximum)steps= -- the number of discrete steps in the temperature-to-level mappingsensitivity= -- the temperature change needed before the controller reactspolling= -- how often the temperature is readmin_temp= / max_temp= -- the temperature interval the controller steers inmin_level= / max_level= -- the fan level interval the temperature interval is mapped tocontrol_function= -- an arbitrary piecewise-linear curve that replaces the four parameters above (see chapter 2)smoothing= -- the moving average window size that damps temperature oscillationerror_tolerance= -- how many failed temperature reads per device are toleratedThe CONST fan controller reads no temperature, so it uses enabled=, ipmi_zone=, polling= and level= only.
| Target devices | Intel and AMD CPUs. On a multi-socket board every socket is a separate device, labelled cpu0, cpu1, and so on. |
| Temperature source | The Linux kernel HWMON interface, through the coretemp (Intel) or k10temp (AMD) kernel module. |
| Device identification | Automatic. smfc finds the CPUs in the udev database, so there is no device parameter. |
| Other parameters | None |
| Docker | Supported in every image. The coretemp or k10temp kernel module must be loaded on the host. |
| Need to know | • The matching kernel module must be loaded. smfc checks it at startup and stops with an error if neither coretemp nor k10temp is present.• The default IPMI zone is 0, the CPU zone. Every other controller defaults to zone 1. • CPU temperature moves fast, so the defaults are a wide window ( min_temp=30.0, max_temp=60.0) with a fast poll (polling=2) and a coarse sensitivity=3.0 that keeps the fans from chasing every spike. |
| Target devices | SATA and SAS/SCSI hard disks and SSDs. NVMe SSDs are not accepted here -- use the NVME fan controller for those. |
| Temperature source | The drivetemp kernel module through HWMON for SATA disks, and the smartctl command for SAS/SCSI disks. smfc decides per disk and falls back to smartctl automatically when a disk has no HWMON device. |
| Device identification | hd_names= -- use the stable /dev/disk/by-id/... names |
| Other parameters | smartctl_path=, standby_guard_enabled=, standby_hd_limit= |
| Docker | Supported in every image. All three ship smartctl, so SAS/SCSI disks work as they do on the host. SATA disks need the drivetemp kernel module loaded on the host; without it smfc falls back to smartctl for them too. |
| Need to know | • HWMON is the faster path, and it can read the temperature while the disk sleeps. See chapter 4 for the full compatibility table. • Disk types can be mixed in hd_names=, but the Standby guard feature is then not supported. See chapter 3.• smartctl usually needs root. Run the service as root, or start smfc with the -s command-line option to prefix the calls with sudo.• This is the slowest controller. Its polling=10 default exists because a smartctl call per disk is expensive, and startup time grows with the number of disks.• Disks are the narrowest temperature window of all controllers ( min_temp=32.0, max_temp=46.0), because a disk reaches its limit far below a CPU. |
| Target devices | NVMe SSDs. |
| Temperature source | The Linux kernel HWMON interface. It reads temp1_input, which the NVMe standard defines as the Composite temperature -- the value the drive itself uses for thermal protection. |
| Device identification | nvme_names= -- use the stable /dev/disk/by-id/nvme-... names |
| Other parameters | None |
| Docker | Supported in every image. |
| Need to know | • No kernel module has to be loaded. The kernel exposes the sensor as soon as the drive is present. • A drive can expose more sensors than Composite, but this controller always reads Composite.• NVMe drives run hotter than SATA disks, so the defaults are min_temp=35.0 and max_temp=70.0. |
| Target devices | Nvidia and AMD GPUs. |
| Temperature source | The nvidia-smi or the rocm-smi command. No kernel module and no udev lookup are involved. |
| Device identification | gpu_type= (nvidia or amd) and gpu_device_ids= -- the indices of the cards |
| Other parameters | nvidia_smi_path=, rocm_smi_path=, amd_temp_sensor= (0-junction, 1-edge, 2-memory) |
| Docker | The standard image does not support it. Use the -nvidia or the -amd image variant, which needs the NVIDIA Container Toolkit or the amdgpu driver on the host. See DOCKER.md. |
| Need to know | • The matching command must be installed. smfc checks it at startup.• One SMI call per polling window covers every index of the section, not one call per card. So a failed call affects all GPUs of the section at once, and their error_tolerance budgets advance in lockstep.• amd_temp_sensor= applies to AMD cards only. Nvidia cards report one temperature. |
| Target devices | Huawei Ascend NPU cards, e.g. the Atlas 300I Duo. |
| Temperature source | The npu-smi command. |
| Device identification | npu_device_ids= -- the -i card IDs reported by npu-smi info -m |
| Other parameters | npu_smi_path=, npu_smi_timeout= |
| Docker | Not supported in any image. npu-smi is part of the Ascend driver on the host and is not in the images, so an enabled [NPU] section stops the container at start-up. Run smfc as a systemd service on the host instead. See DOCKER.md. |
| Need to know | • The card IDs may not start from 0. Read them with npu-smi info -m instead of assuming.• The controlled unit is a card, not a chip. On a multi-chip card the hottest chip drives the card temperature, so a dual-chip card still counts as one unit. • The card also reports an MCU temperature. smfc excludes it on purpose: the MCU is a board sensor that barely follows the workload, and counting it would keep the fans up at idle.• npu-smi is slow, about 1.5 seconds per call, which is why polling=5 is the default. npu_smi_timeout= bounds a hung call.• Ascend cards tolerate high temperatures, so the default window is min_temp=40.0 to max_temp=85.0. |
| Target devices | Any PCI device that exposes a HWMON temperature and has no fan controller of its own, e.g. a 10 Gbit network card. |
| Temperature source | The Linux kernel HWMON interface. The devices are discovered in the udev database, and smfc searches the whole subtree of each card, so a HWMON device attached below the card is found too. |
| Device identification | Exactly one of pci_address= (a list of PCI slot addresses, e.g. 0000:05:00.0), pci_id= (one vendor:device ID, e.g. 1d6a:07b1) or pci_driver= (one driver name, e.g. atlantic) |
| Other parameters | temp_sensor= |
| Docker | Supported in every image. The driver of the card must be loaded on the host, like for the other HWMON-based controllers. |
| Need to know | • The controlled unit is a HWMON device, not a card. A card that exposes several HWMON devices counts as several units, and temp_calc= aggregates across all of them. A SATA controller with three disks behind it is one card but three units.• pci_driver= is the wildcard form: it selects every device the driver serves, so a second card of the same kind joins the section at the next smfc restart with no configuration change.• All addresses of a pci_address= list must be the same model. smfc stops at startup otherwise.• One section covers one kind of PCI device, because a section has one temperature window and one temp_sensor=. Two kinds, e.g. a network card and a storage controller, need two sections, and two sections of the same controller need two different IPMI zones (see chapter 1.4).• A card can expose several sensors, and temp_sensor= picks one by index. An Aquantia AQC107 reports the PHY temperature in temp1_input and the MAC temperature in temp2_input. List the labels of your machine with cat /sys/class/hwmon/hwmon*/temp*_label.• Not every PCI device has a HWMON temperature. The proprietary Nvidia driver exposes none (use the GPU fan controller), and some Wi-Fi cards report through the thermal subsystem instead, which is outside the PCI tree. smfc stops at startup and names the device in that case.• The default temp_calc=2 (maximum) differs from every other controller, so the hottest card of the section drives the fans. |
| Target devices | None. This controller has no temperature source. |
| Temperature source | None. It applies a fixed fan level to its IPMI zone(s). |
| Device identification | None. This controller drives no device. |
| Other parameters | level= -- the constant fan level |
| Docker | Supported in every image. It reads no sensor, but it drives the fans through ipmitool like every other controller, so the container still needs the /dev mount and admin privilege, or [Ipmi] remote_parameters=. |
| Need to know | • Its level acts as a guaranteed minimum on a shared zone. With [CONST] level=40 on zone 1, that zone never drops below 40%, even when every temperature-driven controller asks for less.• It ignores the parameters of the temperature-driven controllers ( temp_calc=, steps=, min_temp=, control_function= and the rest), because it never reads a temperature.• It is the only way to drive a zone that has no sensor at all, e.g. fans cooling a passive backplane. |
When multiple fan controllers are assigned to the same IPMI zone, smfc detects this at startup and automatically switches to a two-phase arbitration loop for those controllers. Controllers on non-shared zones are not affected -- they apply their fan levels directly.
At startup, smfc logs the detected shared zones at INFO level:
Shared IPMI zone 1: ['HD', 'NVME', 'CONST']
For shared zones, the control loop uses a two-phase approach in each iteration:
Controllers on non-shared zones skip the apply phase entirely -- they execute their own IPMI calls directly during the compute phase, just like they would if no sharing existed.
This means the highest requested fan level wins on a shared zone. For example, if the HD fan controller wants 45% on zone 1 and the NVME fan controller wants 70% on the same zone, smfc will set zone 1 to 70%. When the NVME cools down and its curve asks for less than 45%, the HD controller's level takes over again.
Usually this is also the hottest component, so "the hottest wins" is a good rule of thumb -- but "hottest" here means hottest relative to its own limits, not the highest number of degrees. Every controller compares its own temperature against its own [min_temp..max_temp] window, so degrees are not comparable between controllers: with the default windows a hard disk at 44 °C is nearly at its 46 °C ceiling and asks for close to max_level, while a CPU at 45 °C sits halfway through its 30..60 °C window and asks for roughly the middle of its range. The disk wins the zone even though it is the cooler device. Only the requested percentages are comparable, and those are what smfc compares.
The CONST fan controller also participates in the arbitration -- its constant level acts as a guaranteed minimum for its zone(s). For example, configuring [CONST] level=40 on zone 1 ensures that zone never drops below 40%, even if all temperature-driven controllers would request a lower value.
When a shared zone's level changes, the log output at INFO level shows the winning controller and lists all other controllers with their requested levels:
Shared IPMI zone [1]: new level = 70% (winner: NVME=70%/52.0C, losers: HD=45%/38.5C, CONST=40%)
For non-shared zones, only the applied level is logged:
IPMI zone [0]: new level = 60% (CPU=45.0C)
A single fan controller can only apply one temperature-to-level curve. When different IPMI zones need different curves for the same temperature source (e.g. quiet mid-plane fans vs. aggressive CPU coolers), you can create multiple instances of the same controller type by appending a colon and a number to the section name:
# Instance for zone 0 — conservative curve for noisy mid-plane fans
[CPU:0]
enabled=1
ipmi_zone=0
min_temp=55.0
max_temp=75.0
min_level=20
max_level=80
# Instance for zone 1 — aggressive curve for silent CPU coolers
[CPU:1]
enabled=1
ipmi_zone=1
min_temp=40.0
max_temp=80.0
min_level=20
max_level=100
Three naming styles are supported and can be freely mixed:
[CPU] — single instance, original format (unchanged behaviour)[CPU] + [CPU:1] — base section plus numbered extras[CPU:0] + [CPU:1] — all-numbered instancesThe suffix number after : is used only for ordering and logging — it has no relationship to the ipmi_zone= value inside the section. Each instance is a complete, independent fan controller with its own full set of parameters, sharing only the physical temperature source.
Two enabled instances of the same controller type must target different IPMI zones -- smfc rejects the configuration with an error otherwise. Instances of different controller types can share a zone, and those participate in the shared zone arbitration described in chapter 1.3.
While smfc is running, an external event (BMC web UI, a manual ipmitool command, a firmware quirk) can silently flip the BMC out of FULL mode. When that happens, smfc keeps sending per-zone level commands but the BMC ignores them and applies its own profile — fans run at unintended speeds with no error in the log. smfc detects this by checking the fan mode on every loop iteration. The [Ipmi] enforce_fan_mode= parameter controls the reaction: with 1 (default) the drift is logged and FULL mode plus all zone levels are re-asserted; with 0 the service exits with code 11 (add Restart=on-failure to the systemd unit if you want it restarted automatically in this mode).
On X14/H14 boards fan control is not acquired through FULL mode, so enforce_fan_mode= guards the per-zone manual latch instead. The reaction to a loss is the same, and a zone the BMC has forced to 100% after a fan failure counts as a loss too - see chapter 5.
See chapter 6 for the details of the FULL mode itself and the IPMI sensor thresholds it depends on.
While smfc is running the BMC stays in FULL mode, which means the BMC hands fan control over to smfc and does not
regulate anything itself. If the service stopped without touching the fans, the zones would stay frozen at the last
applied level with nothing regulating them, so smfc applies the [Ipmi] exit_level= value (default: 100, i.e. full
speed) to all configured zones before it exits. This happens on a normal systemctl stop (SIGTERM), on Ctrl-C
(SIGINT), and on every error exit. Note that no in-process handler can run on SIGKILL or when the OOM killer
terminates the service; use an ExecStopPost= drop-in in the systemd unit if you want to cover those cases too.
The exit_level= parameter accepts the [0-100] range plus the special value -1, which means "do not change the fan
levels" - smfc then exits without issuing any fan command and the zones stay at the last applied level, exactly like
the deprecated -ne command-line option did.
A lower value (e.g. exit_level=40) is a compromise for quiet systems: the fans keep running, but the machine does not
go to full speed every time the service is stopped or restarted.
[!IMPORTANT] X14/H14 motherboards behave differently. Instead of FULL mode they hold an explicit BMC fan control state (per-zone manual mode on OpenBMC, a global bypass flag on ATEN) that stays armed until
smfcreleases it. The exit is therefore two steps: (1)exit_levelis applied to all configured zones unless it is-1, then (2) the state is released and automatic BMC fan control is restored (on ATEN this also releases zonessmfcnever drove).Step (2) always runs, even with
exit_level=-1or after a failed step (1), because an armed state freezes every zone at its last duty with the BMC's thermal loop suspended.exit_levelitself has almost no effect: the BMC applies its own curve within about a second, so100and-1end up in the same place. That curve uses CPU and system sensors only - hard disk and NVMe temperatures are not part of it.
Fan controllers use user-defined control functions that map a temperature interval to a fan rotation level interval. Two forms are supported in each temperature-driven section: a simple linear mapping (chapter 2.1) or an advanced multi-segment piecewise-linear curve (chapter 2.2). When both are present in the same section, control_function= takes precedence and the min_temp/max_temp/min_level/max_level keys are ignored.
The simple form maps a single temperature interval [min_temp..max_temp] linearly to a single fan-level interval [min_level..max_level], divided into discrete plateaus by the steps= parameter:
steps=5
min_temp=30
max_temp=65
min_level=35
max_level=100
The dashed blue line shows the continuous linear ideal between (min_temp, min_level) and (max_temp, max_level); the solid red staircase is the digitalized output actually applied to the fan (here with steps=5, producing 6 plateaus: one pinned at each endpoint plus four in the interior).
Sample configurations using the linear form are available in
config/samples/— for examplesmfc-sample1.conf(CPU only),smfc-sample2.conf(HD with standby guard),smfc-sample4.conf(CPU + HD hybrid), andsmfc-sample8.conf(multiple fan curves per zone).
For more precise control, the control_function= parameter (available since smfc v6.0.0) lets you define a piecewise-linear fan curve directly as a list of temperature-level pairs:
steps=5
control_function = 30-35, 50-40, 60-90, 65-100
Each pair is written as T-L where T is a temperature in °C and L is a fan level in %. At least two pairs are required, temperatures must be strictly ascending, and all values must be in the range [0..100]. When control_function= is present in a section it takes precedence over min_temp=, max_temp=, min_level=, and max_level= — those keys are ignored (and not validated). The ignored state is reported at CONFIG log level.
The steps= parameter still applies: it controls how many discrete plateaus the interior of the curve is divided into before being sent to the fan. The two endpoint temperatures are always pinned exactly to their specified levels; the steps interior plateaus together with the 2 pinned endpoints produce steps + 2 plateaus in total.
The dashed blue line shows the continuous piecewise-linear ideal described by control_function=; the solid red staircase is the digitalized output actually applied to the fan (here with steps=5, producing 7 plateaus: one pinned at each endpoint plus five in the interior).
See
smfc-sample9.conffor a complete hybrid configuration usingcontrol_function=for both the CPU and HD fan controllers.
Changing fan rotational speed is a slow physical process — depending on the fan type and the magnitude of the change it can take several seconds. Frequent or unnecessary changes also cause audible oscillation. To keep the fans steady, each temperature-driven controller combines five mechanisms that act at different stages of the control loop:
| Stage | Mechanism | Parameter | Effect |
|---|---|---|---|
| Sample | Polling interval | polling= | Sets how often the controller reads the temperature. Larger values reduce the maximum rate of fan-level updates. |
| Smooth | Moving-average smoothing | smoothing= | Averages the last N temperature readings before they enter the control function. Suppresses brief spikes; 1 (default) disables smoothing. |
| Filter | Sensitivity threshold | sensitivity= | The controller does not react until the smoothed temperature has moved by at least this many °C since the last action. |
| Quantize | Discrete fan levels | steps= | The control function produces a fixed number of plateaus (linear: steps + 1, multi-segment: steps + 2) instead of a continuous curve, so small temperature drift inside a plateau yields the same fan level. |
| Apply | Post-change delay | [Ipmi] fan_level_delay= | After every fan-level change, the controller waits this many seconds before issuing another command, giving the fan time to reach the new speed physically. |
The mechanisms are independent and complementary: polling= and smoothing= work on the input side (how the temperature is measured), sensitivity= and steps= work on the decision side (whether and how a temperature maps to a fan level), and fan_level_delay= works on the output side (pacing the IPMI commands themselves).
The mechanisms above all decide whether a new temperature should move the fans. error_tolerance= answers a different question: what should happen when the temperature cannot be read at all?
Transient read errors have been reported in several different setups: with HBA/RAID controllers, with the kernel's drivetemp driver, and in docker environments. One well-understood case is drivetemp, which issues the SMART/ATA temperature command with a hard-coded 10-second timeout. While a disk is spinning up from STANDBY the command can exceed that timeout, and reading .../hwmon*/temp1_input returns EIO for a second or two (see issue #87). Note that the Standby guard feature is exactly what makes a disk array wake up in unison, so it correlates with this window.
Before version 6.1.0 a single failed read was fatal: smfc stopped and left the fans at 100%. Now the last known good temperature of that device is reused for up to error_tolerance= consecutive failed reads (int, default 3, 0 disables the tolerance), and the failure is logged at ERROR level with the current streak, the budget and the number of failed reads of that device since startup:
ERROR: HD: temperature read failed, reusing 33.0C (device=/dev/disk/by-id/..., 2/3, total=9): ...
INFO: HD: temperature read recovered after 2 failure(s) (device=/dev/disk/by-id/..., total=9)
A few properties worth knowing:
polling=: ~6 seconds for [CPU] (polling=2), ~30 seconds for [HD] (polling=10).smfc stop with the original error and the fans go to 100%.read_errors / read_errors_total fields in the HTTP exporter's snapshot and the smfc_device_temp_read_errors gauge / smfc_device_temp_read_errors_total counter in /metrics (see chapter 13.).For the HD fan controller, an additional optional feature was implemented, called Standby guard, with the following assumptions:
This feature monitors the power state of SATA hard disks (with the help of smartctl) and will put the whole array into standby mode if a few members have already stepped into that state. With this feature, the situation can be avoided where the array is partially in standby mode while other members are still active.
SCSI disks are not compatible with this feature.
The following table summarizes how the temperature is read for different disk types:
| Disk type | Temperature source | Kernel module | Command |
|---|---|---|---|
SATA | Linux kernel (HWMON) | drivetemp | - |
NVME | Linux kernel (HWMON) | - | - |
SAS/SCSI | smartctl | - | smartctl |
The
smfcservice was originally designed forSATAhard drives, but fromsmfc v3.0.0, it is also compatible withNVMEandSAS/SCSIdisks.smfc v5.0.0introduced a new NVME fan controller and separated the use of SATA and SAS/SCSI HDDs/SSDs and NVME SSD disks.
Some additional notes:
SATA disks the drivetemp kernel module should be loaded. This is the fastest way to read disk temperature, and the kernel module can report the temperature while hard disks are in sleep mode!SAS/SCSI disks the smartctl command will be used to read disk temperature.drivetemp module is not loaded or an HDD is not compatible with drivetemp module then smfc will use smartctl automatically.hd_names= configuration parameter but the Standby guard feature will not be supported in this case.NVME SSDs, no kernel driver needs to be loaded; the kernel can handle this disk type automaticallyOriginally, this software was designed to work with Supermicro X10-X12/H10-H12 motherboards with IPMI functionality, where the BMC chip is ASPEED AST2400 or AST2500. Most motherboards in this set are compatible with the Supermicro IPMI raw commands used here and are supported out of the box.
Newer X13/H13 motherboards with AST2600 chips are also compatible with smfc (see more details in issue #33 about
an X13SAE-F motherboard). The only difference is in the implementation of thresholds: the AST2600 chip implements only the
Lower Critical threshold, so setting up thresholds is different.
Some motherboards require platform-specific IPMI raw commands for fan control. smfc implements a platform abstraction
(since smfc v5.1.0) that handles these differences. The platform is auto-detected from BMC product name, or can be overridden with the
platform_name= configuration parameter (see chapter 10.2). Currently supported platforms:
platform_name= parameter | Platform | Notes |
|---|---|---|
auto | automatic discovery based on BMC information | Reads BMC product name; selects generic_x14 if it starts with X14 or H14, X10QBi if it starts with X10QBi, generic_x9 if it starts with X9, otherwise falls back to generic. If the BMC reports no usable product name (e.g. Unknown), no prefix matches and the fallback is generic -- set platform_name= by hand then. |
generic | Generic X10-X13/H10-H13 Supermicro boards | Uses standard Supermicro IPMI raw commands |
generic_x9 | Generic Supermicro X9 boards | 4 fan zones (0x10-0x13), duty cycle 0-255 scale |
generic_x14 | Supermicro X14 and H14 boards | A platform family covering both 14th generation BMC firmware stacks; the stack is detected at startup, not guessed from the board name (see doc/X14H14_MANUAL_FANCONTROL.md). Up to 5 fan zones (0-4) — smfc discovers how many the board really has — duty cycle 0-100%, and neither stack uses FULL fan mode — see the notes below. Experimental, see issue #98, discussion #106 |
X10QBi | Supermicro X10QBi motherboard | Nuvoton NCT7904D fan controller, 4 fan zones (0x10-0x13), duty cycle 0-255 scale, see PR #97 and discussion #110 |
With this abstraction layer, new Supermicro motherboards can also be added to smfc with a good understanding of their IPMI raw commands and fan control logic.
Some X9 motherboards are supported (since smfc v5.2.0) via the generic_x9 platform, provided they support the specific IPMI raw commands used for fan control. X9 boards are auto-detected when the BMC product name starts with X9; you can also force the platform by setting platform_name=generic_x9. The X10QBi platform is auto-detected when the BMC product name starts with X10QBi.
X14/H14 motherboard support (generic_x14) was introduced in smfc v6.0.0 and is currently in testing phase. The 14th generation ships two BMC firmware stacks, OpenBMC and ATEN; smfc detects the stack at startup and it cannot be forced from the configuration file. doc/X14H14_MANUAL_FANCONTROL.md covers both stacks, the board-to-stack table, and every raw command involved. What the split means for your configuration:
platform_name=auto fails if the BMC reports no product name. Auto-detection matches the X14/H14 prefix of the BMC product name. Some boards answer ipmitool bmc info with Product Name : Unknown, so no prefix matches, smfc falls back to generic, and it then drives the fans with commands these boards do not use -- it reports that it took control while the BMC keeps running its own curve. Check the product name with ipmitool bmc info; if it is Unknown or empty, set platform_name=generic_x14 by hand.FULL fan mode is not used. X14/H14 boards have their own manual fan control mode, and smfc holds that mode instead. enforce_fan_mode= therefore counts the drift of that manual mode, not of the fan mode.exit_level= behaves differently on these boards. See chapter 1.6.If you own an X14 or H14 board and test smfc, please share your experience in discussion #106.
The earlier X8 motherboards are NOT compatible with this software. They do not implement IPMI FULL mode, and they cannot control fan levels with IPMI raw commands.
Feel free to create a short feedback in discussion #55 on your compatibility experience.
This chapter describes the RPM sensor-threshold/assertion model of the older AST2400/2500/2600-based BMCs (
generic,generic_x9,X10QBiplatforms). Whether and how it applies to the X14/H14 boards (generic_x14platform) is not yet confirmed — see chapter 5 and share your findings in discussion #106.
On Supermicro X10-X11 motherboards IPMI uses six sensor thresholds to specify the safe and unsafe fan rotational speed intervals (these are RPM values rounded to the nearest hundreds, defined for each fan separately):
Lower Non-Recoverable
Lower Critical
Lower Non-Critical
Upper Non-Critical
Upper Critical
Upper Non-Recoverable
but newer Supermicro X13 motherboards (with AST2600 BMC chip) have only one sensor threshold:
Lower Critical
Originally, this chapter was created for Supermicro X10-X11 motherboards, but can be easily adapted to X13 motherboards as well (see more details in #33).
Like many other utilities (created by NAS and home server community), smfc also uses IPMI FULL mode for fan control, where all fans in the zone:
[Lower Critical, Upper Critical] intervalLower Critical or Upper Critical threshold then IPMI will generate an assertion event and will set all fan speeds back to 100% in the zone
smfckeeps the BMC in FULL mode while it runs and restores a level of your choice when it exits — see chapter 1.5 and chapter 1.6 for those two topics.
Please also consider the fact that fans are mechanical devices, their rotational speed is not stable (it could be fluctuating). To avoid IPMI's assertion mechanism described here please follow the next steps:
min_level/max_level values for smfc respecting the variance of all fans in the IPMI zone (it could take several iterations and adjustments)
Here is a real-life example for a Noctua NF-F12 PWM fan:
Upper Non-Recoverable = 1800 rpm
Upper Critical = 1700 rpm
Upper Non-Critical = 1600 rpm
Lower Non-Critical = 200 rpm
Lower Critical = 100 rpm
Lower Non-Recoverable = 0 rpm
Max RPM = 1500 rpm
Min RPM = 300 rpm
max_level = 100 (i.e. 1500 rpm)
min_level = 35 (i.e. 500 rpm)
Notes:
Use the following ipmitool command to display the current IPMI sensor thresholds for fans:
root@home:~# ipmitool sensor|grep FAN
FAN1 | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FAN2 | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FAN3 | na | | na | na | na | na | na | na | na
FAN4 | 400.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FANA | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
FANB | 500.000 | RPM | ok | 0.000 | 100.000 | 200.000 | 1600.000 | 1700.000 | 1800.000
Use the following ipmitool command to list assertion events:
root@home:~# ipmitool sel list
1 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
2 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Asserted
3 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Deasserted
4 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Critical going low | Deasserted
5 | 10/19/2023 | 05:20:59 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
Use the following ipmitool commands to specify all six sensor thresholds for FAN1:
root@home:~# ipmitool sensor thresh FAN1 lower 0 100 200
root@home:~# ipmitool sensor thresh FAN1 upper 1600 1700 1800
You can also edit and run ipmi/set_ipmi_threshold.sh to configure all IPMI sensor thresholds
If you install new BMC firmware on your Supermicro motherboard, you have to configure IPMI thresholds again
If you do not see fans when executing ipmitool sensors, you may want to reset the BMC to factory default using the Web UI or using ipmitool mc reset cold
Noctua specifies the variance of minimum and maximum fan rotational speeds (e.g. see the specification of Noctua NF-F12 PWM). For example:
Rotational speed (+/- 10%) 1500 RPM: 1350-1650 RPM intervalMin. rotational speed @ 20% PWM (+/-20%) 300 RPM: 240-360 RPM intervalPlease note that LNA/ULNA cables or Y-cables can modify the rotational speed calculations here and the required IPMI sensor thresholds too.
You can read more about:
If low noise and low heat generation are important attributes of your Linux box, then you may consider the following chapters.
Most modern CPUs have multiple energy-saving features. You can check your BIOS and enable them to minimize the heat generation.
Intel(R) CPUs:
AMD(R) CPUs:
With this setup the CPU will change its base frequency and power consumption dynamically based on the load.
In case of SATA hard disks, you may enable:
With the help of command hdparm you can enable advanced power management and specify a spin down timer (read more here):
hdparm -B 127 /dev/sda
hdparm -S 240 /dev/sda
In file /etc/hdparm.conf you can specify all parameters persistently:
quiet
/dev/sda {
apm = 127
spindown_time = 240
}
/dev/sdb {
apm = 127
spindown_time = 240
}
...
Important notes:
[HD] polling= to at least twice as big as the spindown_time specified here./etc/hdparm.conf you must define HD names in /dev/disk/by-id/... form to avoid inconsistency.One or more of the following Linux kernel modules need to be loaded for smfc:
coretemp: temperature report for Intel(R) CPUsk10temp: temperature report for AMD(R) CPUsdrivetemp: temperature report for SATA hard disks (available from kernel 5.6+ version)Use /etc/modules file for persistent loading of these modules.
Notes:
drivetemp module is the fastest way to get the temperature of the hard disks, and it can read temperature of the SATA hard disks even if they are in standby mode.For the installation and uninstallation, you need root privileges. There are several ways to install and uninstall smfc, this chapter will show them.
Pre-built .deb packages are available from the smfc-deb APT repository, hosted on GitHub Pages and signed with a dedicated GPG key.
The repository can be added in two ways.
a) deb822 format (recommended)
sudo curl -fsSL https://petersulyok.github.io/smfc-deb/smfc.sources \
-o /etc/apt/sources.list.d/smfc.sources
sudo apt update && sudo apt install smfc
The smfc.sources file contains the repository signing key, so it is a single, self-contained file and no separate keyring is needed. Embedded keys require apt 2.4 or newer, available on all supported distributions (Debian 12+, Ubuntu 22.04+).
b) One-line format with a separate key file
curl -fsSL https://petersulyok.github.io/smfc-deb/smfc-repo.gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/smfc-repo.gpg
echo "deb [arch=amd64,arm64 signed-by=/etc/apt/keyrings/smfc-repo.gpg] https://petersulyok.github.io/smfc-deb stable main" \
| sudo tee /etc/apt/sources.list.d/smfc.list
sudo apt update && sudo apt install smfc
To update: sudo apt update && sudo apt upgrade smfc.
To remove:
sudo apt remove smfc
sudo rm -f /etc/apt/sources.list.d/smfc.sources # a)
sudo rm -f /etc/apt/sources.list.d/smfc.list /etc/apt/keyrings/smfc-repo.gpg # b)
Note: if you added the repository with b) earlier and switch to a), delete the files of the old format (smfc.list and smfc-repo.gpg), otherwise the repository is configured twice.
Compatible with Debian 12+, Ubuntu 22.04+. See the smfc-deb README for the full distribution list. The package installs the same files as the manual installation (service unit, configuration, man page, sample configs). Configuration files under /etc/ are preserved on upgrade. See PACKAGES.md for build-from-source instructions.
The DEB package enables the smfc service but does not start it on installation. First review your configuration (see chapter 10.), then start the service manually (see chapter 11.); from then on it starts automatically on every boot. On upgrade the service is restarted if it was running, and left stopped if you had stopped it.
Pre-built .rpm packages are available from the smfc-rpm DNF repository, hosted on GitHub Pages and signed with a dedicated GPG key.
Add the repository and install:
sudo dnf config-manager addrepo --from-repofile=https://petersulyok.github.io/smfc-rpm/smfc.repo
sudo dnf install smfc
(On older dnf versions: sudo dnf config-manager --add-repo=https://petersulyok.github.io/smfc-rpm/smfc.repo.)
To update: sudo dnf upgrade smfc.
To remove:
sudo dnf remove smfc
sudo rm /etc/yum.repos.d/smfc.repo
Compatible with Fedora 39+, RHEL/Rocky/AlmaLinux 9+ (with EPEL), CentOS Stream 9+, openSUSE Leap 15.5+. See the smfc-rpm README for the full distribution list. The package installs the same files as the manual installation. Configuration files are preserved on upgrade. See PACKAGES.md for build-from-source instructions.
The RPM package enables the smfc service but does not start it on installation. First review your configuration (see chapter 10.), then start the service manually (see chapter 11.); from then on it starts automatically on every boot. On upgrade the service is restarted if it was running, and left stopped if you had stopped it.
An smfc package is available in the AUR. It is created and maintained by
an Arch Linux community member (urirocky), and it is currently based on the v6.0.0 release.
Install it with your preferred AUR helper:
yay -S smfc
or manually:
git clone https://aur.archlinux.org/smfc.git
cd smfc
makepkg -si
Please report package-specific issues (PKGBUILD, dependencies, file locations) on the AUR package page.
smfc is also available as a docker image, see more details in DOCKER.md. In this case, your job is only to provide your configuration file on the host computer, smfc will be executed automatically when the container is starting.
There is an installation script (bin/install.sh) which can install smfc in two different ways:
The installation script requires curl, pip, gzip, and mandb commands, and it has the following
command line parameters:
user@host:~$ ./install.sh --help
usage: install.sh [-h|--help] [-k|--keep-config] [-l|--local] [-v|--verbose]
-h, --help help text
-k, --keep-config keep original configuration file
-l, --local installation from a local git repository
-v, --verbose verbose output
The default location of the installed files:
| Files | Installation folder | Description |
|---|---|---|
smfc.service | /etc/systemd/system | systemd service definition file |
smfc | /etc/default | service command line options |
smfc.conf | /etc/smfc | service configuration file |
smfc.1.gz | /usr/local/share/man/man1 | smfc manual page |
smfc-client.1.gz | /usr/local/share/man/man1 | smfc-client manual page |
smfc | /usr/local/bin or /usr/bin | smfc command |
smfc-client | /usr/local/bin or /usr/bin | smfc-client command |
smfc service | /usr/local/lib/python3.xx or /usr/lib/python3.xx | smfc python package |
Notes for the script:
--local parameter installs from the current folder (the GitHub repository must be cloned first)hd_names= configuration parameter is pre-filled with the list of existing hard disks for the user's convenience
(please check/edit this parameter!)--keep-config parameter, the original configuration file is preserved--verbose parameter, the installation phases are displayedFor remote installation the script can be executed (as root user) this way:
curl --silent https://raw.githubusercontent.com/petersulyok/smfc/refs/heads/main/bin/install.sh|bash /dev/stdin --verbose
or if you want to preserve your existing configuration file:
curl --silent https://raw.githubusercontent.com/petersulyok/smfc/refs/heads/main/bin/install.sh|bash /dev/stdin --verbose --keep-config
For local installation, follow these steps (the uv command is also required):
git clone https://github.com/petersulyok/smfc.git
cd smfc
uv sync
uv build
./bin/install.sh --local --verbose --keep-config
This clones the GitHub repository and builds the smfc package locally before installation.
There is also an uninstallation script (bin/uninstall.sh) for removing smfc.
It has the following command line parameters:
user@host:~$ ./uninstall.sh --help
usage: uninstall.sh [-h|--help] [-k|--keep-config] [-v|--verbose]
-h, --help help text
-k, --keep-config keep original configuration files
-v, --verbose verbose output
It can be executed locally or remotely, just like the installation script. Example of remote execution:
curl --silent https://raw.githubusercontent.com/petersulyok/smfc/refs/heads/main/bin/uninstall.sh|bash /dev/stdin --verbose
The script removes the installed smfc files and the Python package. With the --keep-config parameter, the original
configuration files (/etc/smfc/smfc.conf and /etc/default/smfc) are preserved.
After successful installation, create/edit your new configuration file. Its default location is /etc/smfc/smfc.conf (and command line options live in /etc/default/smfc). If you just upgraded to a new smfc version, you can preserve the existing one.
You have to think over and answer the following questions:
smfc?s-tui, fio, iozone) if you don't have their track records.The configuration file contains sections. The first one for IPMI configuration, the rest for fan controllers.
Edit /etc/smfc/smfc.conf and specify your configuration parameters here:
#
# smfc.conf (C) 2020-2026, Peter Sulyok
# smfc 6.x service configuration parameters
#
# Please read the documentation here: https://github.com/petersulyok/smfc
#
# Ipmi specific parameters.
[Ipmi]
# Path for ipmitool (str, default=/usr/bin/ipmitool)
command=/usr/bin/ipmitool
# Delay time after changing IPMI fan mode (int, seconds, default=10)
fan_mode_delay=10
# Delay time after changing IPMI fan level (int, seconds, default=2)
fan_level_delay=2
# IPMI parameters for remote access (string, default='')
#remote_parameters=-U USERNAME -P PASSWORD -H HOST
# Supermicro platform (string, default='auto')
# Valid platform values:
# auto - automatic discovery based on BMC information
# (an X14/H14 board reporting an unknown product name needs generic_x14 explicitly)
# generic - Generic Supermicro X10-X13/H10-H13 platform
# generic_x9 - Generic Supermicro X9 platform
# generic_x14 - Supermicro X14/H14 platform family (the BMC firmware stack -
# OpenBMC or ATEN - is detected at startup, see README chapter 5)
# X10QBi - Supermicro X10QBi platform
platform_name=auto
# Re-assert FULL fan mode (bool, default=1/true)
enforce_fan_mode=1
# Fan level applied to all configured zones at service termination (int, [-1..100]%, default=100)
# Use -1 to leave the fan levels untouched. On X14/H14 boards this level is only a transition: at exit
# the BMC takes back fan control and rewrites fan levels.
exit_level=100
# Timeout of a single ipmitool execution (int, seconds, default=10, 0 = wait indefinitely)
# It stops a wedged /dev/ipmi0 from parking the control loop; a timed-out call counts as an IPMI error.
#ipmitool_timeout=10
# CPU fan controller: works based on CPU(s) temperature.
[CPU]
# Fan controller enabled (bool, default=0/false)
enabled=1
# IPMI zone(s) (comma- or space-separated list of int, default=0)
ipmi_zone=0
# Calculation method for CPU temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=3.0)
sensitivity=3.0
# Polling time interval for reading temperature (int, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=6)
steps=6
# Minimum CPU temperature (float, C, default=30.0)
min_temp=30.0
# Maximum CPU temperature (float, C, default=60.0)
max_temp=60.0
# Minimum CPU fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum CPU fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# HD fan controller: works based on SATA or SAS HDDs/SSDs temperature.
[HD]
# Fan controller enabled (bool, default=0/false)
enabled=1
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of HD temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (int, sec, default=10)
polling=10
# Discrete steps in mapping of temperatures to fan level (int, default=4)
steps=4
# Minimum HD temperature (float, C, default=32.0)
min_temp=32.0
# Maximum HD temperature (float, C, default=46.0)
max_temp=46.0
# Minimum HD fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum HD fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# Names of the HDs (str multi-line list, default=)
# MUST BE specified in '/dev/disk/by-id/...' form, for example:
# hd_names=/dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_8CH7T91E
# /dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_8CH7T813
# /dev/disk/by-id/ata-WDC_WD100EFAX-68LHPN0_8CHUFFRE
hd_names=
# Path for 'smartctl' command (str, default=/usr/sbin/smartctl).
smartctl_path=/usr/sbin/smartctl
# Standby guard feature for RAID arrays (bool, default=0/false)
standby_guard_enabled=0
# Number of HDs already in STANDBY state before the full RAID array will be forced to it (int, default=1)
standby_hd_limit=1
# NVME fan controller: works based on NVMe SSD(s) temperature.
[NVME]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of NVMe temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (int, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=4)
steps=4
# Minimum NVMe temperature (float, C, default=35.0)
min_temp=35.0
# Maximum NVMe temperature (float, C, default=70.0)
max_temp=70.0
# Minimum NVMe fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum NVMe fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# Names of the NVMe devices (str multi-line list, default=)
# MUST BE specified in '/dev/disk/by-id/...' form, for example:
# nvme_names=/dev/disk/by-id/nvme-ADATA_LEGEND_650_2OFF29AO8DKR
# /dev/disk/by-id/nvme-CT4000P3PSSD7_2446E89408FA
nvme_names=
# GPU fan controller: works based on Nvidia or AMD GPU(s) temperature.
[GPU]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# GPU type (str, ['nvidia', 'amd'], default=nvidia)
gpu_type=nvidia
# AMD GPU temperature sensor (int, 0-junction, 1-edge, 2-memory, default=0)
amd_temp_sensor=0
# Calculation of GPU temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (int, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=5)
steps=5
# Minimum GPU temperature (float, C, default=40.0)
min_temp=40.0
# Maximum GPU temperature (float, C, default=70.0)
max_temp=70.0
# Minimum GPU fan level (int, %, default=35)
# On X14/H14 boards a value below 5 is silently raised to 5%: the duty smfc writes there has no floor
# of its own, and smfc suspends the BMC's own thermal loop while it drives the fans.
min_level=35
# Maximum GPU fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=30-35, 50-55, 60-90, 65-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# GPU device IDs (comma- or space-separated list of int, default=0)
# These are indices in nvidia-smi temperature report.
gpu_device_ids=0
# Path for 'nvidia-smi' command (str, default=/usr/bin/nvidia-smi).
nvidia_smi_path=/usr/bin/nvidia-smi
# Path for 'rocm-smi' command (str, default=/usr/bin/rocm-smi)
rocm_smi_path=/usr/bin/rocm-smi
# NPU fan controller: works based on Ascend NPU(s) temperature (npu-smi), e.g. Atlas 300I Duo.
# A device is an NPU card (npu-smi -i id); for multi-chip cards the hottest chip is used.
[NPU]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of NPU temperatures (int, [0-minimum, 1-average, 2-maximum], default=1)
temp_calc=1
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (float, sec, default=5; npu-smi is slow, ~1.5s/call)
polling=5
# Discrete steps in mapping of temperatures to fan level (int, default=5)
steps=5
# Minimum NPU temperature (float, C, default=40.0)
min_temp=40.0
# Maximum NPU temperature (float, C, default=85.0)
max_temp=85.0
# Minimum NPU fan level (int, %, default=35)
min_level=35
# Maximum NPU fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=50-40, 70-70, 85-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# NPU card IDs (comma- or space-separated list of int, default=0)
# These are the '-i' card IDs reported by `npu-smi info -m` (may not start from 0).
npu_device_ids=0
# Path for 'npu-smi' command (str, default=npu-smi). May be a bare command name (resolved via PATH)
# or a full path (e.g. /usr/local/Ascend/ascend-toolkit/latest/bin/npu-smi).
npu_smi_path=npu-smi
# Timeout for a single npu-smi call (float, sec, default=15.0)
npu_smi_timeout=15.0
# PCI fan controller: works based on the HWMON temperature of PCI device(s).
[PCI]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Calculation of PCI temperatures (int, [0-minimum, 1-average, 2-maximum], default=2)
temp_calc=2
# Threshold in temperature change before the fan controller reacts (float, C, default=2.0)
sensitivity=2.0
# Polling interval for reading temperature (float, sec, default=2)
polling=2
# Discrete steps in mapping of temperatures to fan level (int, default=6)
steps=6
# Minimum PCI temperature (float, C, default=30.0)
min_temp=30.0
# Maximum PCI temperature (float, C, default=60.0)
max_temp=60.0
# Minimum PCI fan level (int, %, default=35)
min_level=35
# Maximum PCI fan level (int, %, default=100)
max_level=100
# User-defined control function (comma- or space-separated list of temp-level value pairs, default=empty)
# Temp in °C, level in %; at least 2 pairs, temps strictly ascending. If this parameter specified
# then min_temp/max_temp/min_level/max_level parameters are skipped
#control_function=45-40, 65-70, 80-100
# Moving average window size for temperature smoothing (int, default=1, 1=disabled)
smoothing=1
# Consecutive failed temperature reads tolerated per device (int, default=3, 0=disabled)
# Inside this budget the last known good temperature is reused, above it smfc stops
error_tolerance=3
# The PCI device(s) of this section. Specify exactly one of the three parameters below.
# PCI slot address(es) (comma- or space-separated list of str, default=empty)
# All of them must be the same model, and a card that is not present is an error.
#pci_address=0000:05:00.0, 0000:06:00.0
# PCI vendor:device ID (str, four hexadecimal digits on each side, default=empty)
# Selects every card of that model.
#pci_id=1d6a:07b1
# PCI driver name (str, default=empty)
# Selects every PCI device the driver serves.
#pci_driver=atlantic
# HWMON sensor index to read (int, default=1, reads temp1_input)
# A card can expose several sensors. Read the labels with `cat /sys/class/hwmon/hwmon*/temp*_label`.
temp_sensor=1
# CONST fan controller: sets constant fan level (without any heat source) for IPMI zones(s).
[CONST]
# Fan controller enabled (bool, default=0/false)
enabled=0
# IPMI zone(s) (comma- or space-separated list of int, default=1)
ipmi_zone=1
# Polling interval for checking/resetting level if needed (int, sec, default=30)
polling=30
# Constant fan level (int, %, default=50)
level=50
# HTTP exporter: serves /snapshot (JSON for smfc-client) and /metrics (Prometheus text format).
[Exporter]
# Enable the HTTP exporter (bool, default=0/false)
enabled=0
# IP to bind on (str, default=127.0.0.1)
# Use 127.0.0.1 for local-only access; use 0.0.0.0 or a specific LAN IP for remote Prometheus.
bind_address=127.0.0.1
# TCP port (int, 1..65535, default=9099)
port=9099
Important notes:
[Ipmi] remote_parameters=-U USERNAME -P PASSWORD -H HOST parameter can be used for remote access for the IPMI interface. It could be useful for a VM setup where the hard disks are configured with PCI passthrough (e.g. a TrueNAS running in a VM on Proxmox), but IPMI needs to be accessed "remotely". Please note that the HOST is the BMC network address (not the VM host address).[HD] hd_names= is a compulsory parameter for HD fan controller, and it must be specified in /dev/disk/by-id/... form. Please note that the /dev/sda form is not persistent and could change after a reboot![NVME] nvme_names= is a compulsory parameter for NVME fan controller, and it must be specified in /dev/disk/by-id/... form. Please note that the /dev/nvme0n1 form is not persistent and could change after a reboot![CPU] / [HD] / [NVME] min_level= / max_level= should be configured in alignment with threshold configuration (see more details in this chapter). Be patient, several refinement cycles could happen.[CPU] / [HD] / [NVME] / [GPU] / [NPU] control_function= defines an advanced multi-segment user-defined control function as a list of temp-level value pairs (at least 2 pairs, temperatures strictly ascending). When specified, it overrides the linear min_temp/max_temp/min_level/max_level form. See chapter 2.2 for details.[CPU:0], [CPU:1]). Each instance has its own full set of parameters and can be assigned to a different IPMI zone with a different fan curve. Two enabled instances of the same type must not share the same IPMI zone. See chapter 1.4 for details../config/samples folder.smfc.smfc?After manual installation, smfc can be started and stopped as a standard systemd service. Remember to reload systemd configuration after a new installation or if you changed the service definition file:
systemctl daemon-reload
systemctl start smfc.service
systemctl stop smfc.service
systemctl restart smfc.service
systemctl status smfc.service
● smfc.service - Supermicro Fan Control
Loaded: loaded (/etc/systemd/system/smfc.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-03-03 21:32:22 CET; 7min ago
Invocation: b613d841e1cf43f4ace80d472623ed4c
Main PID: 82521 (smfc)
Tasks: 1 (limit: 154231)
Memory: 12M (peak: 19.5M)
CPU: 1.271s
CGroup: /system.slice/smfc.service
└─82521 /usr/bin/python3 /usr/local/bin/smfc -c /etc/smfc/smfc.conf -l 3
Mar 03 21:37:27 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 67% (winner: CPU=67%/51.0C, losers: NVME=35%/37.9C)
Mar 03 21:37:30 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 35% (winner: CPU=35%/31.0C, losers: NVME=35%/37.9C)
Mar 03 21:38:46 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 67% (winner: CPU=67%/49.0C, losers: NVME=35%/37.9C)
Mar 03 21:38:49 nas smfc.service[82521]: Shared IPMI zone [0]: new level = 35% (winner: CPU=35%/31.0C, losers: NVME=35%/37.9C)
The smfc program has the following parameters:
root@nas$ smfc --help
usage: smfc [-h] [-c CONFIG_FILE] [-v] [-l {0,1,2,3,4}] [-o {0,1,2}] [-nd] [-s] [-ne]
options:
-h, --help show this help message and exit
-c CONFIG_FILE configuration file (default is /etc/smfc/smfc.conf)
-v, --version show program's version number and exit
-l {0,1,2,3,4} set log level: 0-NONE, 1-ERROR(default), 2-CONFIG, 3-INFO, 4-DEBUG
-o {0,1,2} set log output: 0-stdout, 1-stderr, 2-syslog(default)
-nd no dependency checking at start
-s use sudo command
-ne deprecated, use [Ipmi] exit_level=-1 instead
[!NOTE] The
-neoption is deprecated. It is still accepted and still means "no fan level change at exit", but it is equivalent to the[Ipmi] exit_level=-1configuration parameter and it will be removed in a future release.
smfc command-line options can be specified in /etc/default/smfc file if you run smfc as a systemd service.
If you are testing your configuration, you can start smfc directly in a terminal (logging to the standard output on debug log level):
smfc -o 0 -l 3
In case of Docker installation, smfc will be executed automatically when the container is started. Its command-line parameters can be specified in the docker-compose file.
All messages will be logged to the specific output and the specific level.
With the help of command journalctl you can check logs easily. For example:
listing service logs of the last two hours:
journalctl -u smfc --since "2 hours ago"
listing service logs from the last boot:
journalctl -b -u smfc
When enabled, smfc exposes live fan and temperature data over HTTP. This powers two use cases: smfc-client reads it to give you an instant status overview from the command line, and Prometheus can scrape it to feed a Grafana dashboard with historical charts and alerts.
The exporter is disabled by default. Enable it in smfc.conf:
[Exporter]
enabled=1
bind_address=127.0.0.1 # change to 0.0.0.0 (or a specific LAN IP) for remote access
port=9099
The exporter implements three endpoints:
/metrics — for Prometheus and Grafana: feeds dashboards and alerting rules with live fan and temperature data./snapshot — for smfc-client and ad-hoc inspection: delivers the same data as a structured JSON object./healthz — for monitoring and orchestration: confirms the service is up and responding.Verify locally:
curl -s http://127.0.0.1:9099/metrics
curl -s http://127.0.0.1:9099/snapshot | jq .
curl -s http://127.0.0.1:9099/healthz
All data is served from the daemon's already-cached state — no ipmitool or smartctl subprocesses are spawned per request, so querying the exporter can never wake disks that smfc has put to sleep. A bind failure (e.g. port already in use) is logged but does not stop the fan-control loop.
For Grafana integration with a ready-to-import dashboard and a full monitoring stack setup, see grafana/GRAFANA.md.
smfc-client gives you an instant read-only view of what smfc is currently doing: temperatures, fan levels, IPMI zone states, and standby status — all in one command. Use it to confirm your configuration is working as expected without digging through system logs. It is safe to run at any time and never changes any fan state.
smfc-clientis shipped withsmfc v6.0.0and later
It reads the same configuration file as the service (/etc/smfc/smfc.conf by default), so it always reports on the same controllers and zones the service manages. There are two data sources, selected automatically:
[Exporter] section is enabled in the configuration (see chapter 13.), smfc-client fetches the /snapshot JSON from the running service. This is dramatically faster because it serves already-cached state and spawns no ipmitool/smartctl subprocesses (so it can never wake disks the daemon has put to sleep).--standalone is given, smfc-client reads the BMC and disks directly via ipmitool/hwmon/smartctl. This path typically needs root, so run it with sudo smfc-client -s.The first lines of the output state which source was used, and say only what was established: source: smfc service (live snapshot) online; source: ipmitool (smfc service is not reachable) when the exporter was asked and did not answer; and source: ipmitool (smfc service not queried) when the BMC was read directly without asking at all, which is what --standalone and a configuration without an exporter give - smfc may well be running in that case. The online report additionally shows the service uptime (verbose only) and annotates the fan-mode line with how many times fan control was re-asserted and how old the reading is. It colours that line by the control state the service reported, not by the fan mode value, and prints the reason when control was lost; the offline report reads the IPMI fan mode live and warns if the BMC is not in FULL mode. On X14/H14 boards the fan-mode line reports what is actually driving the fans instead - see chapter 5.
The report has two modes:
--verbose (-V) unfolds the full picture: the complete BMC fingerprint, the service uptime, and a per-controller block below the Fan controllers table showing the steering window ([temp_min..temp_max]C → [level_min..level_max]%), the active LUT (a Curve: line for controllers configured with control_function=...), the per-device temperatures, and the Standby Guard status folded into the HD block.When stdout is a terminal and --no-color is not set, the report is colourised:
BMC, Fan controllers, [CPU]/[HD]/[NVME]/[GPU]/[NPU], IPMI zones (live)) render in bold bright-blue.Temp and Level cell is banded against the controller's own steering window: DIM below the floor (idle), GREEN in the lower 70 % (working), YELLOW in the upper 30 % (warm — fans ramping), RED at or above the ceiling (curve maxed out). The per-device temperatures inside the verbose Device list use the same banding against the parent controller's window — handy for spotting a single hot disk dragging the aggregate up.STANDBY in dim grey and ACTIVE in green.FULL in green and any other mode in red, accompanied by a warning.| Flag | Long form | Argument | Default | Description |
|---|---|---|---|---|
-c FILE | --config FILE | path | /etc/smfc/smfc.conf | Configuration file to read (same format the service uses). |
-s | --sudo | — | off | Run ipmitool and smartctl via sudo. Required on the standalone path as non-root. |
-V | --verbose | — | off | Expand each enabled fan controller into a per-controller block with window, curve, devices, standby state, and per-device read error counts. The read error counts are shown only in this mode — the default summary has no per-device rows. |
-nc | --no-color | — | auto | Disable ANSI colors. Colors auto-disable when stdout is not a terminal. |
-sa | --standalone | — | off | Bypass the service exporter and read sensors directly. |
-v | --version | — | — | Print smfc-client X.Y.Z and exit. |
-h | --help | — | — | Show help and exit. |
Exit codes: 0 = readings printed (per-controller errors are non-fatal), 6 = configuration file missing or invalid, 8 = IPMI/BMC error (e.g. ipmitool not found or permission denied), 9 = disk detection unavailable (udev missing).
Default (non-verbose) — the at-a-glance summary:
smfc-client 6.0.0
config: /etc/smfc/smfc.conf
source: smfc service (live snapshot)
BMC
Product : X11SCH-LN4F (6929)
Fan mode : FULL (1) (enforced 0x, read 0.3s ago)
Fan controllers
Section Type Zones Devices Temp Level
------- ----- ----- ------- ------ -----
CPU cpu [0] 1 51.0 C 47 %
HD hd [1] 4 39.0 C 45 %
CONST const [2] - - 50 %
IPMI zones (live)
Zone Level
---- -----
0 47 %
1 45 %
2 50 %
The Level column shows what each controller asks for, while the IPMI zones (live) table shows what each zone actually runs at. The two agree here, but on a shared zone only the winner's request is applied, so an overruled controller's Level stays below its zone. Run with -V to see which controller won.
With --verbose (-V) the full report expands the BMC fingerprint, adds the service uptime, and emits one block per enabled fan controller with its steering window, active curve (when a control_function is configured), and per-device temperatures. The HD controller's Standby Guard line is folded into its block; CONST controllers stay in the Fan controllers table but don't get their own block (no devices, no curve):
smfc-client 6.0.0
config: /etc/smfc/smfc.conf
source: smfc service (live snapshot)
uptime: 1d 00:00:00
BMC
Manufacturer : Super Micro Computer Inc. (10876)
Product : X11SCH-LN4F (6929)
Firmware : 1.74
IPMI version : 2.0
Platform : GenericPlatform
Fan mode : FULL (1) (enforced 0x, read 0.3s ago)
Fan controllers
Section Type Zones Devices Temp Level
------- ----- ----- ------- ------ -----
CPU cpu [0] 1 51.0 C 47 %
HD hd [1] 4 39.0 C 45 %
CONST const [2] - - 50 %
[CPU] cpu zone(s)=[0] shared=no polling=2.0s
Window: T=[35..75]C → L=[35..100]%
Curve: 35→35, 55→50, 70→80, 75→100
Temp: 51.0 C → Level: 47 %
Device Temp
------ ------
cpu0 51.0 C
[HD] hd zone(s)=[1] shared=no polling=960.0s
Window: T=[35..48]C → L=[35..100]%
Temp: 39.0 C → Level: 45 %
Standby Guard: enabled (limit=2) Array: SAAA (1/4 standby)
Device Temp State Errors
---------------------------------- ------ ------- ------
ata-WDC_WD120EFAX-68UNTN0_99GMFQVW 36.0 C STANDBY 9
ata-WDC_WD120EFAX-68UNTN0_ASWRX1X8 38.0 C ACTIVE 0
ata-WDC_WD120EFAX-68UNTN0_F9ZAPZG7 39.0 C ACTIVE 0
ata-WDC_WD120EFAX-68UNTN0_MPZ04PTK 39.0 C ACTIVE 0
IPMI zones (live)
Zone Level
---- -----
0 47 %
1 45 %
2 50 %
On a shared IPMI zone (see chapter 1.3) the report keeps what a controller asked for separate from what its zone actually got. Below, CPU and NVME both drive zone 0: the CPU wins the arbitration with 74%, while the NVME -- whose window maps 39.9 °C to its 35% floor -- is overruled. The Level column and the Temp: → Level: line always show the controller's own request, so they never contradict the Window:/Curve: lines; the level that actually reached the BMC is appended to the overruled controller's block as (zone 0 applied: 74 %). Only the banner and BMC block are omitted here:
Fan controllers
Section Type Zones Devices Temp Level
------- ---- ----- ------- ------ -----
CPU cpu [0] 1 65.4 C 74 %
NVME nvme [0] 3 39.9 C 35 %
[CPU] cpu zone(s)=[0] shared=yes polling=2.0s
Window: T=[40..75]C → L=[35..100]%
Curve: 40→35, 55→50, 70→80, 75→100
Temp: 65.4 C → Level: 74 %
Device Temp
------ ------
cpu0 65.4 C
[NVME] nvme zone(s)=[0] shared=yes polling=2.0s
Window: T=[38..65]C → L=[35..100]%
Temp: 39.9 C → Level: 35 % (zone 0 applied: 74 %)
Device Temp
---------------------------------- ------
nvme-CT4000P3PSSD8_2412E7B1C4A9 39.9 C
nvme-CT4000P3PSSD8_2412E7B1D5F2 37.9 C
nvme-ADATA_LEGEND_800_2Q7714KX3ZTM 38.9 C
IPMI zones (live)
Zone Level
---- -----
0 74 %
A few things to notice in the verbose block:
shared=yes/no tells you whether this controller shares its IPMI zone with another controller and therefore goes through zone arbitration. It is reported for every participant on a shared zone, including the one currently winning it -- it is not a winner/loser flag. To see who is actually driving the zone, compare the block's Level: with the (zone N applied: ...) note described below.Window: and Curve: describe the active steering curve. When a control_function=... is configured, Window: shows the curve's actual [temp_min..temp_max] → [level_min..level_max] envelope (not the legacy min_temp/max_temp keys, which the runtime ignores in this mode), and Curve: lists the breakpoint pairs directly. Controllers without a control_function (legacy linear mode) skip the Curve: line — the Window: already says everything.Temp: X → Level: Y is the aggregated temperature the curve was evaluated against and the level this controller requested as a result -- so it always agrees with the Window:/Curve: lines above it. On a shared zone the arbiter applies the highest request across all contributors, so a losing controller's request is not what reaches the BMC; in that case the applied zone level is appended as (zone N applied: Z %). The note is omitted when the two agree (non-shared zones, and the winner of a shared zone). The applied level of every zone is also listed in the IPMI zones (live) table at the end of the report. --standalone mode cannot show who won the zone: it reports the current state only, so every controller simply displays its zone's level and the (zone N applied: ...) note never appears. With colours on, both cells carry the band colour against the same window — at a glance you see whether the controller is idle, working, ramping, or maxed out.Device names for HD and NVMe controllers are shown as the path basename (e.g. ata-WDC_WD120EFAX-68UNTN0_99GMFQVW instead of /dev/disk/by-id/ata-WDC_WD120EFAX-68UNTN0_99GMFQVW) so per-disk rows stay scannable. The snapshot JSON and Prometheus labels still carry the full stable-id paths.Standby Guard appears as a single line inside the [HD] block when the feature is enabled; the per-disk STANDBY/ACTIVE annotation lives in the right-most column of that block's device table. Disks in standby render in dim grey because the temperature reading is stale (smartctl is skipped while a disk sleeps).Errors is a conditional column of the verbose blocks, so it needs -V: the default summary has no per-device rows at all. It only appears when at least one device of that controller has failed a temperature read since smfc started (see chapter 2.4). It then shows the lifetime failure count of every device of the controller, so the failing one stands out against its healthy neighbours, and non-zero values are highlighted. On a healthy machine the column is not rendered at all. Note it is only available in online mode: in --standalone mode the client builds its own controllers and has no history, so a failing read shows up as an ERROR temperature cell instead.Each fan controller is constructed independently, so a single failing controller (e.g. a missing GPU tool or a non-existent disk) shows an ERROR row in the Fan controllers table while the rest of the report still renders.
Most probably, there was an assertion (i.e., the rotational speed of a fan went above or below an IPMI threshold) and IPMI switched back that zone to full rotational speed. You can check the current fan rotational speeds:
ipmitool sdr
and you can also check IPMI event log and list assertion events:
root@home:~# ipmitool sel list
1 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
2 | 10/19/2023 | 05:15:35 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Asserted
3 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Non-recoverable going low | Deasserted
4 | 10/19/2023 | 05:15:38 PM CEST | Fan #0x46 | Lower Critical going low | Deasserted
5 | 10/19/2023 | 05:20:59 PM CEST | Fan #0x46 | Lower Critical going low | Asserted
If the problematic fan (causing the alert) is identified, then you must adjust its threshold. This process could take several adjustment cycles. Be patient :) You may read this chapter for more details.
The configuration is the following:
128 GB ECC DDR4-2666MHz RAM
Fractal Design Node 804 case, with separate chambers for the motherboard and the hard disks:
Proxmox 9
8 x WD Red 12TB (WD120EFAX) hard disks in ZFS RAID
4 x Noctua NF-F12 PWM fans (FAN1, FAN2, FAN3, FAN4) in IPMI CPU zone
2 x Noctua NF-F12 PWM on an Y-adapter + Noctua NF-A14 PWM fans (FANA, FANB) in IPMI HD zone
Further readings:
smartctl)Written with StackEdit.
Python
97.8%
Shell
2.2%