Solving the fan-curve problem
• Created by: Domonkos Poór

The age-old question: How on earth do you control an NVIDIA GPU fan on Linux under Wayland?
The usual answer is a bad one: nvidia-settings needs an X server, and Wayland does not have one.
The default driver curve is not adjustable. For a long time the practical
options were to accept the noise or to keep an X session around for a settings tool.
Why the existing tools did not fit
Several tools solve this problem, and most of them work. The problem is the curve shape. Almost every NVIDIA tool interpolates linearly between temperature and speed points. The fan speed tracks the temperature continuously, so the pitch drifts up and down as the card warms and cools.
The ear is good at detecting a changing pitch, and bad at filtering it. A fan that holds one speed is easier to ignore than a fan that slides between speeds, even when the average noise is the same.
I wanted a different curve shape.
The idea: stair-step with hysteresis
The daemon holds a constant fan speed until the temperature crosses the next threshold by a configurable margin. That margin is the hysteresis. It stops the fan from oscillating when the temperature sits on a boundary.
Four points define the curve:
| Point | Meaning |
|---|---|
temp_min |
Below this, use speed_min |
temp_low |
The next step up |
temp_high |
The step above that |
temp_max |
Above this, use speed_max |
This way the fan will stay at a constant speed for the given temperature range, eliminating the constant WOOSHH-fsssh-WOOSHH-fsssh that all of us are all too familiar with.
The implementation
The daemon is written in Go. It uses the NVIDIA Management
Library directly through the official Go bindings,
so it does not need nvidia-settings, an X server, or a pipe from
nvidia-smi. It works on X11, on Wayland, and on a headless machine.
The main properties:
- Multi-GPU by default. It enumerates every device at startup and keeps independent state per GPU.
- TOML configuration. The default path is
/etc/coolyourcard.conf. A--configflag overrides it. - Two curve functions.
stepfor the stair-step curve,linearfor the classic interpolation. The function is a config value. - Silent mode. Below
temp_min, the daemon returns the fans to driver control instead of holding a minimum speed. - Graceful shutdown. On SIGTERM, SIGINT, SIGQUIT, SIGTSTP, or SIGPWR, the daemon restores driver fan control before it exits. A deferred call does this, so it runs on any exit path, including a panic.
- Live reload. SIGHUP re-reads the configuration without a restart, and keeps the current fan state, so the transition is smooth.
- Fault tolerance. If one GPU fails a temperature query or a fan call, the daemon logs a warning and continues with the others.
A systemd unit runs it as a service. Restart=on-failure covers the signals
that cannot be caught, such as SIGKILL, where the fans stay at their last speed until the service restarts.
temp_min = 30
temp_low = 45
temp_high = 60
temp_max = 75
speed_min = 25
speed_low = 40
speed_high = 60
speed_max = 85
interval = 2
threshold = 4
silent = true
function = "step"
Where it sits in the landscape
| Tool | Curve | Hysteresis | Vendor | Language |
|---|---|---|---|---|
| CoolYourCard | Stair-step | Yes | NVIDIA | Go |
| nvfd | Linear | No | NVIDIA | C |
| nvfancontrol | Linear | No | NVIDIA | Rust |
| GreenWithEnvy | Linear | No | NVIDIA | Python |
| NVML-GPU-Control | Stair-step | No | NVIDIA | Python |
| amdgpu-fancontrol | Stair-step | Yes | AMD | Bash |
No NVIDIA tool implements stair-step with hysteresis. The closest matches are
NVML-GPU-Control for Nvidia, without hysteresis, and amdgpu-fancontrol,
which is AMD only. That is the whole reason this project exists.
I considered adding the feature to
nvfd instead. It is mature and well-engineered.
I built CoolYourCard separately for four reasons.
nvfdis about 3,000 lines of C across 15 or more files, and Go removes a class of memory bugs.- The stair-step logic is stateful, because it remembers the current speed
level per GPU, and
nvfdis built around a stateless linear model, so the change is more invasive than it looks. - I wanted control of the release cycle and the config format.
- And a working reference makes a future contribution to
nvfdless risky.