If you run services such as Pi-hole, Fail2Ban, Home Assistant, or other web applications on a Raspberry Pi, you've probably faced the same question:
How can I access the web interface securely without exposing it to my local network or the Internet?
Important note: This article is for Linux users, not for Windows user.
One elegant solution is SSH port forwarding.
In this article, I'll show how I use a small Bash script that:
- creates an SSH tunnel to my Raspberry Pi
- forwards a local port to the remote web service
- automatically opens Chromium
- cleans everything up when I'm done
No reverse proxy. No VPN. No open ports.
Why SSH Port Forwarding?
Imagine your Raspberry Pi runs a web application that only listens on localhost: 127.0.0.1:8080
That means the service is not reachable from the network, which is exactly what we want from a security perspective.
SSH allows us to temporarily expose that service only to our own computer.
The tunnel looks like this:
Browser
│
│ http://127.0.0.1:8080
│
Local PC
│
SSH Tunnel
│
Raspberry Pi
│
127.0.0.1:8080
│
Web Application
The browser thinks it's talking to a local web server, while SSH transparently forwards all traffic to the Raspberry Pi.
The SSH Command
Note: In this article I will use the IP address 192.168.0.10 for the Raspberry Pi server.
The core of the solution is simply:
ssh -N \
-L 8080:127.0.0.1:8080 \
my-user@192.168.0.10
The options mean:
-N – don't execute a remote shell
-L – create a local port forwarding
8080 – local port
127.0.0.1:8080 – destination on the Raspberry Pi
After running the command, opening http://127.0.0.1:8080/
on your own computer actually connects to the service running on the Raspberry Pi.
Automating Everything
Typing the SSH command every time gets old quickly, the Bash script automates the complete workflow.
It performs the following steps:
- Removes any stale tunnel from a previous run
- Creates a new SSH tunnel
- Verifies that the tunnel was established successfully
- Launches Chromium automatically
- Opens the desired web interface
- Cleans up the SSH tunnel when Chromium exits (or when the script is interrupted)
The result is essentially a one-click launcher.
Full shell script
#!/usr/bin/env bash
# fail2ban-ui SSH tunnel launcher
# ===== CONFIG =====
PI_USER="my-user"
PI_HOST="192.168.0.10"
LOCAL_HTTP_PORT="8080"
REMOTE_HOST="127.0.0.1"
REMOTE_HTTP_PORT="8080"
# Browser binary name
# The name of the browser binary. This is used to start the web browser,
# and also to lookup the web browser process
CHROMIUM_BIN="chromium"
PID_FILE="/tmp/fail2ban-ui-ssh.pid"
# ==================
# Pi-hole admin URL
URL="http://127.0.0.1:${LOCAL_HTTP_PORT}/admin/"
# Cleanup function
cleanup() {
echo ""
echo "[+] Cleaning up ..."
if [ -f "${PID_FILE}" ]; then
SSH_PID=$(cat "${PID_FILE}")
if ps -p "${SSH_PID}" > /dev/null 2>&1; then
echo "[+] Closing SSH tunnel ..."
kill "${SSH_PID}" 2>/dev/null
fi
rm -f "${PID_FILE}"
fi
echo "[+] Done"
}
trap cleanup EXIT INT TERM
# Removing any existing old SSH tunnel
echo "[+] Removing old tunnel (if existing) ..."
if [ -f "${PID_FILE}" ]; then
OLD_PID=$(cat "${PID_FILE}")
if ps -p "${OLD_PID}" > /dev/null 2>&1; then
kill "${OLD_PID}" 2>/dev/null
fi
rm -f "${PID_FILE}"
fi
# Create new SSH tunnel
echo "[+] Starting SSH tunnel ..."
ssh -N \
-p 22 \
-L ${LOCAL_HTTP_PORT}:${REMOTE_HOST}:${REMOTE_HTTP_PORT} \
${PI_USER}@${PI_HOST} &
SSH_PID=$!
echo "${SSH_PID}" > "${PID_FILE}"
sleep 2
if ! ps -p "${SSH_PID}" > /dev/null 2>&1; then
echo "[!] Failed to establish SSH tunnel"
exit 1
fi
echo "[+] SSH tunnel established"
# Chromium handling
if pgrep -x ${CHROMIUM_BIN} > /dev/null 2>&1; then
echo "[+] Chromium already running"
echo "[+] Opening URL in new tab ..."
${CHROMIUM_BIN} "${URL}" >/dev/null 2>&1 &
echo "[+] Press CTRL+C to close the SSH tunnel"
wait "${SSH_PID}"
else
echo "[+] Chromium not running"
echo "[+] Starting Chromium ..."
${CHROMIUM_BIN} \
--new-window \
"${URL}" >/dev/null 2>&1 &
CHROMIUM_PID=$!
echo "[+] Waiting for Chromium to close ..."
wait "${CHROMIUM_PID}"
fi
Configuration
Only a few variables need to be adjusted:
PI_USER="my-user"
PI_HOST="192.168.0.10"
LOCAL_PORT="8080"
REMOTE_HOST="127.0.0.1"
REMOTE_PORT="8080"
Explanations:
PI_USER - SSH username
PI_HOST - Raspberry Pi hostname or IP
LOCAL_PORT - Port exposed on your computer
REMOTE_HOST - Usually 127.0.0.1
REMOTE_PORT - Port of the web application on the Pi
Automatic Cleanup
One feature I particularly like is the cleanup function.
trap cleanup EXIT INT TERM
Whenever the script exits, whether normally or because you press Ctrl+C, it automatically:
- stops the SSH tunnel
- removes the PID file
- leaves no orphaned processes running
This prevents multiple tunnels from piling up over time.
Chromium Integration
The script checks whether Chromium is already running.
If it is: a new tab is opened.
Otherwise: Chromium starts in a new window.
When Chromium was launched by the script, it waits until the browser is closed before terminating the SSH tunnel.
This makes the tunnel exist only for as long as it is actually needed.
Why Not Just Expose the Web Interface?
Many Raspberry Pi tutorials suggest binding services to 0.0.0.0, or opening firewall ports.
Personally, I prefer keeping administrative interfaces accessible only via SSH because:
- no additional firewall rules are required
- the service never becomes reachable from the LAN
- authentication is handled by SSH
- all traffic is encrypted
It's a simple security improvement that costs almost nothing.
Other Services
The same approach works for almost any web application running on your Raspberry Pi.
Examples include:
- Pi-hole
- Fail2Ban GUI
- Home Assistant
- Grafana
- Prometheus
- Node-RED
- Portainer
- Gitea
- Admin dashboards
- Internal development tools
Simply change the remote port and URL.
Possible Improvements
Depending on your workflow, you could extend the script with features such as:
- automatic SSH key detection
- configurable command-line parameters
- support for multiple Raspberry Pis
- launching Firefox instead of Chromium
- automatic port availability checks
- desktop launcher integration
- support for multiple forwarded services simultaneously.
Final Thoughts
SSH port forwarding is one of those Unix/Linux features that quietly solves a common problem in a secure and elegant way.
With a small Bash script, you can turn what would normally be several manual steps into a seamless workflow: start the tunnel, open the browser, work as usual, and let the script clean everything up when you're finished.
If you administer Raspberry Pi systems regularly, it's a simple quality-of-life improvement that also helps keep your administrative interfaces private and secure.

Top comments (0)