The Raspberry Pi lacks a native hardware power switch. Yanking the USB-C cable to turn it off is a fast track to SD card corruption and broken file systems. While you can buy dedicated power management HATs, the most reliable, zero-cost method is wiring a momentary pushbutton to GPIO 3 (Physical Pin 5). This triggers a safe software shutdown via a Python script, while uniquely retaining the hardware ability to wake the Pi from a fully halted state.
This guide targets the Raspberry Pi 4 Model B and Raspberry Pi 5 running Raspberry Pi OS (Bookworm). We will use the modern gpiozero library, as the legacy RPi.GPIO library is deprecated and frequently fails on the Pi 5's new RP1 silicon.
Power Management Methods Compared
Before soldering, it is worth understanding where the DIY GPIO method sits compared to commercial alternatives. The table below ranks the most common ways to add a power switch to a Raspberry Pi based on standby current, cost, and wake capabilities.
| Method | Standby Draw (Halted) | Approx. Cost | Wake from Halt? | SD Corruption Risk |
|---|---|---|---|---|
| Direct PSU Pull | 0 mA (Disconnected) | $0 | No | High |
| DIY GPIO 3 Switch (This Guide) | ~1.2 mA (Soft Off) | $0.50 | Yes (Hardware) | None (if coded) |
| Pi Supply Switch V3 | 0 mA (Hard Cut) | $35 | Yes (On-board MCU) | None |
| Geekworm X735 HAT | ~2.5 mA | $28 | Yes | None |
| Smart Plug (WiFi) | 0 mA (Disconnected) | $12 | No | High |
Parts List and Pin Mapping
Keep the bill of materials minimal. You only need basic passive components to handle switch bounce and protect the pin.
Required Components
- Microcontroller: Raspberry Pi 4 Model B or Raspberry Pi 5 (Bookworm OS installed)
- Switch: Momentary pushbutton (e.g., C&K PTS645 series, 12x12mm, SPST-NO)
- Capacitor: 100nF (0.1µF) ceramic capacitor (for hardware debouncing)
- Resistor: 10kΩ (optional, Pi 3/4/5 has internal pull-ups on GPIO 3, but external adds noise immunity in industrial environments)
- Wire: 26 AWG silicone stranded wire
Pin Mapping Table
| Physical Pin | BCM GPIO | Function | Connection |
|---|---|---|---|
| Pin 5 | GPIO 3 (SCL) | Shutdown / Wake | Switch Terminal A |
| Pin 6 | GND | Ground Reference | Switch Terminal B |
| N/A | Across Switch | Hardware Debounce | 100nF Capacitor Legs |
Wiring the Safe Shutdown Circuit
- Prep the Switch: Bend the terminals of your momentary pushbutton to match the 0.1-inch (2.54mm) pitch of the Pi's GPIO header, or solder 26 AWG pigtails to the switch terminals.
- Add Hardware Debounce: Solder the 100nF ceramic capacitor directly across the two switch terminals. This creates a low-pass RC filter (using the Pi's internal pull-up resistor) that absorbs the microsecond voltage spikes caused by mechanical contact bounce.
- Connect to GPIO: Connect one side of the switch to Physical Pin 5 (GPIO 3) and the other side to Physical Pin 6 (GND). Polarity does not matter for the switch or the ceramic capacitor.
- Verify with Multimeter: Set your multimeter to continuity mode. Place probes on Pin 5 and Pin 6. It should read open (OL). Press the button; it should read near 0.0 ohms. Release; it should return to OL.
The Python Shutdown Script (Bookworm Compatible)
On Raspberry Pi OS Bookworm, RPi.GPIO is largely broken on the Pi 5 and deprecated on the Pi 4. We use gpiozero, which abstracts the underlying lgpio or sysfs pin factories automatically.
Create a file named safe_shutdown.py in your home directory:
#!/usr/bin/env python3
"""
Safe Power Switch Script for Raspberry Pi 4 & 5
Target OS: Raspberry Pi OS (Bookworm)
Library: gpiozero (Pre-installed on Bookworm)
"""
from gpiozero import Button
from signal import pause
import os
import sys
import logging
# Configure logging for systemd journal integration
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
# Pin definition: BCM GPIO 3 (Physical Pin 5)
SHUTDOWN_PIN = 3
# Require a 2-second hold to prevent accidental shutdowns from bumps
HOLD_TIME = 2.0
def safe_shutdown():
logging.info('Shutdown button held for 2 seconds. Initiating safe shutdown...')
# Execute the system shutdown command
os.system('sudo shutdown -h now')
def main():
try:
# Initialize button.
# pull_up=True: Uses the internal 50k pull-up resistor.
# bounce_time=0.2: Software debounce backup (200ms).
shutdown_btn = Button(
SHUTDOWN_PIN,
hold_time=HOLD_TIME,
bounce_time=0.2,
pull_up=True
)
# Bind the hold event to our shutdown function
shutdown_btn.when_held = safe_shutdown
logging.info(f'Power switch listener active on GPIO {SHUTDOWN_PIN}.')
logging.info('Press and hold the button for 2 seconds to shut down.')
# Keep the script running efficiently
pause()
except Exception as e:
logging.error(f'Failed to initialize GPIO {SHUTDOWN_PIN}: {e}')
sys.exit(1)
if __name__ == '__main__':
main()
Running as a Background Service
To make this run on boot, create a systemd service. Create /etc/systemd/system/safeswitch.service:
[Unit]
Description=Safe GPIO Power Switch
After=multi-user.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/safe_shutdown.py
Restart=always
User=root
[Install]
WantedBy=multi-user.target
Enable it with: sudo systemctl enable --now safeswitch.service.
Debugging: 'RuntimeError: Failed to add edge detection'
If your script crashes immediately upon execution, you will likely see this exact traceback:
Traceback (most recent call last):
File "/home/pi/safe_shutdown.py", line 34, in main
shutdown_btn = Button(SHUTDOWN_PIN, hold_time=HOLD_TIME...)
File "/usr/lib/python3/dist-packages/gpiozero/devices.py", line 124, in __init__
self._pin = pin_factory.pin(pin)
RuntimeError: Failed to add edge detection
This error means the kernel refuses to let your Python script attach an interrupt to GPIO 3. Here are the ranked causes and fixes:
- Cause 1: I2C Interface is Enabled (Most Likely). GPIO 3 is hardwired as the I2C1 SCL (Serial Clock) line. If you enabled I2C in
raspi-configfor a sensor or display, thei2c-devkernel module claims the pin at boot.
Fix: Runsudo raspi-config-> Interface Options -> I2C -> Disable. Reboot. - Cause 2: Ghost Python Processes. You ran the script previously, it crashed, but the Python process is still holding the GPIO file lock in the background.
Fix: Runsudo killall python3orsudo systemctl stop safeswitchbefore testing manually. - Cause 3: Pin Factory Conflict (Pi 5 specific). On the Pi 5,
gpiozeromight default to the wrong pin factory iflgpiois missing.
Fix: Ensure the modern GPIO backend is installed:sudo apt install python3-lgpio.
The First Three Things to Check When It Fails
If the script runs but the Pi doesn't shut down when you press the button, check these three items in order:
- Verify Pin State via CLI: Open a terminal and run
pinctrl get 3(Bookworm) orraspi-gpio get 3(Bullseye). It should readhi(high) when the button is released, and drop tolo(low) when pressed. If it stayshi, your switch wiring is open. - Check the Hold Time: The script requires a continuous 2.0-second press. If you are just tapping the button, nothing will happen. Temporarily change
HOLD_TIME = 0.1in the script to test for immediate response. - Inspect the Systemd Logs: Run
journalctl -u safeswitch -f. If you see permission denied errors on theshutdowncommand, your service file is missingUser=rootor you need to add thepiuser to the sudoers file without a password prompt for the shutdown command.
Extending and Simplifying the Build
How to Extend: Adding a Status LED
Want visual feedback? Wire a 3mm LED with a 330Ω current-limiting resistor to GPIO 14 (Physical Pin 8 / TXD). The Pi's firmware automatically flashes the TXD LED during boot and keeps it lit while the OS is running. You can add this to your Python script to turn the LED off right before the shutdown command executes, providing a clean 'power off' indicator.
How to Simplify: Use a Power Management HAT
If you do not want to solder, or if you need the Pi to physically cut power to peripherals (like a 5V fan or USB devices) when halted, abandon the DIY GPIO method. Purchase a dedicated HAT like the Geekworm X735 or the Pi Supply Switch. These boards feature an onboard ATtiny microcontroller that handles the safe shutdown handshake via I2C/UART and physically disconnects the 5V rail via a MOSFET when the Pi halts, dropping the standby current to absolute zero.
For further reading on Raspberry Pi hardware interfaces and GPIO allocation, refer to the official Raspberry Pinout and Configuration Documentation, and the gpiozero API reference for advanced button debouncing parameters.






