Figuring out how to remote access Raspberry Pi nodes deployed in the field or tucked inside a remote server rack is a rite of passage for embedded engineers. If you rely solely on local LAN IP addresses or, worse, port-forwarding SSH through your router, your deployment will eventually fail or get compromised. The direct answer for secure, zero-config internet access is Tailscale paired with headless SSH. But software is only half the battle; when the Pi's network peripheral freezes, software access dies. You need a hardware fallback.

This guide walks through building a robust remote access gateway using a Raspberry Pi 5, secured via Tailscale, and fortified with a GPIO-driven hardware watchdog to automatically reset frozen 4G LTE modems or network switches.

The Remote Access Decision Matrix (Stop Guessing)

Before writing a single line of code, you must choose the right transport layer. Here is the decision path for remote Pi deployments. Follow the logic down to the default recommendation.

Scenario Method Security Verdict
Strict Local LAN (No Internet) mDNS / Bonjour (ssh pi@raspberrypi.local) Medium (LAN bound) Use for bench testing only.
Internet Access + GUI Needed Tailscale + RealVNC High (WireGuard mesh) Use for kiosk debugging.
Internet Access + Port Forwarding SSH via Router Port 22 Critical Risk NEVER DO THIS. Botnets will brute-force it in hours.
Internet Access + Headless + Secure Tailscale + SSH Maximum (End-to-end encrypted) DEFAULT PICK: Use this for 95% of embedded deployments.
Decision Terminated: We are building the Tailscale + SSH stack. It requires no port forwarding, assigns a static 100.x.y.z IP to your Pi, and works seamlessly through symmetric NATs and strict corporate firewalls.

Hardware BOM and GPIO Watchdog Pin Mapping

Software remote access fails when the Pi's upstream network hardware (like a USB 4G LTE modem or an unmanaged PoE switch) locks up. We will wire a 3.3V relay to the Pi's GPIO to physically cut and restore power to the peripheral.

Parts List

  • Board: Raspberry Pi 5 (8GB variant) - Target board for this guide.
  • OS: Raspberry Pi OS Bookworm (64-bit, Lite/Headless).
  • Relay Module: MakerHawk 3.3V Relay Module (or any opto-isolated relay explicitly rated for 3.3V logic trigger). Do not use standard 5V Arduino relays; the 5V backfeed will fry the Pi 5's BCM2712 GPIO bank.
  • Storage: 32GB Class A2 MicroSD (SanDisk Extreme) or NVMe via PCIe HAT.
  • Power: Official 27W USB-C PD Power Supply.

Pin Mapping Table

The Pi 5 uses the standard 40-pin header, but its GPIO pins are strictly 3.3V. Wire the relay as follows:

Pi 5 Pin (Physical) BCM GPIO Function Relay Module Pin
Pin 2 5V Power VCC (Power) VCC
Pin 6 GND Ground GND
Pin 11 GPIO 17 Logic Trigger IN (Signal)

Step-by-Step: Configuring Headless SSH and Tailscale

Assume the Pi is freshly flashed with Bookworm Lite and connected to a temporary local network via Ethernet or Wi-Fi.

  1. Enable SSH Headlessly: If you haven't booted yet, place an empty file named ssh (no extension) in the root of the SD card's boot partition. If already booted, run sudo raspi-config -> Interface Options -> SSH -> Enable.
  2. Update the OS: Run sudo apt update && sudo apt upgrade -y. Bookworm's kernel updates frequently fix PCIe and USB power-state bugs critical for remote nodes.
  3. Install Tailscale: Execute the official install script:
    curl -fsSL https://tailscale.com/install.sh | sh
  4. Authenticate and Set Static Keys: Run sudo tailscale up --ssh. The --ssh flag enables Tailscale's built-in SSH server, bypassing the need to manage local authorized_keys files entirely. Authentication is handled by your Tailscale identity provider.
  5. Verify the Tunnel: Run tailscale ip -4. Note the 100.x.y.z address. From your remote laptop (which must also be logged into the same Tailscale network), test the connection: ssh username@100.x.y.z.
Safety & Code Caveat: If this Pi controls mains-voltage equipment (like a smart PDU or HVAC contactor), ensure all AC wiring is done inside a grounded metal enclosure by a qualified electrician. The 3.3V GPIO relay should only switch low-voltage DC control signals or isolated IoT smart plugs, never raw 120V/240V AC directly.

The Code: Python Peripheral Watchdog with Error Handling

This script targets the Raspberry Pi 5 on Bookworm. It uses gpiozero (the default, maintained GPIO library for Pi 5, replacing the deprecated RPi.GPIO). It pings a reliable external IP; if the ping fails, it assumes the external 4G modem has frozen and triggers the relay to hard-reset it.

import subprocess
import time
import logging
import sys
from gpiozero import OutputDevice
from gpiozero.exc import GPIODeviceError

