Building an Ironclad Edge Gateway: Turn a Raspberry Pi 5 into a Zero-Trust Homelab Firewall and Micro-SOC

Featured Banner Image

Modern homelabs and Internet of Things (IoT) ecosystems have evolved far beyond simple hobbyist setups. From smart switches, ESP32 microcontrollers, and IP surveillance cameras to self-hosted media servers and hypervisors, the sheer density of networked devices inside our homes now rivals small enterprise branch offices. However, consumer routers provided by Internet Service Providers (ISPs) offer practically zero visibility into lateral network movement, lack granular traffic inspection, and leave connected hardware vulnerable to botnet recruitment, malicious payload drops, and DNS spoofing.

Implementing a Zero-Trust Architecture (ZTA) at the local edge is the most effective strategy to safeguard your infrastructure. By assuming every device—especially consumer IoT hardware—is potentially compromised, we can isolate subnets, enforce strict cryptographic authentication, and deploy automated intrusion mitigation. In this comprehensive guide, we will transform a Raspberry Pi 5 into an autonomous, bulletproof edge security gateway and micro-Security Operations Center (Micro-SOC) using WireGuard, Pi-hole, Unbound, and CrowdSec.

Architectural Blueprint: The Edge Micro-SOC

Our security architecture relies on a multi-layered defense-in-depth model running seamlessly on top of a hardened 64-bit operating system:

  • Hardware Base: Raspberry Pi 5 (4GB or 8GB) with PCIe NVMe SSD storage for high I/O throughput and endurance.
  • DNS Isolation Layer: Pi-hole paired with an Unbound recursive resolver executing full DNSSEC validation, stripping third-party tracking and malicious domains before IP resolution.
  • Encrypted Remote Ingress: Kernel-space WireGuard VPN, bypassing vulnerable port-forwarding schemes with cryptographic keypairs.
  • Collaborative Intrusion Detection & Prevention (IDS/IPS): CrowdSec analyzes SSH, authentication, and packet logs in real time, leveraging global peer threat intelligence to auto-ban malicious IPs via nftables.
  • Observability & Telemetry: Prometheus and Node Exporter streaming metrics to a lightweight Grafana instance for anomaly detection.

Step 1: Host OS Hardening & Kernel Tuning

Flash Raspberry Pi OS Lite (64-bit) or Debian 12 Bookworm to your NVMe drive. Before containerizing services, we must harden the host kernel parameters to optimize packet forwarding and protect against common network attack vectors like SYN floods and spoofing.

Open the sysctl configuration file:

sudo nano /etc/sysctl.d/99-security-hardening.conf

Append the following kernel security parameters:

# Enable IPv4 Packet Forwarding for Gateway Routing
net.ipv4.ip_forward = 1

# Protect against SYN flood attacks
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2

# Disable ICMP Redirect Acceptance (Prevents MITM Route Alterations)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Enable Reverse Path Filtering (Mitigate IP Spoofing)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP Broadcast Requests (Smurf Attack Mitigation)
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

Apply the parameters immediately without rebooting:

sudo sysctl --system

Next, enforce key-only SSH authentication and change the default port. Edit /etc/ssh/sshd_config.d/hardened.conf:

Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3

Restart the SSH daemon to apply changes:

sudo systemctl restart ssh

Step 2: Deploying Containerized Core Services

We deploy our core edge stack using Docker Compose. This ensures isolation, reproducible configuration, and rapid disaster recovery.

Create a dedicated directory structure for the gateway stack:

mkdir -p ~/edge-gateway/{pihole,unbound,wireguard,crowdsec,crowdsec/data}
cd ~/edge-gateway

Configuring the Recursive Unbound Resolver

Create the Unbound configuration file at ~/edge-gateway/unbound/unbound.conf:

