Building a Secure Travel Router with Raspberry Pi, CSF, and Pi-hole

Introduction

When connecting to public networks — hotels, airports, cafés — you are effectively placing your devices in an untrusted environment. While VPNs help, they do not give you full control over how your devices communicate with the outside world.

In this project, I built a portable travel router using a Raspberry Pi that:

  • creates its own secure Wi-Fi network
  • strictly controls outbound traffic
  • enforces DNS filtering via Pi-hole
  • adapts to different uplinks (Wi-Fi or Ethernet)
  • remains observable and debuggable at all times

This article explains both the design philosophy and the implementation approach, with key snippets to illustrate the setup.

If you just want the summary:

TL;DR

🔒 Secure Wi-Fi network
🛡️ DNS filtering via Pi-hole
🚫 Strict outbound firewall
🌍 Works in hotels and public networks


High-Level Architecture

At a high level, the router sits between your devices and the internet:

  • Devices connect to a local Wi-Fi network (wlan1)
  • The Raspberry Pi routes traffic through:
    • wlan0 (Wi-Fi uplink), or
    • eth0 (wired / hotel network)

The system enforces strict firewall rules and DNS policies before traffic leaves the device.


Design Goals

This project was guided by a few key principles:

  • Default deny: nothing leaves the network unless explicitly allowed
  • Deterministic behavior: no hidden system magic — everything is script-controlled
  • Portability: works in hotels, homes, and mobile setups
  • Observability: every decision can be inspected and verified
  • Safe switching: uplink changes should never break connectivity silently

Core Components

The system combines a few well-known tools with custom logic:

  • hostapd → provides the Wi-Fi access point
  • Pi-hole → DNS filtering and control
  • CSF (iptables) → firewall and traffic policy
  • Custom scripts:
    • uplink switching
    • mode control (strict vs bootstrap)
    • diagnostics

Network Modes

Egress Mode

The router operates in two outbound modes:

  • Normal mode
    • strict firewall rules
    • DNS forced through Pi-hole
  • Bootstrap mode
    • relaxed rules
    • useful for captive portals (hotels)

Example:

sudo egress-mode normal
sudo egress-mode bootstrap

Uplink Switching

The router can dynamically switch its internet source:

sudo uplink-switch wlan0
sudo uplink-switch eth0
sudo uplink-switch none

This updates:

  • default route
  • NAT configuration
  • firewall rules

Hotel Mode (Ethernet DHCP)

Some hotel networks require DHCP and captive portals.

The router supports this with:

sudo hotel-hard-wired-mode on

This:

  • switches eth0 to DHCP
  • waits for a valid lease
  • verifies routing before switching over

Firewall Philosophy

The firewall is the most critical part of the system.

Key rules:

  • Default: DROP everything
  • Only allow:
    • explicitly whitelisted outbound ports
    • established connections
  • Block:
    • direct DNS (force Pi-hole)
    • lateral movement between clients

Example Firewall Logic

Allow outbound HTTPS:

-A FORWARD -s 192.168.4.0/24 -o wlan0 \
-p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT

Allow established connections:

-A FORWARD -i wlan1 -o wlan0 \
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Without this rule, even valid HTTPS sessions break — a subtle but critical detail.


Handling Real-World Traffic

One of the most interesting aspects of this setup is observing real device behavior.

Example: Apple Devices

Apple devices use:

  • TCP 5223 → push notifications (APNs)

If blocked:

  • notifications are delayed
  • battery usage increases

Conclusion:
→ this port should be allowed


Diagnostics and Observability

A dedicated script (router-health) provides a full system overview:

  • interface status
  • routing
  • firewall state
  • DNS enforcement
  • access point health

This avoids the typical “black box” feeling of network setups.


Lessons Learned

A few key insights from building this:

  • Stateful firewalling is essential
    Missing ESTABLISHED,RELATED rules breaks everything in subtle ways
  • Devices use unexpected ports
    Not all traffic is HTTP/HTTPS
  • Strict control requires iteration
    You observe → adjust → refine
  • Separation of config and state is critical
    Avoid mixing scripts and runtime values

Conclusion

This travel router provides:

  • a secure networking layer on top of untrusted networks
  • full control over outbound traffic
  • a transparent and inspectable system

It strikes a balance between security, usability, and flexibility.


Source Code

The full implementation (scripts, configuration, firewall rules) is available here:

👉 [For future reference]


Final Thoughts

This project started as a simple idea — “make public Wi-Fi safer” — but quickly turned into a deep dive into networking, firewalling, and real-world device behavior.

If you enjoy understanding how systems actually behave under the hood, this is a very rewarding build.