Why Your Raspberry Pi Can Login Directly But Not Via SSH
If you can plug a monitor and keyboard into your Pi (or tap into the serial console) and log in locally, but SSH connections from your main PC are rejected, the issue isn't a dead board or a corrupted OS. The Pi's operating system is actively blocking, dropping, or misrouting the TCP connection on port 22.
In modern Raspberry Pi OS (Bookworm/Debian 12), the default security posture has shifted. Headless setups no longer enable SSH out-of-the-box without an explicit ssh file in the boot partition or a raspi-config toggle. Furthermore, network topology changes, host key mismatches, and firewall rules frequently strand makers who rely purely on Wi-Fi.
The First Three Things to Check (Ranked Causes)
Before you start tearing apart your router settings, run through these three checks directly on the Pi's local terminal. These solve 95% of local-login-only SSH failures.
- Is the SSH daemon actually running and enabled? Run
sudo systemctl status ssh. If it saysinactive (dead)ormasked, the service is disabled. Fix it withsudo systemctl enable --now ssh. - Is the IP address correct and reachable? Run
ip aon the Pi. Compare theinetaddress to what you are pinging. If your router's DHCP lease expired and assigned the Pi a new IP, your PC's ARP cache or SSH config might be pointing to a dead endpoint. - Are the host keys mismatched? If you recently reflashed the Pi's SD card but kept the same IP address, your PC will block the connection to prevent Man-in-the-Middle (MitM) attacks. You must clear the old key from your PC's
known_hostsfile.
SSH Failure Symptom & Root Cause Matrix
When you attempt to connect from your host PC, the exact error string returned by the OpenSSH client tells you exactly where the packet is dying. Use this table to map the error to the fix.
| Exact Error String (Client Side) | Root Cause | Local Pi Fix / Action |
|---|---|---|
ssh: connect to host [IP] port 22: Connection refused |
sshd is not running, not installed, or listening on a non-standard port. |
Run sudo systemctl enable --now ssh. Check /etc/ssh/sshd_config for the Port directive. |
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! |
SD card was reflashed or Pi was replaced, but the IP address remained the same. ECDSA/RSA keys differ. | On your host PC, run ssh-keygen -R [IP] to purge the old fingerprint, then reconnect. |
Permission denied (publickey,password) |
Pi OS Bookworm disables password auth by default if created via Raspberry Pi Imager without setting a password. | Generate an SSH keypair on your PC and append the .pub key to ~/.ssh/authorized_keys on the Pi. |
ssh: connect to host [IP] port 22: Operation timed out |
Network isolation. Router 'AP Isolation' is on, or Pi is on a 2.4GHz IoT VLAN that blocks local routing. | Disable AP Isolation in your router (common on UniFi/TP-Link). Ensure PC and Pi share the same subnet. |
Hardware Fallback: UART Serial Console Pinout
When Wi-Fi is completely misconfigured and you don't have a micro-HDMI cable handy, the hardware serial console is your lifeline. This bypasses the network stack entirely and gives you a raw root shell via a USB-to-TTL adapter.
- Raspberry Pi 4 Model B (4GB) or Raspberry Pi 5 (8GB)
- USB-to-TTL Serial Cable (CP2102 or PL2303 chipset, not CH340 if you're on macOS due to driver kernel panics)
- Dupont jumper wires (Female-to-Female)
- Host PC with PuTTY (Windows) or
screen/minicom(Linux/macOS)
Wire the CP2102 adapter to the Pi's 40-pin header as follows. Never connect the 5V/VCC wire from the USB adapter to the Pi's 5V pin if the Pi is already powered by its USB-C supply; you will backfeed the 5V rail and potentially fry the PMIC.
| Pi 40-Pin Header | GPIO / Function | CP2102 Adapter Wire |
|---|---|---|
| Pin 6 | GND | GND (Black) |
| Pin 8 | GPIO 14 (TXD) | RX (White/Green) |
| Pin 10 | GPIO 15 (RXD) | TX (Orange/Yellow) |
Note: TX on the Pi goes to RX on the adapter, and RX on the Pi goes to TX on the adapter. Cross the data lines. Connect via your terminal emulator at 115200 baud, 8N1. You must also ensure enable_uart=1 is set in /boot/firmware/config.txt (Bookworm moved the boot partition to /boot/firmware/).
Automated SSH & GPIO Diagnostic Script
Rather than manually typing systemctl and ip a every time you boot a headless node, use this Python script. It checks the SSH daemon status, verifies port 22 is bound, and blinks a physical status LED on GPIO 17 so you know the Pi is ready for remote login just by looking at your workbench.
Hardware Requirement: Connect a 330Ω resistor and a standard 5mm LED from GPIO 17 (Pin 11) to GND (Pin 9). The script uses the gpiozero library, which is pre-installed on Raspberry Pi OS Bookworm.
#!/usr/bin/env python3
"""
SSH & Network Diagnostic Script
Target: Raspberry Pi 4 / Pi 5 (Raspberry Pi OS Bookworm 64-bit)
Hardware: Status LED on GPIO 17 (Pin 11)
"""
import socket
import subprocess
import sys
import time
from gpiozero import LED
# Pin definition for physical status indicator
STATUS_LED = LED(17)
def check_ssh_service():
"""Check if sshd is active via systemctl."""
try:
result = subprocess.run(
['systemctl', 'is-active', 'ssh'],
capture_output=True, text=True, check=False
)
return result.stdout.strip() == 'active'
except Exception as e:
print(f'Error checking systemctl: {e}')
return False
def check_port_binding(port=22):
"""Verify port 22 is actively listening on localhost."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# Set a 1-second timeout to prevent hanging
s.settimeout(1.0)
try:
s.connect(('127.0.0.1', port))
return True
except (socket.timeout, ConnectionRefusedError):
return False
def get_ip_address():
"""Fetch the primary non-loopback IPv4 address."""
try:
# Connect to a public DNS to find the active routing interface IP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return '127.0.0.1'
def main():
print('--- Pi SSH Diagnostic Tool ---')
ssh_active = check_ssh_service()
port_open = check_port_binding(22)
ip_addr = get_ip_address()
if ssh_active and port_open:
print(f'[PASS] SSH is running and listening on {ip_addr}:22')
# Blink LED to indicate healthy SSH state
STATUS_LED.blink(on_time=0.5, off_time=0.5, background=True)
else:
print('[FAIL] SSH is NOT ready for connections.')
if not ssh_active:
print(' -> Action: Run `sudo systemctl enable --now ssh`')
if not port_open:
print(' -> Action: Check /etc/ssh/sshd_config for port conflicts.')
# Solid LED indicates error state
STATUS_LED.on()
print(f'Current IP: {ip_addr}')
print('------------------------------')
if __name__ == '__main__':
try:
main()
# Keep script alive to maintain LED blinking
while True:
time.sleep(1)
except KeyboardInterrupt:
STATUS_LED.off()
print('\nDiagnostics halted.')
sys.exit(0)
Save this as ssh_diag.py and run it with python3 ssh_diag.py. If the LED blinks rhythmically, your Pi is accepting SSH connections. If it stays solid, the daemon is down.
Extending and Simplifying Your Remote Access
Once you have restored basic SSH access, you should harden the setup so you never have to ask "why can't I SSH into my Pi today?" again. Relying on local DHCP leases for headless nodes is a fragile practice that breaks the moment your router reboots.
1. Simplify with Tailscale (Zero-Config Mesh)
If your Pi is deployed in a different physical location, or you are tired of port-forwarding and dynamic DNS, install Tailscale. It creates a WireGuard-based mesh network. You simply run curl -fsSL https://tailscale.com/install.sh | sh and sudo tailscale up. You can then SSH into your Pi using its static 100.x.y.z Tailscale IP from anywhere in the world, bypassing local NAT, VLANs, and CGNAT entirely.
2. Extend with mDNS (Avahi)
If you just want to stop typing IP addresses on your local network, ensure the avahi-daemon is installed and running. This broadcasts the Pi's hostname via Multicast DNS. Instead of ssh pi@192.168.1.45, you can simply type ssh pi@raspberrypi.local. Bookworm includes this by default, but it frequently gets uninstalled by users trying to 'trim' the OS.
3. Harden the SSH Daemon
OpenSSH on Debian 12 defaults to reasonable security, but you should explicitly disable password authentication once your SSH keys are copied over. Edit /etc/ssh/sshd_config.d/99-custom.conf (using the .d drop-in directory is cleaner than editing the main file) and add:
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
Restart the daemon with sudo systemctl restart ssh. For a deeper dive into OpenSSH security parameters, refer to the Debian SSH Wiki and the official Raspberry Pi Configuration Documentation.
sshd_config edit locks you out of the network entirely, the hardware serial console is the only way to fix the typo without pulling the SD card and mounting it on another Linux machine.






