Access macOS via Public IPv6 SSH from Mobile: Full Troubleshooting Log

Published 2026-07-22

The goal: SSH into a home Mac from Termius on a phone over cellular (5G/4G IPv6), with no port forwarding and no third-party tunnel. Half of this log is the usual macOS/Termius troubleshooting. The other half — and the part that actually took time — was figuring out where, among four pieces of router hardware and an ISP-locked ONT, the chain was actually breaking. Writing it down so I don’t have to re-derive it next time.

1. Rebuilding the Topology: From Double NAT to Native IPv6

1.1 Why the ONT can’t be the primary router

The ONT (光猫) is hardware provided by the ISP, and configuration control lives with them — router/bridge mode switching and IPv6 Prefix Delegation (PD) are off by default, and changing either usually means calling support or logging in with a super-admin password. This particular unit is also fairly old, with a modest CPU, so the number of devices it can serve concurrently is limited (NAT translation and DHCP both cost CPU cycles — that’s the working assumption). Using it as the whole house’s primary router was already a bad trade before IPv6 entered the picture.

There are actually four pieces of router hardware at play:

  • The ONT: ISP-provided, the first device fiber lands on.
  • A Ruijie router running FIT AP (“thin AP”) firmware.
  • Two TP-LINK dual-band gigabit routers (AC2600, TL-WDR86xx series): noticeably better hardware, capable of serving more devices.

The original topology:

ONT (dynamic routing) → Ruijie (primary router, Wi-Fi + fan-out) → two TP-LINK routers (one per room)

Everything ran on dynamic IP, IPv6 was never enabled, and both Ruijie and the TP-LINK routers each performed their own NAT — double NAT stacked on top of each other, so nothing from outside could reach in.

1.2 Breaking down the goal: coverage can’t regress, don’t spend if avoidable, still get native IPv6

The original ask sounds simple: “low-latency SSH from my phone.” But once it came time to actually decide something, it turned out three goals were in tension at once, and the answer only counts if it satisfies all three:

  1. The Wi-Fi coverage and experience everyone else in the house relies on can’t get worse.
  2. If it can be solved with what’s already owned, don’t spend money on new hardware or go through another round of cabling.
  3. Still land a native, low-latency, publicly reachable IPv6 path to the Mac.

The first idea was to route through Cloudflare: host the domain there and tunnel back to the Mac, which touches nothing in the home routing topology at all — the least disruptive option for goals 1 and 2. But a free-tier account doesn’t get Argo Smart Routing, and in practice the traffic wasn’t landing on a Hong Kong edge node — it was getting routed to the US West Coast instead, with a measured RTT of around 200ms. That’s noticeable, uncomfortable lag for interactive SSH (typing, tmux) and not something worth living with. The tunnel flatly failed goal 3, so it got dropped in favor of a native connection.

Going native meant redesigning the home routing topology was unavoidable. Before that was clear, buying a new router — one dedicated to sitting next to the ONT as the primary router with IPv6 dial-up support — was seriously on the table. Less thinking required, but it costs money and means another round of cabling and setup, so the choice kept flip-flopping between “just buy another box” and “figure out how to make the existing gear work.” Taking a hard look at what the four devices already in the house (the ONT, Ruijie, and the two TP-LINK routers) could actually each do turned out to be the unlock — no new purchase needed at all, as long as each device landed in the role and position that matched its actual capability, all three goals could be met without spending anything. The attempts below are really just testing whether the existing hardware could actually pull that off.

1.3 Attempt 1: dial PPPoE on the innermost room router

Originally, IP addressing from the ONT down to the room routers was all dynamic, handed down via DHCP at each hop. The first instinct was to have the TP-LINK closest to the Mac dial PPPoE itself, hoping IPv6 would come along for the ride. The dial-up worked fine — IPv6 never showed up at all.

Digging further, it became clear that IPv6 Prefix Delegation follows whichever hop actually authenticates the PPPoE session, and this specific ONT only delegates a prefix to the device directly attached to it. With Ruijie sitting in between, the delegation simply had nowhere to go — it had nothing to do with whether the innermost router dialed its own session.

1.4 Attempt 2: move the dial-up to Ruijie

Since PD only reaches a directly-attached device, the next step seemed obvious: dial from Ruijie, which sits right under the ONT. But this Ruijie unit runs FIT AP (“thin AP”) firmware — a mode whose entire design is to be a subordinate device managed by an AC (wireless controller) over a CAPWAP tunnel. It only forwards wireless frames; it has no independent routing stack, no DHCP server, no PPPoE client. Forget IPv6 — it can’t even dial up on its own at all.

