Building a High-Availability Home Proxy: From Ngrok to FRP + Nginx Failover

Building a High-Availability Home Proxy: A Deep Dive into FRP and Nginx Stream Failover

In the world of personal networking and home labs, exposing local services securely or creating a stable proxy for family use is a common challenge. What started as a simple requirement—“I need a proxy for my family that uses my clean home IP”—evolved into a lesson in High Availability (HA) system design.

Here’s how I migrated from unreliable free-tier solutions to a robust, self-hosted architecture using FRP (Fast Reverse Proxy) and Nginx Stream, achieving zero-downtime failover between my Mac (Primary) and VPS (Backup).

⚠️ Disclaimer & Use Cases

The techniques discussed in this article are intended strictly for legitimate Home Network Remote Access and Network Stability Optimization.
Primary use cases include:

  1. Service Continuity (The Need for HA): Ensuring that critical services relying on this network channel (like remote monitoring or smart home controls) remain accessible even when the primary home server (Mac) is undergoing maintenance, sleeping, or experiencing ISP outages.
  2. Home Lab Access: Securely connecting to home NAS, Home Assistant, or git servers from external networks.
  3. Development Environment: Providing a static residential IP for mobile work devices to access IP-whitelisted services or debug in specific network conditions.
  4. Privacy Protection: Encrypting traffic on public Wi-Fi to prevent Man-in-the-Middle attacks.

🛑 The Problem: Quotas, Blocks, and instability

My initial setup relied on ngrok and later Cloudflare Tunnel. Both are excellent tools, but they hit walls in my specific use case:

  1. Ngrok: Great for quick tests, but the free tier has connection limits and dynamic domains. It’s not “production ready” for daily family use.
  2. Cloudflare Tunnel: Zero-trust is amazing, but it struggled with specific UDP traffic and non-standard ports in my restricted network environment.
  3. Local Only: Running a proxy solely on my Mac meant that if I closed my laptop or the network dropped, the internet went “down” for everyone else.

I needed a solution that was fast (using my home bandwidth/IP when available) but reliable (falling back to a VPS when my Mac was offline).

🛠 The Solution: Active-Passive High Availability

I designed a Primary-Backup architecture.

  • Primary Node (Mac): Runs the proxy locally. Low latency, “clean” residential IP.
  • Backup Node (VPS): A cheap cloud server. Always online, acts as a failsafe.
  • The “Switch”: An Nginx load balancer running on the VPS.


graph TD
    User["Client Devices"] -->|"Connect VPS:10086"| Nginx["Nginx Stream LB"]
    
    subgraph VPS ["VPS Server (Cloud)"]
        direction TB
        Nginx
        Backup["Backup Proxy (Port 10088)"]
        FRPS["FRPS Server"]
    end
    
    subgraph Home ["Home Network"]
        direction TB
        FRPC["FRPC Client"]
        Mac["Mac Primary Proxy (Port 10086)"]
    end

    Nginx -->|"Primary Path"| FRPS
    FRPS <==>|"FRP Tunnel (:7000)"| FRPC
    FRPC -->|"Forward"| Mac
    Mac -->|"Internet"| Internet1
    
    Nginx -.->|"Failover Path"| Backup
    Backup -->|"Internet"| Internet2

    Internet1(("Internet"))
    Internet2(("Internet"))

    style Mac fill:#a7f3d0,stroke:#047857,stroke-width:2px
    style Backup fill:#fde68a,stroke:#d97706,stroke-width:2px
    style Nginx fill:#bfdbfe,stroke:#1d4ed8

1. The Tunnel: Why FRP?

Since my Mac has no public IP, I needed a reverse tunnel. I chose FRP over others because:

  • Protocol Support: It handles TCP/UDP perfectly, which is crucial for proxy traffic (unlike some HTTP-only tunnels).
  • Self-Hosted: I control the server. No third-party rate limits.
  • Lightweight: frpc (Client) and frps (Server) are single binaries.

The Config:
My Mac (frpc) connects to the VPS (frps) on port 7000, mapping its local proxy port (10086) to the VPS’s port 10087.

2. The Magic: Nginx Stream Module & Failover

This was the biggest technical learning point. Standard Nginx is famous for HTTP reverse proxying, but for a raw TCP proxy (like a secure custom tunnel), we need the Stream Module.

I used Nginx’s upstream module with the backup parameter to implement active-passive failover logic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
stream {
upstream backend_service {
# Primary: The Tunnel Port from Mac
# max_fails=1 fail_timeout=3s: If this fails ONCE, mark it down for 3 seconds
server 127.0.0.1:10087 max_fails=1 fail_timeout=3s;

# Backup: The Local VPS Proxy
# 'backup': Only used when primary is down
server 127.0.0.1:10088 backup;
}

server {
listen 10086; # The public entry point
proxy_pass backend_service;
proxy_connect_timeout 2s;
}
}

How it works:

  1. Clients connect to VPS:10086.
  2. Nginx attempts to forward traffic to 10087 (the FRP tunnel to Mac).
  3. Happy Path: Mac is online → Traffic flows through the tunnel → Mac -> Internet.
  4. Failure Path: Mac is sleeping/offline → 10087 connection refuses.
  5. Failover: Nginx detects the failure immediately and routes traffic to 10088 (the backup proxy running directly on the VPS).
  6. Recovery: Every 3 seconds (as defined by fail_timeout), Nginx tentatively tries the Primary again. If the Mac wakes up, traffic automatically flows back.

3. The “StateLess” Client Experience

To make this seamless for the end-users (my family), I ensured that both the Mac Proxy and VPS Backup Proxy share the exact same UUID and encryption settings.

To the client (e.g., Shadowrocket), it just sees “Server A”. It has no idea that the underlying backend shifted from a Mac in Seattle to a VPS in LA.

🚀 Key Takeaways

1. Passive Health Checks > Active Probing

In simple TCP setups, you don’t always need complex active health checks (pinging endpoints). Nginx’s passive health check (monitoring the actual connection response) is incredibly efficient. It reacts instantly to connection refusals.

2. The Power of “Wait” (fail_timeout)

Tuning fail_timeout is an art. Set it too high, and you stay on the backup server too long after the primary recovers. Set it too low, and a flaky connection causes “flapping” (rapid switching). 3 seconds turned out to be the sweet spot for a home network environment.

3. Self-Hosting Gives You Control

By moving away from managed services like Cloudflare Tunnel for this specific use case, I regained control over the transport layer. I can see exactly where connections fail—whether it’s the FRP tunnel, the local proxy process, or the Nginx routing.

Conclusion

This project was more than just saving a few dollars on a paid proxy service. It was a practical exercise in Resiliency Engineering. We built a system that degrades gracefully rather than failing catastrophically.

For anyone looking to harden their home lab accessibility: Don’t just build a service; build a backup. And let Nginx handle the rest.


Tools used: FRP (v0.66.0), Nginx (1.26), Docker, X-UI