Project 01 — Linux / Hosting

Self-Hosted Portfolio
on Raspberry Pi

Configuring a Raspberry Pi 4 as a home web server — setting up lighttpd, enabling SSH remote access, and exposing the site publicly through a Cloudflare Tunnel on a custom domain.

Raspberry Pi 4 Raspberry Pi OS lighttpd SSH Cloudflare Tunnel Linux CLI

Goal

Host this portfolio website directly from a Raspberry Pi on my home network, practising real Linux administration, remote access, and web server configuration along the way.

Environment

  • Raspberry Pi 4 (4 GB)
  • Raspberry Pi OS (64-bit, Lite)
  • lighttpd web server
  • SSH for remote terminal access
  • Cloudflare Tunnel for public domain
  • Windows 11 host on same LAN

Setup process

1
Started from an existing Raspberry Pi OS install

The Pi 4 came with Raspberry Pi OS already on the microSD card. Connected via SSH and ran sudo apt update && sudo apt upgrade -y to bring the system fully up to date before starting configuration.

2
Found the Pi on the network

Used ping raspberrypi.local from Windows to confirm it was reachable. Verified the IP in the router's DHCP lease table and set a static reservation so the address doesn't change.

3
Connected via SSH and updated the system

SSHed in with ssh [email protected], ran sudo apt update && sudo apt upgrade -y, and confirmed the system was fully up to date before proceeding.

4
Confirmed lighttpd was running

lighttpd was already installed on the system. Verified the service was active with sudo systemctl status lighttpd, then loaded the Pi's IP in a browser to confirm it was serving HTTP correctly.

5
Deployed the site files

Transferred the portfolio from the Windows PC to /var/www/html/ using SCP. Restored web server file ownership with chown and restarted lighttpd. Verified the portfolio loaded correctly at http://192.168.1.174.

6
Exposed the site publicly via Cloudflare Tunnel

Installed cloudflared, created a tunnel routing elliottmorrisit.com to lighttpd on port 80, and registered it as a systemd service so it starts automatically on every boot. The site is now publicly accessible with no port forwarding on the router.

Commands used

bash — pi@raspberrypi
# Connect to the Pi over SSH from Windows
ssh [email protected]
Linux raspberrypi 6.1.21-v8+ aarch64
graymor@raspberrypi:~ $

# Update system
sudo apt update && sudo apt upgrade -y

# Verify web server is running
sudo systemctl status lighttpd
● lighttpd.service - Lightweight HTTP server
   Active: active (running)

# Transfer site files from Windows via SCP
scp -r ".\elliott-portfolio-v2\." [email protected]:/var/www/html/

# Restore ownership so web server can read files
sudo chown -R www-data:www-data /var/www/html/
sudo systemctl restart lighttpd
● lighttpd.service: active (running)

Screenshots

Windows Terminal SSH session connected to the Raspberry Pi showing the graymor@raspberrypi prompt
SSH session from Windows Terminal — connected to the Pi over the LAN
systemctl status output showing the lighttpd web server active and running
lighttpd service — active (running)
The portfolio site loading in a browser at elliottmorrisit.com
Live site loading at elliottmorrisit.com

What went wrong

Pi and PC ended up on different subnets

The router was handing the Pi a 10.0.0.x address while the Windows PC was on 192.168.1.x, so SCP couldn't reach the Pi at all. Diagnosed it with hostname -I on the Pi and ipconfig on Windows, then pinned the Pi to a static 192.168.1.174 via /etc/dhcpcd.conf so both machines shared a subnet.

Site went down after deploying files

After SCP'ing the site, the pages stopped loading. The transferred files were owned by my user account, not the web server, so lighttpd couldn't read them. Fixed by restoring ownership with sudo chown -R www-data:www-data /var/www/html/ and restarting the service — a reminder that web servers run as their own user.

What I learned

Linux file permissions matter immediately

The first real error I hit was a permissions issue. Learned to check ownership with ls -la and to understand when sudo is needed vs. when it's the wrong tool.

A static IP is essential for a server

The Pi's address kept shifting — and at one point landed on a different subnet than my PC, which broke remote access entirely. Setting a static IP in /etc/dhcpcd.conf gave it a fixed, predictable address to SSH and deploy to.

systemctl is the go-to for service management

start / stop / enable / status — I used these constantly. Understanding the difference between starting a service now vs. enabling it at boot is fundamental.

Test connectivity at every layer

ping → SSH → HTTP. Verifying each layer separately made it easy to narrow down where a problem lived rather than guessing at the application level first.