Worth chasing down whether that’s a hardware or a firmware limitation: a FIT AP’s inability to route independently is a design decision about its operating mode, not a case of the chipset lacking the horsepower — a FIT AP was simply never meant to be a gateway. Flashing it into “Fat AP” (standalone/autonomous) mode should, in principle, unlock full router functionality, but whether Ruijie’s firmware actually implements an IPv6 PPPoE client and DHCPv6-PD stack depends on the specific model and firmware version — not something to assume either way. There was no way to verify it on this particular unit, so it’s left as an open question rather than something to build a plan around.

Dead end.

1.5 Attempt 3: have the ONT itself dial and re-delegate

Contacted the ISP to configure the ONT itself into router mode, hoping it could take the PD prefix it received and re-delegate pieces of it to multiple downstream devices. Turned out the ONT’s PD is hard-locked to a single recipient — it only delegates a prefix to the one device directly attached to it, and does not forward or sub-delegate further down a second or third hop. Ruijie and the room TP-LINK routers still got nothing.

PD only recognizes “whatever’s directly attached to the ONT,” and that device also has to support an IPv6 PPPoE client and PD itself — so the answer became obvious: take one of the two edge TP-LINK routers (the one that had been sitting in a room doing Wi-Fi only, with no IPv6 requirement) and move it next to the ONT. Switch the ONT to bridge mode, let this TP-LINK dial its own PPPoE session and turn on IPv6 PD, run SLAAC + DHCPv6 on the LAN side, and connect the Mac directly to it.

Ruijie steps down from being the primary router and moves to an edge position that doesn’t need IPv6 and doesn’t serve the Mac — just wireless coverage. No hardware left idle, nothing wasted.

The coverage constraint checks out too: the room the reassigned TP-LINK used to sit in never had many devices in it, and the router there was only ever doing lightweight NAT and Wi-Fi access — it never came close to its performance ceiling, so there was no real capacity being given up. Ruijie backfilling that spot is a fine match: it can’t carry routing or IPv6, but a lightweight edge AP is well within what it can do, so coverage and signal strength don’t take a hit. It amounts to reseating all four devices by matching capability to demand: the ONT is locked to bridge-only duty regardless, so it just hands off the fiber signal; whichever TP-LINK has the better hardware and IPv6 dial-up support takes the demanding spot (directly on the ONT, running routing plus PD); Ruijie, which was never going to carry routing or IPv6 anyway, drops back to the lightest-demand edge position — exactly where it belongs. All three goals — coverage intact, no new spending, native IPv6 — land at once.

Final topology:

ONT (Bridge Mode) → TP-LINK (primary router, PPPoE dial + IPv6 PD) → macOS Client
                              └→ Ruijie (edge AP, wireless only, no IPv6 needed)

With that chain sorted out, everything left on the macOS/Termius side was routine troubleshooting — logged below.

2. macOS / Termius Troubleshooting Log

2.1 Confusion between multiple IPv6 addresses on macOS

ifconfig shows two global IPv6 addresses:

inet6 2001:db8:abcd:1234:10d5:ba62:47bb:dd4c secured
inet6 2001:db8:abcd:1234:7000:bcf7:361d:349d temporary

At first it wasn’t clear which one to use.

  • secured: a stable EUI64 address derived from the MAC address — suitable for inbound services.
  • temporary: a privacy extension address (RFC 8981) that rotates periodically. Never use this for a server.
  • fe80:: addresses: link-local only, unreachable from the public internet.

Always use the address marked secured for inbound SSH.

2.2 Two square-bracket [] gotchas, back to back

nc -6zv [2001:db8:abcd:1234::xxxx] 22
zsh: no matches found: [2001:db8:abcd:1234::xxxx]
  • Shell syntax: zsh treats [] as a glob pattern by default, so it never reaches nc/ssh as a literal string. For nc, drop the brackets entirely. For ssh, either write the address bare (ssh alanlee@2001:db8:...) or wrap the whole RFC-style form in single quotes (ssh 'alanlee@[2001:db8:...]').
  • Tool syntax: even with quoting fixed, macOS’s built-in BSD netcat still doesn’t support bracket-wrapped IPv6 syntax — brackets are a convention for URLs, SSH clients, and browsers, not the nc binary, and forcing them throws getaddrinfo: nodename nor servname provided. Always omit [] when testing connectivity locally.

Important distinction: terminal shell syntax is not the same as the Termius client’s syntax. The Termius address field must use square brackets, [IPv6 address], per RFC 3986.

2.3 Local nc test succeeds, but the phone can’t connect over cellular

