Build a Private Residential Egress Gateway with MASQUE and rstream

Run your own MASQUE-based private residential egress gateway behind a local network, publish it through rstream, route TCP with HTTP CONNECT, route UDP and QUIC with CONNECT-UDP, and validate the setup from macOS and Firefox.


This guide builds a private residential egress gateway. The gateway runs inside a network you control, such as a home network, lab, office, or small site, and remote clients use it as their outbound path to the Internet. The result gives you the practical value people look for in a residential proxy while keeping ownership of the machine, policy, logs, and credentials.

The design has to solve both protocol coverage and reachability.

Protocol coverage starts with the classic HTTP proxy path, where CONNECT tunnels HTTPS over TCP. Modern client traffic also includes UDP for HTTP/3, QUIC, DNS paths, and other application transports, so the gateway adds MASQUE and CONNECT-UDP alongside plain CONNECT.

Reachability is the other half. The useful egress point is often a machine inside the residential or office network, behind NAT, a consumer router, or a site firewall. rstream gives that local gateway a reachable public endpoint through an outbound agent connection, so remote clients can use the gateway while it keeps running inside the private network.

The comparison with a VPN helps frame the boundary. Both patterns make selected traffic exit from another network. A VPN usually attaches the device or workload to a remote network segment. This gateway works at the proxy and application-routing layer. macOS Network Relay, Firefox, or another MASQUE-capable client sends matching traffic through the egress gateway, while unrelated traffic can keep using the normal local path.

Clone the gateway sample:

rstream examples / private-masque-egress-gatewayOpen the private MASQUE egress gateway sample.
git clone https://github.com/rstreamlabs/rstream-examples.git
cd rstream-examples/private-masque-egress-gateway

What You Will Build

The final setup has a gateway process published through rstream.

macOS Network Relay or Firefox
        |
        | HTTPS / HTTP/3 to the public rstream endpoint
        v
rstream edge
        |
        | outbound gateway session
        v
private MASQUE egress gateway
        |
        | TCP CONNECT or UDP CONNECT-UDP
        v
Internet, exiting from the gateway network

The published endpoint carries both TCP and UDP egress:

TrafficClient mechanismGateway protocolTypical use
TCPHTTP proxy tunnelCONNECTHTTPS sites, APIs, package registries, Git over HTTPS
UDPMASQUE tunnelCONNECT-UDPHTTP/3, QUIC, UDP-based application protocols
IP packetsMASQUE packet tunnelCONNECT-IPFull packet routing with a real TUN or router backend

This guide focuses on CONNECT and CONNECT-UDP because they are the useful browser and system egress path today. CONNECT-IP is a lower-level packet-routing primitive for real IP routers or TUN backends. The macOS and Firefox gateway path in this guide uses proxy-level TCP and UDP routing.

The implementation follows the standardized MASQUE shape. CONNECT-UDP uses a URI-template target model, with the default interoperable path:

/.well-known/masque/udp/{target_host}/{target_port}/

For plain TCP tunneling, the gateway accepts the normal HTTP CONNECT method. CONNECT-IP is the companion packet-routing protocol for IP proxy backends, while this guide's macOS and Firefox path focuses on proxy-level TCP and UDP routing.

Client Support

macOS Network Relay is the best first client for this guide. Apple documents Network Relays as MASQUE-based relays built into the OS network stack, available on macOS 14, iOS 17, iPadOS 17, tvOS 17, or later, and able to proxy TCP and UDP traffic at the OS layer. The generated profile uses Apple Relay payload fields for HTTP3RelayURL, HTTP2RelayURL, and AdditionalHTTPHeaderFields.

Firefox is a useful browser validation target. Its networking documentation describes CONNECT-UDP over HTTP/3. Firefox's CONNECT-UDP support is HTTP/3-specific; fallback paths use TCP and standard CONNECT for the inner connection.

That split is important operationally. The gateway should support both paths:

  • CONNECT-UDP over HTTP/3 for UDP and QUIC egress.
  • Plain CONNECT over HTTP/1.1, HTTP/2, and HTTP/3 for TCP fallback and HTTPS egress.

Prepare The Project

Install the rstream CLI, log in, and select the project that will own the published tunnel.

rstream login
rstream project use <project-endpoint> --default

The gateway reads the local rstream context with the Go SDK. It needs an engine that supports HTTP/3, Extended CONNECT, and MASQUE. In self-hosted deployments, run this against rstream Engine Enterprise Edition with the same HTTP protocol support enabled on the public edge listener.

Build and test the sample before publishing it:

make verify

The build produces two binaries:

out/bin/private-masque-egress-gateway
out/bin/private-masque-egress-probe

