Setting Manual Fan Speeds on Dual RTX 3090 GPUs
A Tale of Fans, Frustration, and Finally Figuring It Out
Introduction

What began as a simple mission—“Make the fans go brrrr”—quickly transformed into an unexpected deep dive into the mysterious inner workings of nvidia-settings on a headless Linux box.
As any good homelab tinkerer knows, nothing is ever truly simple, especially when GPUs are involved.
This post chronicles my journey to manually set fan speeds on two RTX 3090s, complete with a surprising plot twist: the second GPU’s only fan decided it wanted to be called fan2 instead of fan1.
Because why shouldn’t a GPU have an identity crisis?
Understanding the Goal
The mission was clear:
- Enable manual fan control on both GPUs
- Set the fans to full send (100%)
- Make the configuration stick at boot
Doing it on a headless system meant turning to nvidia-settings via xvfb-run, turning a GUI-driven tool into a CLI-friendly one.
Why xvfb-run Was Needed (a.k.a. “Why won’t this thing work headless?”)
Before diving into fan drama, it’s worth answering the question:
Why on Earth was xvfb-run even involved?
nvidia-settings is fundamentally a GUI application, even when you’re only asking it to perform CLI-friendly tasks like toggling fan control or changing clock speeds. On a headless system—no monitor, no X session, no window manager—nvidia-settings sulks and refuses to cooperate because it expects an active X display to attach to.
That’s where xvfb-run comes in.
What is xvfb-run?
xvfb-run is a wrapper around Xvfb: the X virtual framebuffer.
Xvfb provides a full X11 display environment without requiring physical hardware. It creates a virtual display in memory, allowing GUI applications to run in environments where no actual screen exists.
In other words:
xvfb-runtricks GUI applications into thinking there’s a real monitor attached—even on a fully headless machine.
Why was it needed here?
Because nvidia-settings absolutely insists on having an X display.
Without one, it refuses to apply configuration settings, and instead produces errors like:
ERROR: Unable to load info from any available system
or the classic:
You do not appear to be using the NVIDIA X driver.
Both of which are lies… but arguments with nvidia-settings are unwinnable.
So instead, we give it the fake display it craves.
Early Success With GPU #1
The first attempt looked promising enough:
sudo xvfb-run -a nvidia-settings -a [gpu:0]/GPUFanControlState=1 -a [fan:0]/GPUTargetFanSpeed=100 -a [gpu:1]/GPUFanControlState=1 -a [fan:1]/GPUTargetFanSpeed=100
GPU #1 spun up like a turbine preparing for takeoff.
GPU #2, however, responded more like a sleepy housecat: “No.”
The Unexpected Plot Twist: Fan Numbering Madness
Here’s where things got interesting.
I had assumed—naively—that fans were numbered per GPU, meaning each card would have its own fan:0, fan:1, etc.
nvidia-settings, however, had other plans.
It turns out fan indices are global, not per GPU:
fan:0→ the first GPU’s fanfan:1→ unused in this setupfan:2→ the second GPU’s fan
In other words, fan:1 didn’t belong to GPU #2 at all.
GPU #2’s fan was out there living its best life as fan:2.
Mystery solved… After spending about an hour troubleshooting with with a local LLM. That was trying really hard to overheat my new GPUs (since you know fans didn’t kick on until about 65° C)
So to summarize, the issue is that I could set the fan speed on GPU 1 but GPU 2 would either kill the fan speed completely (and temps would not make it spin back up) or be completely unresponsive on GPU 2. It was only after noticing that a few times the LLM gave me the same damn answer of xvfb-run -a nvidia-settings -a "[gpu:0]/GPUFanControlState=1" -a "[fan:0]/GPUTargetFanSpeed=100" -a "[gpu:1]/GPUFanControlState=1" -a "[fan:0]/GPUTargetFanSpeed=100"
Then it hit me like a brick wall, this command was setting the fan0 to 100% twice. That is what got me thinking to increment the number a few times just to see what would happen.
Applying the Correct Configuration
Once I accepted the GPU’s chosen identity, the correct command became:
xvfb-run -a nvidia-settings -a "[gpu:0]/GPUFanControlState=1" -a "[fan:0]/GPUTargetFanSpeed=100" -a "[gpu:1]/GPUFanControlState=1" -a "[fan:2]/GPUTargetFanSpeed=100"
This finally gave both GPUs the full-speed treatment they deserved and kept the local LLM from melting my GPUs, mostly… One still got into the +90° C range, though I can hardly blame it it has barely any room to move air, give that it is back to back with another 3090 which are 3 PCI slots thick in a case designed to hold 7 PCI slots worth of equipment.
Automating the Process With systemd
Since typing this command manually at each boot would be a special kind of misery, I automated it, after of course verifying that the fan numbers didn’t change on me.
Helper Script
Create /usr/local/bin/fancontrol.sh:
#!/usr/bin/env bash
/usr/bin/xvfb-run -a /usr/bin/nvidia-settings -a "[gpu:0]/GPUFanControlState=1" -a "[fan:0]/GPUTargetFanSpeed=100" -a "[gpu:1]/GPUFanControlState=1" -a "[fan:2]/GPUTargetFanSpeed=100"
Make it executable:
sudo chmod +x /usr/local/bin/fancontrol.sh
systemd Service File
Create /etc/systemd/system/fancontrol.service:
[Unit]
Description=Set NVIDIA fan speeds once, on boot
After=systemd-modules-load.service
Wants=systemd-modules-load.service
[Service]
Type=oneshot
User=root
Group=root
ExecStart=/usr/local/bin/fancontrol.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Enable it:
sudo systemctl daemon-reload
sudo systemctl enable fancontrol.service
sudo systemctl start fancontrol.service
This ensures the fans roar to life automatically, every time the system boots.
Conclusion
If there’s anything to take away from this adventure, it’s this:
Actually… I don’t know what I learned aside from a new tool called xvfb-run
Sometimes that’s enough.
Written by David, made funnier with AI