What Is the Frankenrouter?
The Frankenrouter is a repurposed Dell OptiPlex desktop running Linux Mint that replaced a consumer router to serve as the production network gateway for LocalAd Media LLC. The name comes from bolting together enterprise-grade routing and proxy functionality on commodity hardware — a little ugly under the hood, but it works.
It handles every inbound request for all production domains across the entire LocalAd infrastructure. One public IP, multiple servers, 20+ domains — the Frankenrouter sorts it all out.
Hardware
- Hardware: Dell OptiPlex (retired desktop PC)
- OS: Linux Mint (Debian-based)
- Hostname: r2
- LAN IP: 10.0.1.1 (gateway for the LAN segment)
- NICs: 4x Intel NICs — MAC-locked to persistent names via udev
- Public IP: 38.30.162.111 (via Wire3 fiber modem)
The four-NIC setup was the key enabler. It allowed proper network segmentation into distinct zones that a single-NIC machine or a consumer router couldn't cleanly provide.
Network Interfaces
Early on there was a major headache: Linux would randomly reassign NIC names on reboot, scrambling which physical port was wan0 vs lan0 vs dmz1. The fix was MAC address locking via udev persistent net rules:
/etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:e2:59:01:ad:25", NAME="wan0"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:e2:59:02:34:25", NAME="lan0"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:e2:59:01:ad:24", NAME="dmz1"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="00:e2:59:02:34:26", NAME="dmz2"
Once those were locked in, reboots became predictable. The interface map:
- wan0 — uplink to Wire3 fiber modem / internet
- lan0 — LAN segment (10.0.1.x) — desktops, NAS, ai1, mail server, vc1
- dmz1 — DMZ1 (10.0.10.x) — ser1 (HestiaCP, user la1)
- dmz2 — DMZ2 (10.0.20.x) — ser2 (HestiaCP, user S2)
Network Topology
Internet (38.30.162.111)
|
Wire3 Modem (bridge mode / 10.0.1.1 side)
|
wan0 — FRANKENROUTER (r2)
|
+---------+----------+----------+
| | | |
lan0 dmz1 dmz2 (internal)
10.0.1.x 10.0.10.x 10.0.20.x
| | |
| ser1 ser2
| (HestiaCP) (HestiaCP)
|
+-- ai1 (10.0.1.190)
+-- vc1 (10.0.1.160)
+-- mail (10.0.1.187)
+-- NAS (10.0.1.148)
+-- linux2 (desktop)
Core Software Stack
HAProxy
HAProxy is the heart of the routing layer. With a single public IP serving two servers and 20+ domains, HAProxy inspects each incoming request and routes it to the right backend:
- HTTP (port 80):
mode http— reads theHost:header, routes based on domain ACLs - HTTPS (port 443):
mode tcp— SNI passthrough, peeks at the TLS handshake to read the SNI hostname, forwards the encrypted stream without terminating SSL
SSL is terminated at the backend servers (nginx on ser1/ser2), not at HAProxy. This keeps certificate management on HestiaCP where it belongs.
Example ACL structure in /etc/haproxy/haproxy.cfg:
frontend https_front
bind *:443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
acl is_server1 req_ssl_sni -i volusiamarket.com
acl is_server1 req_ssl_sni -i www.volusiamarket.com
acl is_server2 req_ssl_sni -i marketsfl.com
acl is_server2 req_ssl_sni -i www.marketsfl.com
...
use_backend server1_https if is_server1
use_backend server2_https if is_server2
default_backend server1_https
backend server1_https
mode tcp
server server1 10.0.10.10:443 check
backend server2_https
mode tcp
server server2 10.0.20.10:443 check
Adding a domain is simple: add two ACL lines (bare domain + www) to both the HTTP and HTTPS frontends, then haproxy -c -f /etc/haproxy/haproxy.cfg && systemctl reload haproxy.
iptables / NAT
Standard MASQUERADE NAT for outbound traffic, with FORWARD rules allowing internal servers to reach the internet and receive inbound traffic on 80/443. Rules are persisted via iptables-save > /etc/iptables/rules.v4 so they survive reboots.
An early problem was Apache on one of the servers hijacking port 80 before nginx, which broke the HAProxy routing. Fixed by correcting the NAT rules and ensuring nginx was the first listener on the server side.
dnsmasq
Handles DHCP and local DNS for the LAN (10.0.1.x segment). Internal machines resolve each other by hostname. The LAN segment includes ai1, vc1, the mail server, NAS, and linux2 desktop — all get stable IPs via DHCP reservations or static assignment.
Special Routing: ai.localad.pro
The AI API server (ai1, 10.0.1.190) needed external HTTPS access for the domain ai.localad.pro. Because ai1 is on the LAN segment (not DMZ), it can't receive direct inbound connections. The solution:
- HAProxy routes
ai.localad.proto ser1 (10.0.10.10) via the standard frontend ACL - ser1's nginx has a custom include at
/home/la1/conf/web/ai.localad.pro/nginx.ssl.conf_ai_proxywith a regex location block that proxies specific API routes (/health,/models,/chat,/generate, etc.) through toai1:8001 - The nginx include approach avoids editing HestiaCP-managed config files that get wiped on domain rebuild
This gives the FastAPI/Uvicorn service on ai1 a clean public HTTPS endpoint without punching it directly through the firewall.
Monitoring Dashboard
A custom Python monitoring dashboard was built and runs as a systemd service (frankenrouter.service) on r2. It exposes a local web UI at port 8888 with:
- Live interface status (wan0, lan0, dmz1, dmz2)
- HAProxy backend health
- iptables rule verification
- Packet capture (top talkers via tcpdump)
- Security alerts (failed SSH, open ports, NAT issues)
- Flap detection for interface bouncing
Stack: pure Python 3 stdlib (no dependencies), cyberpunk-styled dashboard HTML. Files live at /opt/frankenrouter/.
Backup System
A backup/restore script set was built at /opt/frankenrouter/:
- backup_frankenrouter.sh — snapshots current config with timestamp to
/srv/router-backups/ - install_safe.sh — automated safe install that backs up first, then installs, then verifies
- restore_frankenrouter.sh — one-command emergency restore from any named backup
Standard discipline before any config changes: run the backup script, make the change, verify, commit.
Current Status
Production — Healthy.
The Frankenrouter is the live gateway for all LocalAd Media LLC production traffic. All domains across ser1 and ser2 route correctly. HAProxy handles HTTP and HTTPS for 20+ domains across two backend servers. The ai.localad.pro reverse proxy is stable. NIC names are MAC-locked and survive reboots without intervention.
Known standing issues:
- Wire3 modem is in router mode (double-NAT: Wire3 → Frankenrouter). This works but is not ideal. Bridge mode on Wire3 would give Frankenrouter the true public IP on wan0. Low priority since traffic flows correctly.
- The frankenrouter systemd monitor service requires manual start after reboot (same issue as the ai1 FastAPI service — both need a proper ExecStart verification pass).
Lessons Learned
- MAC-locking NICs via udev rules is non-negotiable on a multi-NIC Linux router. Without it, any reboot is a gamble.
- HAProxy SNI passthrough for HTTPS is cleaner than SSL termination at the proxy — keeps certs on the servers where HestiaCP manages them.
- Never touch iptables without saving first and having a console fallback. Remote lockout on a router with no physical KVM access nearby is a bad day.
- The nginx include mechanism (HestiaCP's
conf/web/domain/nginx.ssl.conf_*) is the right way to extend nginx config without fighting HestiaCP's rebuild cycle.