If you are building a dedicated raspberry pi with plex media server in 2026, the direct answer for your hardware target is the Raspberry Pi 5 (8GB) paired with an NVMe M.2 HAT. While older guides suggest USB 3.0 enclosures, the Pi 5’s exposed PCIe 2.0 x1 lane eliminates the USB storage bottleneck, allowing your Plex database to index large libraries without I/O latency. This guide assumes you are running the 64-bit Raspberry Pi OS (Bookworm) and using the official 27W USB-C PD power supply to prevent peripheral brownouts.
Hardware Selection: Why the Pi 5 8GB Wins for Plex
Plex relies heavily on "Direct Play" (sending the raw file to the client) rather than transcoding, which the ARM architecture struggles with. Therefore, your bottleneck is almost always storage I/O and RAM for database caching, not raw CPU compute. Here is how the current board variants stack up for a dedicated Plex node.
| Board Variant | RAM | Storage Interface | Max 1080p Direct Streams | 4K Transcode Capability |
|---|---|---|---|---|
| Raspberry Pi 4 Model B | 8GB | USB 3.0 (5 Gbps shared bus) | 3-4 | Fails (Hardware decode unsupported) |
| Raspberry Pi 5 (4GB) | 4GB | PCIe 2.0 x1 (via M.2 HAT) | 4-5 | 1x 4K HEVC (Software, high CPU load) |
| Raspberry Pi 5 (8GB) | 8GB | PCIe 2.0 x1 (via M.2 HAT) | 6+ | 1-2x 4K HEVC (Software, requires active cooling) |
Source: Raspberry Pi Official Hardware Documentation
Bill of Materials & Pin Mapping
To make this a true embedded project rather than just a software install, we are adding an I2C OLED status monitor and a PWM-controlled cooling fan. The Pi 5 runs hot under sustained network I/O, and passive cooling often leads to thermal throttling at 80°C.
Parts List
- Compute: Raspberry Pi 5 (8GB)
- Case/Storage: Argon ONE V3 M.2 NVMe Case (includes integrated PWM fan and M.2 HAT)
- SSD: Samsung 990 EVO 2TB (PCIe Gen4 x4, backward compatible with Pi's Gen2 x1)
- Display: Adafruit PiOLED 128x32 (SSD1306 I2C chipset)
- Power: Official Raspberry Pi 27W USB-C PD Power Supply
GPIO & I2C Pin Mapping
This table maps the physical pins to the BCM GPIO numbers used in our Python script. We are using the standard I2C bus and repurposing GPIO 18 for hardware PWM fan control.
| Component | Function | Pi 5 Physical Pin | BCM GPIO |
|---|---|---|---|
| OLED VCC | Power (3.3V) | 1 | N/A |
| OLED GND | Ground | 6 | N/A |
| OLED SDA | I2C Data | 3 | GPIO 2 |
| OLED SCL | I2C Clock | 5 | GPIO 3 |
| PWM Fan Signal | Fan Speed Control | 12 | GPIO 18 |
GPIO Thermal & Status Monitor Code (Python)
The following Python script targets the Raspberry Pi 5 (8GB). It reads the CPU temperature, dynamically adjusts the PWM fan duty cycle, and pushes the current temp and Plex server status to the SSD1306 OLED. It includes robust error handling for I2C bus dropouts, which are common if your ribbon cables are too long or lack proper pull-up resistors.
Prerequisites: sudo apt install python3-pip i2c-tools and pip3 install luma.oled psutil gpiozero
import time
import os
import psutil
from gpiozero import PWMLED
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import ImageFont, ImageDraw, Image
import subprocess
# --- PIN & BUS DEFINITIONS ---
FAN_PWM_PIN = 18 # BCM GPIO 18 (Physical Pin 12)
I2C_PORT = 1 # Default I2C bus on Pi 5
OLED_ADDRESS = 0x3C # Standard SSD1306 address
# --- TARGET TEMPS (Celsius) ---
TEMP_MIN = 50.0
TEMP_MAX = 75.0
def get_cpu_temp():
try:
temp = os.popen("vcgencmd measure_temp").readline()
return float(temp.replace("temp=", "").replace("'C\n", ""))
except Exception:
return 0.0
def check_plex_status():
# Quick check if Plex Media Server process is running
try:
output = subprocess.check_output(["pgrep", "-f", "Plex Media Server"])
return "Online" if output else "Offline"
except subprocess.CalledProcessError:
return "Offline"
def main():
# Initialize PWM Fan (using gpiozero PWMLED as a proxy for PWM output)
fan = PWMLED(FAN_PWM_PIN, frequency=25000) # 25kHz is standard for PC fans
# Initialize I2C OLED with error handling
try:
serial = i2c(port=I2C_PORT, address=OLED_ADDRESS)
device = ssd1306(serial, width=128, height=32)
except OSError as e:
print(f"CRITICAL: I2C Initialization Failed. Check wiring. Error: {e}")
return
# Load default font
font = ImageFont.load_default()
try:
while True:
temp = get_cpu_temp()
plex_state = check_plex_status()
# Calculate PWM duty cycle (0.0 to 1.0)
if temp < TEMP_MIN:
duty = 0.0
elif temp > TEMP_MAX:
duty = 1.0
else:
duty = (temp - TEMP_MIN) / (TEMP_MAX - TEMP_MIN)
fan.value = duty
# Render OLED Frame
image = Image.new('1', (device.width, device.height))
draw = ImageDraw.Draw(image)
draw.text((0, 0), f"CPU: {temp:.1f}C", font=font, fill=255)
draw.text((0, 12), f"Fan: {duty*100:.0f}%", font=font, fill=255)
draw.text((0, 24), f"Plex: {plex_state}", font=font, fill=255)
device.display(image)
time.sleep(2)
except OSError as e:
print(f"I2C Bus Dropout during runtime: {e}. Resetting bus...")
# Fallback: run fan at 100% if screen fails to prevent thermal throttle
fan.value = 1.0
except KeyboardInterrupt:
print("Shutting down thermal monitor.")
fan.off()
device.cleanup()
if __name__ == "__main__":
main()
Debugging: When the Plex Transcoder or I2C Bus Fails
Embedded Linux environments are unforgiving when hardware and software collide. Here are the exact error strings you will encounter and how to fix them.
Error 1: OSError: [Errno 121] Remote I/O error
This occurs in the Python script when the Pi loses communication with the SSD1306 OLED over the I2C bus.
- Cause 1 (Most Likely): I2C is not enabled in the OS. Run
sudo raspi-config, navigate to Interface Options > I2C, and enable it. - Cause 2: Missing pull-up resistors. The Adafruit PiOLED has onboard 10k pull-ups, but if you are using a generic clone board, you may need to add 4.7kΩ resistors between SDA/SCL and 3.3V.
- Cause 3: Clock stretching issues on the Pi 5. Add
dtparam=i2c_arm_baudrate=50000to your/boot/firmware/config.txtto slow the bus down.
Error 2: Plex Transcoder exited due to signal 9 (SIGKILL)
You will see this in the Plex Media Server logs (~/Library/Application Support/Plex Media Server/Logs) when a video stops abruptly during playback.
- Cause 1 (Most Likely): The Linux OOM (Out of Memory) Killer terminated the transcoder process. The Pi 5 8GB can still run out of RAM if you allocate too much to the GPU or run other containers. Check
dmesg -T | grep -i oom. - Cause 2: The transcoder temporary directory is mapped to a RAM disk (
tmpfs) that filled up. Go to Plex Settings > Transcoder, and set the "Transcoder temporary directory" to your NVMe SSD path (e.g.,/mnt/nvme/plex_transcode) (Source: Plex Advanced Settings). - Cause 3: Thermal throttling caused the CPU to downclock below the threshold required to maintain the transcode buffer, leading to a timeout crash.
The First Three Things to Check When the Build Fails
If your Plex server is offline or the Pi is randomly rebooting, run this diagnostic triage:
- Check for Throttling/Brownouts: Run
vcgencmd get_throttled. If it returns anything other thanthrottled=0x0, your power supply is failing under load, or your USB-C cable has too high a voltage drop. Replace the cable and power brick. - Verify NVMe Link Speed: Run
lspci -vv | grep -i 'lnksta'. Ensure your M.2 SSD is negotiating at5GT/s(PCIe Gen 2). If it drops to 2.5GT/s, reseat the FPC ribbon cable connecting the HAT to the Pi 5. - Check Filesystem Mounts: Ensure your
/etc/fstabis mounting the NVMe drive with thenofailflag. If the SSD takes too long to initialize on boot, systemd will drop you into emergency mode withoutnofail, preventing Plex from starting.
Scaling the Build: Simplify or Extend
Not every maker needs an 8GB NVMe powerhouse, and some need far more than a Pi can offer. Here is how to adapt this architecture to your actual library size.
How to Simplify (The Budget Direct-Play Node)
If your media library is under 2TB and you only stream to devices that support Direct Play (like Apple TVs or Nvidia Shields), you do not need the Pi 5. Downgrade to a Raspberry Pi 4 (4GB). Remove the NVMe HAT and use a standard 2.5-inch SATA SSD in a USB 3.0 to SATA enclosure (like the Sabrent EC-SSGP). Remove the OLED and Python script entirely, relying on the Argon ONE case's built-in hardware fan curve. This cuts the BOM cost by roughly 40% while maintaining perfect 1080p/4K Direct Play capabilities.
How to Extend (The 10GbE & Multi-User Cluster)
If you are serving multiple remote users and hitting the Pi 5's network saturation limits (Gigabit Ethernet caps at ~110MB/s, which bottlenecks high-bitrate 4K REMUX files), the Pi's PCIe lane becomes your upgrade path. Extend with a Pineboards HatNET! 10G Adapter. This HAT plugs into the same PCIe connector, providing a 10 Gigabit Ethernet port. Note: You cannot run both an NVMe HAT and a 10GbE HAT simultaneously on a single Pi 5 due to the single PCIe lane. If you need both high-speed local storage and 10GbE networking, you have outgrown the Raspberry Pi architecture. At that point, migrate your Plex installation to an x86 Mini PC (like an Intel N100 or i3-12100) running TrueNAS Scale or Unraid, which offers native PCIe lanes for both NVMe and 10GbE NICs, plus Intel QuickSync for hardware transcoding.