server:
    verbosity: 1
    interface: 0.0.0.0@5335
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    prefer-ip6: no

    # Access control
    access-control: 127.0.0.1/32 allow
    access-control: 172.16.0.0/12 allow
    access-control: 192.168.0.0/16 allow
    access-control: 10.0.0.0/8 allow

    # Privacy & Security Hardening
    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no
    edns-buffer-size: 1232
    prefetch: yes
    num-threads: 2
    so-rcvbuf: 4m
    so-sndbuf: 4m

The Orchestration: `docker-compose.yml`

Now, build the full multi-service composition file at ~/edge-gateway/docker-compose.yml:

version: "3.8"

networks:
  gateway_net:
    ipam:
      driver: default
      config:
        - subnet: 172.28.0.0/16

services:
  unbound:
    image: mvance/unbound:latest
    container_name: gateway_unbound
    restart: always
    volumes:
      - ./unbound/unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
    networks:
      gateway_net:
        ipv4_address: 172.28.0.2

  pihole:
    image: pihole/pihole:latest
    container_name: gateway_pihole
    restart: always
    depends_on:
      - unbound
    environment:
      TZ: 'UTC'
      WEBPASSWORD: 'SuperSecretAdminPasswordChangeMe'
      PIHOLE_DNS_: '172.28.0.2#5335'
      DNSSEC: "true"
      DNS_BOGUS_PRIV: "true"
      DNS_FQDN_REQUIRED: "true"
    volumes:
      - ./pihole/etc-pihole:/etc/pihole
      - ./pihole/etc-dnsmasq.d:/etc/dnsmasq.d
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
    networks:
      gateway_net:
        ipv4_address: 172.28.0.3

  wireguard:
    image: lscr.io/linuxserver/wireguard:latest
    container_name: gateway_wireguard
    restart: always
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
      - SERVERURL=vpn.yourhomelab.net # Replace with your dynamic DNS
      - SERVERPORT=51820
      - PEERS=laptop,phone,tablet
      - PEERDNS=172.28.0.3 # Route VPN client DNS directly through Pi-hole
      - INTERNAL_SUBNET=10.13.13.0/24
      - ALLOWEDIPS=0.0.0.0/0
    volumes:
      - ./wireguard/config:/config
      - /lib/modules:/lib/modules:ro
    ports:
      - "51820:51820/udp"
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1
    networks:
      gateway_net:
        ipv4_address: 172.28.0.4

  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: gateway_crowdsec
    restart: always
    environment:
      - GID=1000
      - COLLECTIONS=crowdsecurity/linux crowdsecurity/sshd crowdsecurity/whitelist-good-actors crowdsecurity/wireguard
    volumes:
      - ./crowdsec/data:/var/lib/crowdsec/data
      - ./crowdsec:/etc/crowdsec
      - /var/log/auth.log:/var/log/auth.log:ro
      - /var/log/syslog:/var/log/syslog:ro
    networks:
      gateway_net:
        ipv4_address: 172.28.0.5

Start the gateway stack in detached mode:

docker compose up -d

Step 3: Implementing CrowdSec Host-Level Remediation

While the CrowdSec container analyzes logs and correlates events, we must install the CrowdSec Firewall Bouncer directly on the Raspberry Pi host. This bouncer hooks into nftables to drop packets at the kernel layer before they ever reach our containers or internal network.

Install the repository and the firewall bouncer on the host system:

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt-get install -y crowdsec-firewall-bouncer-nftables

Generate a new API key inside the CrowdSec container for the bouncer:

docker exec -it gateway_crowdsec cscli bouncers add host-nftables-bouncer

Copy the generated API token, open the bouncer configuration on the host:

sudo nano /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml

Configure the API URL and token:

api_url: http://127.0.0.1:8080/ # Or http://172.28.0.5:8080/
api_key: <YOUR_GENERATED_API_KEY>
mode: nftables
update_frequency: 10s
log_mode: file
log_level: info

Restart and verify the bouncer service:

sudo systemctl restart crowdsec-firewall-bouncer
sudo systemctl status crowdsec-firewall-bouncer

