The most reliable way to add a physical Raspberry Pi power button to a Pi 4 Model B is to wire a normally-open momentary switch to GPIO 21 and run a Python gpiozero daemon that triggers a safe shutdown when held for three seconds. If you also need the button to wake the Pi from a halted state without unplugging it, you must use GPIO 3 (Pin 5) and the hardware I2C EEPROM wake circuit. Below, we cover the software-controlled hold-to-shutdown build, complete with production-ready code, and explain the hardware wake alternative.

Project Spec Sheet & Difficulty Rating

Parameter Specification
Target Board Raspberry Pi 4 Model B (4GB / 8GB)
OS Environment Raspberry Pi OS (Bookworm 64-bit, Python 3.11+)
Core Library gpiozero (with lgpio backend)
Hardware 12mm Momentary Push Button (Normally Open), 2x Female-to-Female Jumpers
Build Time 20 minutes (Hardware: 5m, Software: 15m)
Difficulty Rating Intermediate (Requires basic Linux systemd knowledge)

Hardware Wiring: Pin Mapping & Physical Setup

For this software-controlled build, we are using GPIO 21. We avoid GPIO 3 (Pin 5) for the Python script because Pin 5 is reserved for the hardware wake-from-halt circuit, and running a software pull-up on it can interfere with the I2C bus during boot.

Button Terminal Pi 4 GPIO Pin Physical Pin # Function
Terminal 1 GPIO 21 Pin 40 Signal (Internal Pull-Up enabled in code)
Terminal 2 GND Pin 39 Ground Reference
Bench Tip: You do not need an external 10kΩ pull-up resistor for this build. The gpiozero library enables the Pi's internal 50kΩ pull-up resistor by default when you initialize the Button object. If your button wires are longer than 6 inches and you experience phantom triggers from EMI, add a 10kΩ physical resistor between GPIO 21 and 3.3V (Pin 1).

Wiring Steps

  1. Disconnect the Pi from mains power. Never hot-swap GPIO connections.
  2. Solder or crimp two female DuPont connectors to the terminals of your 12mm momentary push button.
  3. Connect one wire to Physical Pin 40 (GPIO 21) on the Pi's 40-pin header.
  4. Connect the second wire to Physical Pin 39 (GND), located immediately adjacent to Pin 40.
  5. Mount the button to your enclosure. If using a panel-mount button, ensure the metal nut is tight to prevent rotation when pressing.

Python Shutdown Daemon: Complete Code

This script uses gpiozero to monitor the button. Instead of shutting down on a quick tap (which leads to accidental shutdowns when dusting the desk), it requires a 3-second hold. It also drives an optional LED on GPIO 20 to provide visual feedback: solid while holding, blinking during the shutdown sequence.

import os
import sys
import time
import logging
from gpiozero import Button, LED
from signal import pause

# --- Pin Definitions ---
SHUTDOWN_BTN_PIN = 21
STATUS_LED_PIN = 20
HOLD_TIME_SEC = 3.0

# Setup logging to journalctl
logging.basicConfig(
    level=logging.INFO, 
    format='%(asctime)s - %(levelname)s - %(message)s'
)

try:
    # pull_up=True uses internal resistor; hold_time sets the 3s threshold
    btn = Button(SHUTDOWN_BTN_PIN, hold_time=HOLD_TIME_SEC, pull_up=True, bounce_time=0.05)
    led = LED(STATUS_LED_PIN)
except Exception as e:
    logging.critical(f"Failed to initialize GPIO hardware: {e}")
    sys.exit(1)

def shutdown_sequence():
    logging.info(f"Button held for {HOLD_TIME_SEC}s. Initiating safe shutdown...")
    # Blink LED to indicate shutdown in progress
    led.blink(on_time=0.2, off_time=0.2, background=True)
    time.sleep(0.5) # Allow blink thread to start
    # Execute system shutdown
    os.system("sudo shutdown -h now")

def button_pressed():
    led.on() # Solid LED while user is holding the button

def button_released():
    led.off() # Turn off if released before hold_time threshold

# --- Event Bindings ---
btn.when_held = shutdown_sequence
btn.when_pressed = button_pressed
btn.when_released = button_released

logging.info("Power button daemon started. Waiting for 3s hold event...")

try:
    pause() # Keeps the script running efficiently without a while-loop
except KeyboardInterrupt:
    logging.info("Daemon interrupted by user (Ctrl+C).")
except Exception as e:
    logging.error(f"Unexpected error in main loop: {e}")
finally:
    led.off()
    btn.close()
    logging.info("GPIO resources released.")