private-masque-egress-gateway is the real gateway. private-masque-egress-probe is a runtime protocol probe used for validation and CI-style checks. The probe gives you a quick preflight before you point a browser or an operating system at the gateway.

Choose The Egress Network

Run the gateway on the network that should become the egress point. That can be a small always-on host in your home, a Mac mini in an office, a Raspberry Pi, a lab server, or a workload inside a private site.

The host needs outbound connectivity to the rstream engine. Residential and small-site networks work well because the gateway connects out through the existing router and firewall path, while rstream publishes the endpoint remote clients will use.

For the first deployment, pick explicit match domains instead of routing the whole device. The example below routes only the domains used for validation.

export PRIVATE_EGRESS_TOKEN="$(openssl rand -hex 32)"
export RSTREAM_EGRESS_HOST="<stable-domain-in-your-rstream-namespace>"
 
./out/bin/private-masque-egress-gateway \
  --name home-austin-egress \
  --hostname "$RSTREAM_EGRESS_HOST" \
  --label role=egress \
  --label network=residential \
  --label country=US \
  --label region=us-south \
  --metrics 127.0.0.1:9090 \
  --match-domain ifconfig.me \
  --match-domain ipinfo.io \
  --match-domain cloudflare.com \
  --write-mobileconfig ./home-austin-egress.mobileconfig

Use a stable domain that belongs to your current rstream namespace. Hosted projects use the project-scoped shape documented in Stable Domains, such as:

<slug>-<project-endpoint>.t.<engine-host>

Self-hosted engines use:

<slug>.t.<engine-host>

The process prints the published endpoint when it is ready:

READY https://<stable-domain-in-your-rstream-namespace>

Keep PRIVATE_EGRESS_TOKEN secret. The generated Apple profile stores it as an additional relay request header so that macOS can authenticate to the gateway when it opens proxy requests.

Validate the generated profile before installing it:

plutil -lint ./home-austin-egress.mobileconfig
grep -n "HTTP3RelayURL\|HTTP2RelayURL\|MatchDomains\|X-Egress-Token" ./home-austin-egress.mobileconfig

You should see HTTP3RelayURL, HTTP2RelayURL, the configured match domains, and the access-token header.

Understand The Security Defaults

This gateway is intentionally a closed proxy.

The gateway supports two authorization layers. rstream edge authorization is enabled with --rstream-auth or --rstream-token-auth when the downstream client can satisfy the edge requirement. The gateway access token is carried by the X-Egress-Token header, and the generated Apple profile sends it through AdditionalHTTPHeaderFields. Treat the profile as a secret because it contains the gateway token.

The target policy is also conservative by default. The gateway denies private, loopback, link-local, multicast, carrier-grade NAT, documentation, and other special-purpose address ranges unless you explicitly allow them. That matters because an egress proxy sits at a sensitive network boundary. Remote clients should only reach the destination classes you choose, especially around 127.0.0.1, the gateway host, the home router, and the internal LAN.

Use these flags only when the gateway is meant to expose that target class:

--allow-loopback-targets
--allow-private-targets
--allow-link-local-targets

For a private residential Internet egress gateway, keep them disabled. For an internal access gateway, enable only the ranges you deliberately want to expose and pair that with rstream-side authorization.

Install The macOS Network Relay Profile

On a personal test machine, open the generated profile and install it through System Settings.

open ./home-austin-egress.mobileconfig

For a managed fleet, distribute the same Relay payload through MDM instead of installing it manually. The important fields are:

Profile fieldPurpose
HTTP3RelayURLMASQUE CONNECT-UDP URI template for UDP and QUIC traffic
HTTP2RelayURLTCP relay fallback endpoint on the same rstream hostname
AdditionalHTTPHeaderFieldsGateway access token header
MatchDomainsDomains routed through this private egress gateway

Start with MatchDomains. An empty match-domain list makes the relay eligible for broad routing, except excluded domains configured elsewhere in the profile. Domain-scoped rollout gives you a cleaner first validation.

After installation, open a terminal on the client machine and validate the routed domains:

curl -fsS https://ifconfig.me
curl -fsS https://ipinfo.io/ip

The IP address should match the gateway network. If you are testing from a laptop outside the home network, this should look like traffic exiting from home.

Validate HTTP/3 And Firefox

Firefox is useful because its CONNECT-UDP implementation is tied to HTTP/3. With the relay active and a matching domain, open a site that supports HTTP/3, such as:

https://www.cloudflare.com/

Then open:

about:networking#http3

Use the gateway metrics as the source of truth for the protocol path:

curl -fsS http://127.0.0.1:9090/metrics \
  | grep 'rstream_private_masque_requests_total'