nc on the Mac reports Connection succeeded! and SSH works fine over LAN, but Termius on cellular times out. Combined with the topology worked out above, the failure was really several layers stacked together:

  1. The old topology itself (the main culprit): Ruijie (in FIT AP mode) as the primary router, with double NAT, meant nothing from outside could reach the LAN at all.
  2. The ONT’s IPv6 PD only reaches its directly-attached device — Ruijie and the room routers, one hop further out, were never going to get a public IPv6 prefix.
  3. The router’s own IPv6 firewall drops inbound TCP traffic by default.
  4. Some ISPs block inbound port 22 over IPv6 outright.
  5. A phone on Wi-Fi without dual-stack IPv6 can’t originate an IPv6 connection at all — testing has to happen over cellular.

The fix is the one from the previous section: promote an edge TP-LINK to be the router directly attached to the ONT, let it dial up and obtain IPv6 PD itself, then add a firewall rule allowing the SSH port.

2.4 The address changes over time

After a PPPoE re-dial, the ISP-assigned /64 prefix can change entirely, invalidating every IPv6 address recorded so far — residential ISPs almost never hand out static IPv6. The long-term fix is IPv6 DDNS: a Cloudflare AAAA record updated by a scheduled script that reports the Mac’s current secured address, with Termius connecting by hostname instead of a raw address.

3. Full Setup Guide

Step 1: Configure the ONT (fiber modem)

  1. Log in with super admin credentials.
  2. Change the WAN connection from Router Mode → Bridge Mode, and disable the ONT’s built-in DHCP and IPv6 routing.
  3. Reboot the ONT.

If you don’t have super admin access, contact ISP support to switch it to bridge mode remotely.

This TP-LINK now connects directly to the ONT and takes over the role Ruijie used to hold:

  1. WAN connection type: PPPoE, with your broadband account and password.
  2. Advanced → IPv6:
    • Enable IPv6.
    • IPv6 connection type: PPPoE, and tick share the PPPoE session with IPv4.
    • Under advanced settings, turn on Prefix Delegation (PD).
    • LAN IPv6 mode: SLAAC + DHCPv6, so LAN hosts pick up a public IPv6 address automatically.
  3. IPv6 firewall rule (the important part) — add an inbound allow rule:
    • Protocol: TCP
    • Destination port: 22 (SSH)
    • Destination range: your assigned /64 prefix
    • Source: restrict where possible — don’t leave this open to any source permanently
  4. Ruijie moves downstream of this router as an edge AP — wireless coverage only, no dial-up or IPv6 responsibilities.

Security tip: restrict the source range if your router supports it, rather than leaving port 22 open to the whole internet indefinitely. Changing the SSH port can also help dodge ISP-level port blocking.

Step 3: macOS configuration

  1. System Settings → General → Sharing → enable Remote Login (SSH).
  2. Verify sshd is listening on IPv6 (usually on by default on macOS):
sudo lsof -i6 :22
# Expect output containing [::]:ssh
  1. Find the usable public IPv6 address:
ifconfig
# Pick the address marked secured; ignore temporary and fe80:: addresses
  1. Validate locally:
nc -6zv <your secured IPv6 address> 22
ssh alanlee@<your secured IPv6 address>

Step 4: Termius mobile client setup

  1. Create a new host.
  2. Address: [<your secured IPv6 address>] (brackets required).
  3. Port: 22.
  4. Username: your macOS login account.
  5. Authentication: password, or SSH key (recommended).

Test over cellular 5G/4G — not home Wi-Fi.

Step 5: Long-term fix — IPv6 DDNS

  1. Add an AAAA record on a Cloudflare-managed domain.
  2. Run a scheduled shell script on the Mac that detects the current secured IPv6 address and updates the DNS record via the Cloudflare API.
  3. Connect via the hostname in Termius instead of a raw IPv6 address.

Step 6: Troubleshooting checklist

  1. Is the ONT in bridge mode? Router mode blocks inbound IPv6.
  2. Is the router doing the dial-up directly attached to the ONT? Anything one hop further out will never get a PD prefix.
  3. Is IPv6 Prefix Delegation enabled on that router?
  4. Does the router’s IPv6 firewall allow the SSH port?
  5. Does the macOS firewall accept incoming remote login connections?
  6. Is the test phone on an IPv6-capable network (cellular preferred)?
  7. Are you accidentally using the temporary address instead of secured?

Summary

This turned out to be two layers of debugging. The upper layer is the home topology and ISP policy: double NAT, an ONT that only delegates IPv6 to whatever’s directly attached, a thin AP with no independent routing capability at all — until those were sorted out, nothing done on the macOS/Termius side mattered. The lower layer is the more familiar layered-verification problem: working locally doesn’t mean it works from the outside. Between the router’s IPv6 firewall, ISP port policy, and the phone’s own IPv6 support, any single broken layer just looks like “can’t connect.” The only way through is to trace the whole chain from the ONT to the phone and check each hop, instead of guessing at any one layer.