Test the dynamic ban mechanism using the command line interface:

# Simulate a manual IP ban
docker exec -it gateway_crowdsec cscli decisions add --ip 198.51.100.23 --duration 4h --reason "Suspicious Probe"

# Verify that nftables dropped the IP
sudo nft list set inet crowdsec crowdsec_blacklists

Step 4: Network Micro-Segmentation and Firewall Rules

To establish true Zero-Trust isolation, the Raspberry Pi gateway should dictate traffic rules between your primary LAN, your isolated IoT VLAN, and external remote tunnels. Using nftables, we reject unauthenticated lateral movement from IoT devices to critical servers.

Create an nftables ruleset at /etc/nftables.conf:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Accept loopback traffic
        iif "lo" accept

        # Accept established and related connections
        ct state established,related accept

        # Drop invalid connections
        ct state invalid drop

        # Allow Custom SSH Port
        tcp dport 2222 accept

        # Allow DNS queries from internal subnets
        ip saddr { 192.168.1.0/24, 10.13.13.0/24 } udp dport 53 accept
        ip saddr { 192.168.1.0/24, 10.13.13.0/24 } tcp dport 53 accept

        # Allow WireGuard VPN Ingress
        udp dport 51820 accept

        # Allow ICMP (Ping) with Rate Limiting
        ip protocol icmp icmp type echo-request limit rate 5/second accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;

        # Allow established and related forwarded traffic
        ct state established,related accept

        # Allow WireGuard clients (10.13.13.0/24) to reach Internet and Internal LAN
        ip saddr 10.13.13.0/24 oifname "eth0" accept

        # ISOLATION: Prevent IoT Subnet (VLAN 30) from accessing Management LAN (VLAN 10)
        ip saddr 192.168.30.0/24 ip daddr 192.168.10.0/24 drop

        # Allow IoT Subnet to access only WAN through Gateway
        ip saddr 192.168.30.0/24 oifname "eth0" accept
    }
}

Load and enable the ruleset permanently:

sudo nft -f /etc/nftables.conf
sudo systemctl enable nftables

Step 5: Telemetry & Real-Time Threat Auditing

Visibility is the core pillar of Zero-Trust. Without active telemetry, stealthy beaconing from compromised IoT appliances can go unnoticed for months. To monitor gateway health and dropped traffic:

  1. Expose CrowdSec Metrics: CrowdSec natively exposes a Prometheus metrics endpoint on port 6060. Query metrics using curl http://localhost:6060/metrics.
  2. Deploy Node Exporter: Monitor Raspberry Pi 5 CPU throttling, temperature under load, and I/O bottlenecks.
  3. Grafana Dashboard Integration: Import Grafana dashboard ID 13332 for CrowdSec to visualize active global bans, brute-force attempts, and top offending autonomous system numbers (ASNs).

Verification and Penetration Testing

Once deployed, validate that your Zero-Trust gateway performs as expected:

# 1. Verify Unbound DNSSEC validation
dig sigfail.verteiltesysteme.net @127.0.0.1 -p 53
# The query MUST return SERVFAIL, demonstrating bogus signature rejection.

# 2. Test WireGuard Endpoint Handshake from an external cellular network
wg show

# 3. Verify Active CrowdSec Log Stream Processing
docker exec -it gateway_crowdsec cscli metrics

Conclusion

By transforming a low-power single-board computer like the Raspberry Pi 5 into a hardened edge gateway, you achieve enterprise-grade network security without recurring SaaS subscriptions or closed-source proprietary appliances. With full recursive DNSSEC resolution, adaptive intrusion mitigation powered by global threat intelligence, and zero-trust micro-segmentation, your homelab is thoroughly fortified against modern automated exploits and lateral intrusions.

Comments

Popular posts from this blog

1.Using an LDR (Light Dependent Resistor) with Arduino to Measure Light Intensity

2.Light-Controlled LED Using an LDR and Arduino

Modal verbs