Note: To run this automatically on boot, save it as /home/pi/power_button.py and create a systemd service file (/etc/systemd/system/power-btn.service) configured to run as the pi user with WantedBy=multi-user.target.

Debugging: First Three Things to Check When It Fails

When deploying GPIO scripts on Raspberry Pi OS Bookworm, permission and backend errors are the most common points of failure. If your daemon crashes or ignores the button, check these three ranked causes.

1. The 'gpiomem' Permission Error

Exact Error String: PermissionError: [Errno 13] Permission denied: '/dev/gpiomem'

Cause: Your user account lacks the group permissions to access the GPIO memory map directly, or you are running the script inside a Python virtual environment that hasn't inherited system groups.

Fix: Ensure your user is in the gpio group by running sudo usermod -aG gpio $USER, then log out and log back in. If running via systemd, ensure the service file includes SupplementaryGroups=gpio.

2. The Pin Factory Backend Failure

Exact Error String: gpiozero.exc.BadPinFactory: Unable to load any default pin factory!

Cause: In Bookworm, gpiozero defaults to the lgpio backend. If lgpio is missing from your environment, it falls back to RPi.GPIO, which is deprecated and often uninstalled by default on newer 64-bit images.

Fix: Install the modern backend explicitly: sudo apt install python3-lgpio or pip install lgpio inside your venv.

3. The Ghost Pin Conflict

Exact Error String: gpiozero.exc.GPIOPinInUse: pin 21 is already in use

Cause: A previous instance of your script crashed without reaching the finally block, leaving the pin locked in the kernel's GPIO character device.

Fix: Kill the orphaned process with pkill -f power_button.py. If the pin remains locked, a full reboot (sudo reboot) is required to reset the lgpio daemon state.

Extending and Simplifying the Build

The Python daemon above gives you granular control over LED feedback and hold-times, but there are two ways to alter this build depending on your hardware generation and complexity tolerance.

Simplifying: The Device Tree Overlay (Pi 4)

If you don't need an LED indicator and just want basic shutdown/wake without writing Python, use GPIO 3 (Pin 5). Wire your button between Pin 5 and Pin 6 (GND). Then, add this line to /boot/firmware/config.txt:

dtoverlay=gpio-shutdown,gpio_pin=3,active_low=1,gpio_pull=up

This hands control to the kernel. Pressing the button sends an ACPI-style shutdown signal. Because Pin 5 is tied to the hardware I2C EEPROM, grounding it while the Pi is halted will automatically trigger a cold boot. No Python required. See the official Raspberry Pi config.txt documentation for overlay parameters.

Extending: The Native Pi 5 Power Header

If you upgrade to a Raspberry Pi 5, the board includes a dedicated J2 power button header (a 2-pin JST-SH connector near the USB-C power input). You can buy the official Raspberry Pi 5 power button cable for roughly $2. It plugs directly into J2, handles safe shutdown, wake-from-halt, and hard-reset (hold for 10 seconds) entirely in hardware via the RP1 southbridge chip. No GPIO pins are consumed, and no software configuration is needed.

Frequently Asked Questions

How do I add a power button to Raspberry Pi 5 without using GPIO?

The Raspberry Pi 5 features a dedicated 2-pin JST-SH connector labeled J2 specifically for a power button. Purchase the official Pi 5 power button cable or a compatible third-party JST-SH 1.0mm pitch cable, plug it into J2, and mount the button. The RP1 chip natively handles soft shutdown, wake, and hard reset without any software setup or GPIO configuration.

Can a Raspberry Pi power button wake it from sleep or a halted state?

On a Pi 4, a software Python script cannot wake the Pi because the OS is halted and the script isn't running. To achieve wake-from-halt on a Pi 4, you must wire the button to GPIO 3 (Pin 5) and Ground. The Pi's hardware power management circuit monitors Pin 5 and will trigger a boot sequence when pulled low, even if the OS is completely shut down.

Why does my Raspberry Pi turn on immediately when plugged in?

This is the default hardware behavior of the Pi's power management IC (PMIC). When 5V is applied to the USB-C port, the PMIC automatically asserts the power-on rail. To change this on a Pi 4 or Pi 5, you must modify the EEPROM configuration or use the POWER_OFF_ON_HALT=1 setting in the bootloader config, which keeps the board in a deep sleep state after shutdown until the physical button is pressed.

Is it safe to just unplug the Raspberry Pi without a shutdown button?

No. Yanking the USB-C cable while the OS is writing to the SD card or eMMC drive frequently causes file system corruption, specifically in the /var/log and /boot partitions. A physical power button that triggers shutdown -h now ensures the kernel unmounts file systems cleanly, flushes the write cache, and parks the storage controller before cutting power, vastly extending the lifespan of your microSD card.