For TCP-only sites, you should see protocol="connect". For UDP and QUIC paths, you should see protocol="connect_udp" once the client has used the MASQUE path.

The sample also logs session closure with the target, resolved address, duration, and byte counters for CONNECT. That gives you enough information to distinguish a client configuration problem from a gateway policy denial or an upstream dial failure.

Run A Protocol Probe Before Browser Testing

The browser path is the real outcome, but a protocol probe is useful before installing profiles across machines. It proves the gateway can actually tunnel TCP and UDP before you involve macOS policy, browser cache, DNS behavior, or fallback timers.

Point the probe at a running gateway and at TCP and UDP echo services you control:

./out/bin/private-masque-egress-probe \
  --addr "https://${RSTREAM_EGRESS_HOST}" \
  --tcp-target <tcp-echo-host:port> \
  --udp-target <udp-echo-host:port> \
  --access-token "$PRIVATE_EGRESS_TOKEN"

Expected output:

CONNECT h1 ok
CONNECT h2 ok
CONNECT h3 ok
CONNECT-UDP ok

This check is intentionally lower-level than the rest of the guide. It gives release and operations teams a deterministic protocol check before browser or OS rollout.

Operate More Than One Gateway

The same pattern becomes more useful when you run more than one private egress gateway. Give every gateway labels that describe where it exits and what kind of network it represents.

./out/bin/private-masque-egress-gateway \
  --name home-sf-egress \
  --label role=egress \
  --label network=residential \
  --label country=US \
  --label region=us-west \
  --label metro=san-francisco \
  --metrics 127.0.0.1:9090 \
  --match-domain ifconfig.me \
  --match-domain ipinfo.io \
  --write-mobileconfig ./home-sf-egress.mobileconfig

The labels let your control plane, internal tooling, or operators choose the right egress point. Start by listing the live egress inventory from the CLI:

rstream tunnel list \
  --filter 'labels.role=egress,labels.network=residential' \
  -o json \
  | jq -r '.[] | [.name, (.hostname // .host), .labels.country, .labels.region, .labels.metro] | @tsv'

Then select a gateway for a target region:

RSTREAM_EGRESS_URL="$(
  rstream tunnel list \
    --filter 'labels.role=egress,labels.network=residential,labels.country=US,labels.region=us-west,labels.metro=san-francisco' \
    -o json \
    | jq -er '.[0] | "https://" + (.hostname // .host)'
)"

That is where rstream becomes more than a transport tunnel. It gives each private gateway a reachable endpoint, while labels become the selection and orchestration layer for the egress fleet.

Production Hardening Checklist

Use this checklist before treating the gateway as a production egress path.

  • Require a high-entropy gateway access token and rotate it when profiles are redistributed.
  • Use rstream edge authorization for endpoints restricted to authenticated clients.
  • Keep private, loopback, and link-local targets denied unless the gateway is explicitly an internal access gateway.
  • Use MatchDomains for initial rollout, then expand routing only when observability is in place.
  • Export metrics and alert on authentication denials, policy denials, dial failures, and unexpected traffic volume.
  • Keep gateway logs target-aware but avoid logging application payloads.
  • Run the gateway as a service with restart policy and controlled environment injection for PRIVATE_EGRESS_TOKEN.
  • Keep the machine patched. This host is now a controlled egress boundary.

Troubleshooting

When the profile installs and gateway metrics stay empty, confirm that the tested domain is present in MatchDomains. Then confirm that the client is running an OS version that supports Network Relay.

When CONNECT-UDP stays absent, test an HTTP/3-capable destination and check the UDP/443 path between the client and the rstream endpoint. Firefox uses CONNECT-UDP over HTTP/3, so an HTTP/2 or HTTP/1.1 fallback turns the inner path into plain CONNECT.

When the gateway returns 407 Proxy Authentication Required, the client sent a missing or incorrect access header. Regenerate the Apple profile after changing the token.

If the gateway returns 403 Forbidden, the target was denied by policy. That is expected for private, loopback, link-local, multicast, and special-use ranges unless the corresponding allow flag is set.

When public IP checks still show the client network, route a domain that appears in MatchDomains, remove browser proxy overrides during the test, and check whether the application uses its own network stack outside the OS relay path.

Where To Go Next

The first gateway gives you one private residential egress path. A small fleet extends the pattern with one gateway per trusted site, each labeled by country, region, network type, and owner. Then generate targeted profiles or client configuration from those labels instead of hard-coding one endpoint into every client.

That turns the pattern into a controlled private egress layer. The gateway stays inside the network that owns the IP address, rstream provides outbound reachability, MASQUE carries modern TCP and UDP traffic, and policy stays close to the egress boundary.