Use a Raspberry Pi as a Headless Server
Turn a Raspberry Pi into a reliable headless server: configure SSH-only access, set a static IP, mount USB storage persistently, enable the hardware watchdog, and tune swap.
Before you start
- ▸Raspberry Pi 3B+ or newer with a quality microSD card (16 GB minimum, A2-rated recommended)
- ▸Raspberry Pi Imager installed on your workstation
- ▸The Pi connected to your router via ethernet
- ▸A USB drive for external storage (USB 3.0 drive recommended for Pi 4/5)
A Raspberry Pi makes a capable low-power home server — file sharing, a local DNS resolver, a small web app host, a backup target. Running it headless (no monitor, no keyboard) keeps the setup clean. This guide covers everything from first boot to a stable, self-healing server: SSH access, a static IP, external USB storage, the hardware watchdog, and swap tuning to protect your SD card.
What You Need Before You Start
Flash a 64-bit server image — Raspberry Pi OS Lite (64-bit) is the right choice. Avoid the full desktop image; it wastes RAM and adds unnecessary services. Use Raspberry Pi Imager to write the image. In the imager's advanced options (the gear icon), set a hostname, enable SSH, create a user with a password, and configure your Wi-Fi SSID if needed. Do all of this before you write the card — it creates the /boot/firmware/firstrun.sh mechanism that applies your settings on first boot.
First Boot and SSH Access
Insert the card, connect an ethernet cable, and power on. Wait about 60 seconds for first-boot provisioning to finish. Find the Pi's current IP from your router's DHCP lease table, then connect:
ssh [email protected]
mDNS (.local) works on most networks. If it doesn't resolve, use the IP address directly. Once logged in, update the system immediately:
sudo apt update && sudo apt full-upgrade -y
sudo reboot
After the reboot, SSH back in. You now have a current, minimal system.
Set a Static IP Address
Raspberry Pi OS Lite uses NetworkManager on recent releases (Bookworm and later). Older Bullseye-based images use dhcpcd. Check which is running:
systemctl is-active NetworkManager
systemctl is-active dhcpcd
NetworkManager (Bookworm / current)
Find your connection name first:
nmcli connection show
The ethernet connection is typically named Wired connection 1 or the interface name like eth0. Set a static address:
sudo nmcli connection modify "Wired connection 1" \
ipv4.method manual \
ipv4.addresses 192.168.1.50/24 \
ipv4.gateway 192.168.1.1 \
ipv4.dns "192.168.1.1 8.8.8.8"
sudo nmcli connection up "Wired connection 1"
dhcpcd (Bullseye / older)
Append to /etc/dhcpcd.conf:
sudo tee -a /etc/dhcpcd.conf <<'EOF'
interface eth0
static ip_address=192.168.1.50/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1 8.8.8.8
EOF
sudo systemctl restart dhcpcd
From now on, SSH to the static address. Verify connectivity:
ping -c 3 8.8.8.8
Mount USB Storage
Plugging in a USB drive and using it by device path (/dev/sda1) is fragile — the path can change on reboot. Use the filesystem UUID instead.
Format and Label the Drive
If the drive is new or you want a clean ext4 filesystem:
sudo fdisk -l # identify the device, e.g. /dev/sda
sudo mkfs.ext4 -L pidata /dev/sda1
If it's already formatted (NTFS from Windows, exFAT, etc.) and you want to keep the data, skip the format step. Install the required driver if using exFAT:
sudo apt install exfatprogs -y
Create a Mount Point and Get the UUID
sudo mkdir -p /mnt/pidata
blkid /dev/sda1
The output includes a line like UUID="a1b2c3d4-...". Copy that value.
Add to /etc/fstab
sudo tee -a /etc/fstab <<'EOF'
UUID=a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/pidata ext4 defaults,noatime,nofail 0 2
EOF
The nofail option is important — it lets the Pi boot normally if the USB drive is absent. The noatime flag reduces write traffic.
Test without rebooting:
sudo mount -a
df -h /mnt/pidata
Set ownership so your user can write to it:
sudo chown your-username:your-username /mnt/pidata
Enable the Hardware Watchdog
The Pi has a hardware watchdog timer baked into the BCM chip. If the kernel stops petting it, the board resets automatically — useful for an unattended server that might hang.
Enable the Watchdog Device
On Raspberry Pi OS the device is usually available but not activated in /boot/firmware/config.txt. Verify it exists:
ls /dev/watchdog*
If it's missing, add the overlay (rarely needed on current images):
echo 'dtparam=watchdog=on' | sudo tee -a /boot/firmware/config.txt
sudo reboot
Configure systemd to Use the Watchdog
systemd has built-in watchdog support via /etc/systemd/system.conf. Edit it:
sudo nano /etc/systemd/system.conf
Find and uncomment (or add) these two lines:
RuntimeWatchdogSec=15
RebootWatchdogSec=2min
RuntimeWatchdogSec=15 tells systemd to pet the watchdog every 15 seconds. If systemd itself dies or the kernel hangs, the hardware timer fires after ~15 seconds and reboots the board. Apply it:
sudo systemctl daemon-reexec
Confirm it's working:
sudo cat /proc/sys/kernel/watchdog_thresh
journalctl -b | grep -i watchdog
Manage Swap to Protect the SD Card
SD cards have limited write endurance. The default Raspberry Pi OS uses a 100 MB swapfile (via dphys-swapfile) which isn't terrible, but you can do better by disabling it entirely if you have enough RAM, or by moving swap to the USB drive and setting a low swappiness to reduce how eagerly the kernel uses it.
Option A: Disable Swap (Pi 4/5 with 4 GB+ RAM, light workloads)
sudo dphys-swapfile swapoff
sudo systemctl disable dphys-swapfile
sudo apt purge dphys-swapfile -y
Option B: Move Swap to USB and Tune Swappiness
Create a swapfile on the USB drive:
sudo dd if=/dev/zero of=/mnt/pidata/swapfile bs=1M count=1024 status=progress
sudo chmod 600 /mnt/pidata/swapfile
sudo mkswap /mnt/pidata/swapfile
sudo swapon /mnt/pidata/swapfile
Disable the SD-based swap and add the USB swapfile to fstab:
sudo dphys-swapfile swapoff
sudo systemctl disable dphys-swapfile
sudo tee -a /etc/fstab <<'EOF'
/mnt/pidata/swapfile none swap sw,nofail 0 0
EOF
Lower swappiness so the kernel avoids swap unless RAM pressure is significant:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl -p /etc/sysctl.d/99-swappiness.conf
Verify swap is active on the USB drive:
swapon --show
Verification Checklist
- SSH connects using the static IP after a full reboot:
ssh [email protected] - USB storage mounts automatically:
df -h /mnt/pidatashows the drive - Watchdog is active:
journalctl -b | grep -i watchdogshows no errors - Swap is on the USB drive (or absent):
swapon --showconfirms the location - System time is correct (important for logs and TLS):
timedatectl status
Troubleshooting
Can't reach the Pi after setting a static IP
Boot with a monitor or connect via serial console (raspi-config can enable UART). On NetworkManager systems, the old DHCP address may still be cached on your workstation — flush it with ip neigh flush all and try again.
USB drive not mounting on boot
Check sudo journalctl -b | grep fstab and sudo systemctl status local-fs.target. A common cause is the USB controller initialising after systemd's mount attempt. Add x-systemd.mount-timeout=30 to the fstab options as a workaround, or make the unit require usb-storage.target.
Watchdog not rebooting on a hang
Confirm /dev/watchdog exists and that RuntimeWatchdogSec is uncommented (no leading #) in system.conf. Some third-party kernels ship with CONFIG_BCM2835_WDT disabled — stick to the official Raspberry Pi OS kernel.
Frequently asked questions
- Can I use Wi-Fi instead of ethernet for the static IP?
- Yes, but ethernet is strongly preferred for a server — Wi-Fi adds latency, dropout risk, and power management quirks. If you must use Wi-Fi, configure it in Raspberry Pi Imager before flashing and apply the static IP to the wlan0 connection instead of eth0.
- Why use UUID in fstab instead of /dev/sda1?
- Device paths like /dev/sda1 are assigned by the order the kernel detects devices at boot and can shift if you add another USB device. UUIDs are tied to the filesystem itself and remain stable regardless of port or boot order.
- Does the watchdog protect against application crashes, or only kernel hangs?
- The systemd-managed hardware watchdog catches kernel panics and complete system hangs. For application-level restarts, use systemd service units with Restart=on-failure — those work independently of the watchdog.
- How much swap should I allocate on the USB drive?
- 1 GB is sufficient for a Pi 4 with 4 GB RAM running typical server workloads. Swap on USB is much slower than on a real SSD, so the goal is to have it as an emergency safety net, not as extended working memory — hence setting swappiness to 10.
- Will the static IP survive a full SD card reflash?
- No. The static IP configuration is stored on the SD card. After a reflash you start from DHCP again. Keep a note of your configuration or use a preseed/cloud-init approach to automate it.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.