← Lab Notes
July 2, 2026 homelab

Networking Basics #8: Firewalls and Rate Limiting, From the Router Down to 12 Lookups an Hour


Overview

In this installment of our networking basics series, we dive into firewalls and rate limiting. Specifically, we cover how these security measures are implemented in a multi-layered approach starting from the router all the way down to application-level rate limits.

Background

Our homelab environment is designed with security in mind. We use a combination of hardware and software components to ensure that our services remain secure against various types of attacks, including brute-force attempts and automated bots. The layers of defense include:

  1. Router/NAT: Unwanted inbound traffic is blocked by default.
  2. Cloudflare Edge: Web Application Firewall (WAF) and bot detection rules filter traffic before it reaches the tunnel.
  3. Host Firewall (UFW/Iptables): Allows SSH from the local network and specific game ports, while blocking everything else.
  4. Docker Network Isolation: Services without published ports are inaccessible to external traffic.
  5. Application Layer Limits: These limits are implemented in our FastAPI backend using rate limiting with the X-Real-IP header passed by Nginx.

How It Works

Router/NAT

The router acts as the first line of defense, implementing stateful packet filtering. Unwanted inbound traffic is blocked by default, and only explicitly forwarded ports can receive incoming connections. This is a classic example of default deny security policy.

# Example UFW rule for forwarding game port 2049 to internal service
sudo ufw allow from 192.168.1.0/24 to any port 2049 proto tcp

Cloudflare Edge

Cloudflare's edge provides an additional layer of security by filtering web traffic before it reaches the tunnel. This includes WAF rules and bot detection mechanisms.

# Example Nginx configuration for Cloudflare integration (not directly related to rate limiting, but important for overall security)
server {
    listen 80;
    server_name example.com;

    # Cloudflare reverse proxy settings
    set_real_ip_from 1.2.3.4/24; # Replace with actual Cloudflare IP ranges
    real_ip_header CF-Connecting-IP;
}

Host Firewall (UFW/Iptables)

The host firewall uses UFW and iptables to control inbound and outbound traffic. It allows SSH from the local network and specific game ports, while blocking everything else.

# Example UFW rules for allowing SSH and game port 2049
sudo ufw allow ssh
sudo ufw allow 2049/tcp

Docker Network Isolation

Docker containers use network isolation to ensure that services without published ports are not accessible from the outside. This is a form of default deny policy at the container level.

# Example Docker network configuration
docker network create --driver bridge my_network
docker run -d --name game-server --network my_network --publish 2049:2049/tcp my_game_server_image

Application Layer Limits

At the application layer, we implement rate limiting to prevent abuse. This is particularly important for services like contact forms and order trackers.

Contact Form Rate Limiting

The contact form allows up to three submissions per IP per hour using a sliding window mechanism. Additionally, it includes a honeypot field that bots are expected to fill but humans typically do not.

# Example FastAPI rate limiting implementation for the contact form
from fastapi import Depends, HTTPException, status
from fastapi.security import APIKeyHeader
from pydantic import BaseModel
from typing import Optional

X_API_KEY = APIKeyHeader(name="X-Real-IP")

class ContactForm(BaseModel):
    name: str
    email: str
    message: str
    honeypot: Optional[str] = None  # Honeypot field to detect bots

async def check_contact_form_rate_limit(ip_address: str = Depends(X_API_KEY)):
    if await is_ip_rate_limited(ip_address, limit=3):
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests")

def is_ip_rate_limited(ip_address: str, limit: int) -> bool:
    # Implementation of rate limiting logic
    pass

@app.post("/contact-form")
async def submit_contact_form(form_data: ContactForm = Depends(check_contact_form_rate_limit)):
    if form_data.honeypot and len(form_data.honeypot.strip()) > 0:
        return {"status": "ok"}  # Bot detected, silently drop the request
    else:
        # Process valid form data
        pass

Order Tracker Rate Limiting

The order tracker service limits the number of lookups per IP to 12 per hour. This prevents automated enumeration attacks.

# Example FastAPI rate limiting implementation for the order tracker
from fastapi import Depends, HTTPException, status
from fastapi.security import APIKeyHeader
from pydantic import BaseModel
from typing import Optional

X_API_KEY = APIKeyHeader(name="X-Real-IP")

class OrderTrackerRequest(BaseModel):
    order_id: str

async def check_order_tracker_rate_limit(ip_address: str = Depends(X_API_KEY)):
    if await is_ip_rate_limited(ip_address, limit=12):
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Too many requests")

def is_ip_rate_limited(ip_address: str, limit: int) -> bool:
    # Implementation of rate limiting logic
    pass

@app.post("/order-tracker")
async def track_order(order_request: OrderTrackerRequest = Depends(check_order_tracker_rate_limit)):
    if not order_request.order_id.match(r"^[A-Z0-9]{12}$"):  # Example regex validation
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid order ID")
    
    # Process valid order tracking request
    pass

Results

Implementing these layers of security has significantly improved the overall security posture of our homelab. By default denying traffic and implementing rate limiting at various levels, we have effectively mitigated many common attack vectors.

Lessons Learned

  1. Default Deny vs Default Allow: Starting with a default deny policy is crucial for security.
  2. Stateful vs Stateless Filtering: Stateful filtering can provide more context about the state of connections, which is important for detecting and preventing attacks.
  3. Defense in Depth: Multiple layers of defense are more effective than relying on a single solution.

By combining these techniques, we have created a robust security framework that protects our services from various types of attacks while maintaining usability for legitimate users.

Was this useful?