Cooling and Overclocking a Raspberry Pi 4 Safely: Measure, Then Tune
The Raspberry Pi 4 is far more powerful than earlier models, and it also produces more heat. Without a heatsink, a long compile, video encode or OpenCV job can push the SoC to its temperature limit, at which point the firmware reduces the clock speed. Your program keeps running, just slower, and many people never notice why.
This tutorial takes an engineering approach: first measure temperature and clock speed under a repeatable load, then add cooling, then overclock only if the cooling has headroom, and finally re-measure to prove the result is stable.
How Thermal Throttling Works
The firmware watches the SoC temperature continuously. At the soft limit of 80 °C it starts reducing the ARM clock to limit heat. If the temperature still rises to 85 °C, it throttles further. The vcgencmd get_throttled flags record whether this has happened since boot, so you can check after a test instead of watching the whole time.
| Command | Shows | Example output |
|---|---|---|
| vcgencmd measure_temp | SoC temperature | temp=61.3'C |
| vcgencmd measure_clock arm | Current ARM clock in Hz | frequency(48)=1800457088 |
| vcgencmd get_throttled | Throttling and power flags | throttled=0x0 |
| vcgencmd measure_volts core | Core voltage | volt=0.8600V |
Step 1: Measure Under Load
Install stress-ng with sudo apt install -y stress-ng. The script below starts a four-core CPU load for a set time and records temperature, clock and throttle flags to a CSV file every two seconds. Run it once on the bare board, once with your cooling solution, and again after overclocking, and compare the files.
#!/usr/bin/env python3
# thermal_test.py - repeatable thermal test for a Raspberry Pi 4
# Usage: python3 thermal_test.py 600 baseline.csv (seconds, output file)
import csv
import subprocess
import sys
import time
DURATION = int(sys.argv[1]) if len(sys.argv) > 1 else 600
OUTFILE = sys.argv[2] if len(sys.argv) > 2 else 'thermal.csv'
def vc(*args):
"""Return the value part of a vcgencmd reply, e.g. '61.3' from temp=61.3'C."""
out = subprocess.check_output(['vcgencmd', *args], text=True).strip()
return out.split('=')[1]
def sample():
temp = float(vc('measure_temp').rstrip("'C"))
mhz = int(vc('measure_clock', 'arm')) // 1_000_000
flags = vc('get_throttled')
return temp, mhz, flags
# Start stress-ng on all 4 cores in the background
load = subprocess.Popen(['stress-ng', '--cpu', '4', '--timeout', f'{DURATION}s'],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
start = time.time()
max_temp, min_mhz = 0.0, 10_000
with open(OUTFILE, 'w', newline='') as f:
w = csv.writer(f)
w.writerow(['seconds', 'temp_c', 'arm_mhz', 'throttled'])
while load.poll() is None: # until stress-ng exits
t, mhz, flags = sample()
elapsed = int(time.time() - start)
w.writerow([elapsed, t, mhz, flags])
max_temp, min_mhz = max(max_temp, t), min(min_mhz, mhz)
print(f'{elapsed:4d}s {t:5.1f} C {mhz:5d} MHz {flags}')
time.sleep(2)
print(f'Peak temperature: {max_temp:.1f} C, lowest clock: {min_mhz} MHz')
print('Final flags:', vc('get_throttled'), '(0x0 means no throttling)')
If the lowest clock in the log is below the board's normal maximum, or bit 18 or 19 of the final flags is set (values like 0x40000 or 0x80000), the board throttled during the test.
Step 2: Add Cooling
| Option | Noise | Comment |
|---|---|---|
| Stick-on heatsinks | Silent | Helps short bursts; not enough for long full load in a closed case |
| Large passive aluminium case | Silent | The whole case acts as a heatsink; good for NAS and servers |
| Official Case Fan or a 30 mm 5 V fan | Low to medium | Strong cooling; best when switched by temperature |
| Heatsink plus fan HAT | Medium | Highest headroom for overclocking |
A fan running all the time is noisy and wears out. Bookworm's firmware can switch it for you with the gpio-fan overlay. Drive the fan through an NPN transistor (for example a 2N2222 with a 1 k base resistor from GPIO14, fan negative to the collector, emitter to GND, and a 1N4148 diode across the fan) because a GPIO pin cannot supply fan current. Then add this line to /boot/firmware/config.txt:
# Fan on GPIO14 switches on at 60 C (value in millidegrees) dtoverlay=gpio-fan,gpiopin=14,temp=60000
The same setting is available in raspi-config under Performance Options, Fan.
Step 3: Overclock in Small Steps
Overclocking is set in /boot/firmware/config.txt under the [all] section. A commonly used, moderate setting for a well-cooled Pi 4 is shown below. over_voltage raises the core voltage in steps of 0.025 V, which the higher clock usually needs to remain stable.
[all] # Moderate Pi 4 overclock - only with active cooling over_voltage=6 arm_freq=2000 # Optional GPU clock increase gpu_freq=750
- Back up
config.txtfirst:sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.bak. - Increase
arm_freqin steps of 100 MHz and run the thermal test after each step. - If the Pi will not boot after a change, put the card in a PC and edit
config.txton the boot partition to undo it. The boot partition is FAT32, so Windows can open it directly. - Do not use
force_turbo=1together withover_voltageabove 6. That combination sets a permanent bit in the SoC that records the board was over-volted, which can void the warranty. - Not every chip reaches the same clock. If a setting causes random crashes or SD card errors, step back down; stability matters more than benchmark numbers.
Common Mistakes
- Judging stability by a quick boot. Always run at least ten minutes of full load before trusting a setting.
- Forgetting the power supply. Overclocked boards draw more current; under-voltage flags (bit 0 or 16) mean the supply, not cooling, is the problem.
- Connecting a fan straight to a GPIO pin. It will not spin properly and can damage the pin.
- Covering the board in a closed plastic case with a fan but no air inlet.
Summary
Measure first with a repeatable stress test that logs temperature, clock and throttle flags. Add heatsinks or a temperature-controlled fan with dtoverlay=gpio-fan, then raise arm_freq with a matching over_voltage in small steps, re-running the same test each time. Keep the settings only if the final flags read 0x0 and the clock never drops under load.