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
Why GPIO 3? GPIO 3 (Pin 5) is hardwired to the Pi's power management IC (PMIC). When the Pi is halted, pulling this pin low via a physical button signals the PMIC to boot the board. No other GPIO pin can wake a halted Pi 4 or 5 without an external microcontroller HAT.

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

Safety First: Always de-energize the Raspberry Pi and disconnect the USB-C power cable before soldering or connecting wires to the GPIO header. Shorting 3.3V or 5V to a GPIO pin will instantly destroy the RP1 I/O bank or the BCM2711 SoC.
  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. 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-config for a sensor or display, the i2c-dev kernel module claims the pin at boot.
    Fix: Run sudo raspi-config -> Interface Options -> I2C -> Disable. Reboot.
  2. 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: Run sudo killall python3 or sudo systemctl stop safeswitch before testing manually.
  3. Cause 3: Pin Factory Conflict (Pi 5 specific). On the Pi 5, gpiozero might default to the wrong pin factory if lgpio is 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:

  1. Verify Pin State via CLI: Open a terminal and run pinctrl get 3 (Bookworm) or raspi-gpio get 3 (Bullseye). It should read hi (high) when the button is released, and drop to lo (low) when pressed. If it stays hi, your switch wiring is open.
  2. 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.1 in the script to test for immediate response.
  3. Inspect the Systemd Logs: Run journalctl -u safeswitch -f. If you see permission denied errors on the shutdown command, your service file is missing User=root or you need to add the pi user 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.