# --- Configuration ---
RELAY_PIN = 17          # BCM GPIO 17 (Physical Pin 11)
WATCHDOG_TARGET = '8.8.8.8'
PING_TIMEOUT = 5        # Seconds
FAIL_THRESHOLD = 3      # Consecutive failures before triggering reset
RESET_DURATION = 10     # Seconds to hold relay open (power off)

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

def check_network(host):
    """Returns True if host is reachable, False otherwise."""
    try:
        # -c 1 (count), -W 5 (timeout)
        result = subprocess.run(
            ['ping', '-c', '1', '-W', str(PING_TIMEOUT), host],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL
        )
        return result.returncode == 0
    except Exception as e:
        logging.error(f'Ping subprocess error: {e}')
        return False

def main():
    try:
        # active_high=True means pin goes HIGH (3.3V) to trigger relay
        relay = OutputDevice(RELAY_PIN, active_high=True, initial_value=False)
        logging.info(f'Watchdog initialized on GPIO {RELAY_PIN}.')
    except GPIODeviceError as e:
        logging.critical(f'GPIO initialization failed: {e}. Check pin mapping and permissions.')
        sys.exit(1)

    fail_count = 0

    while True:
        if check_network(WATCHDOG_TARGET):
            if fail_count > 0:
                logging.info('Network recovered.')
            fail_count = 0
        else:
            fail_count += 1
            logging.warning(f'Ping failed ({fail_count}/{FAIL_THRESHOLD}).')
            
            if fail_count >= FAIL_THRESHOLD:
                logging.critical('Threshold reached. Hard-resetting peripheral via relay.')
                try:
                    relay.on()  # Cut power to peripheral
                    time.sleep(RESET_DURATION)
                    relay.off() # Restore power
                    logging.info('Reset complete. Waiting 60s for peripheral boot.')
                    time.sleep(60) # Wait for modem to reconnect to tower
                except Exception as e:
                    logging.error(f'Relay toggle failed: {e}')
                fail_count = 0 # Reset counter after action

        time.sleep(30) # Check every 30 seconds

if __name__ == '__main__':
    main()

Save this as watchdog.py and run it via a systemd service so it survives reboots and SSH disconnects.

Debugging: When 'Connection Refused' Strikes

Remote access debugging requires a systematic approach. When you are 50 miles away and the terminal hangs, do not guess. Follow this ranked cause list.

Exact Error: ssh: connect to host 100.105.x.x port 22: Connection timed out

First three things to check (if you have physical access or a secondary out-of-band console):

  1. Tailscale Service State: Run systemctl status tailscaled. If it's dead, the Pi likely experienced a brownout and the service failed to auto-start. Fix: sudo systemctl enable --now tailscaled.
  2. Key Expiry: Tailscale keys expire every 180 days by default. If the node dropped off the admin console, log into the Tailscale web UI and disable key expiry for this specific embedded node.
  3. Local Firewall (UFW/iptables): If you enabled ufw, it might be blocking the Tailscale virtual interface (tailscale0). Fix: sudo ufw allow in on tailscale0.

Exact Error: gpiozero.exc.GPIODeviceError: No module named 'lgpio'

Cause: You are running Bookworm, which abandoned the legacy RPi.GPIO C-library in favor of lgpio via the gpiozero wrapper. You likely installed an older tutorial's dependencies.
Fix: Run sudo apt install python3-gpiozero python3-lgpio. Do not use pip for system-level GPIO libraries on Bookworm due to PEP 668 externally-managed-environment restrictions.

Exact Error: RuntimeError: Cannot determine SOC peripheral base address

Cause: You are trying to use the deprecated RPi.GPIO library on a Raspberry Pi 5. The BCM2712 chip's memory map is entirely different from the BCM2711 (Pi 4).
Fix: Rewrite your code to use gpiozero as shown in the script above. It abstracts the hardware layer and works natively on the Pi 5.

Extending and Simplifying the Build

Once your baseline Tailscale SSH and watchdog script are stable, you can scale the deployment up or down based on your exact field requirements.

How to Extend (Add Telemetry)

If you need to monitor the Pi's thermal throttling and CPU load remotely without logging in, install Prometheus Node Exporter. It exposes a metrics endpoint on port 9100. Because Tailscale creates a private mesh network, you can safely scrape this port from your home Grafana dashboard without exposing port 9100 to the public internet.

How to Simplify (Drop the Custom Code)

If you only need to reboot the Pi itself on a schedule (rather than resetting an external peripheral based on network health), ditch the relay and Python script entirely. Buy a Witty Pi 4 Mini (approx. $25). It sits on the GPIO header, has its own real-time clock (RTC) and firmware, and can hard-cut power to the Pi 5 on a cron schedule or via a physical pushbutton, completely independent of the Pi's OS state.

By locking down your transport layer with Tailscale and backing it up with hardware-level peripheral resets, your remote Raspberry Pi deployments will survive the inevitable network hiccups that plague field electronics.