Add remote.bindHost config to force interface#478
Merged
ericdallo merged 2 commits intoMay 30, 2026
Conversation
a793e25 to
875b901
Compare
The remote server tries the 0.0.0.0 wildcard first and silently falls back to 127.0.0.1 plus the detected LAN IP when the wildcard is taken — which routinely happens when running multiple ECA instances on the same port. A loopback-bound instance is unreachable over a VPN, and the Tailscale CGNAT range (100.64.0.0/10) is never auto-added as a LAN connector since it isn't a site-local address, so remote.host (which is display-only) gave no way to fix it. remote.bindHost pins the server to a specific interface — e.g. a Tailscale IP, or 0.0.0.0 to always claim the wildcard — binding only there with no fallback. Port discovery (7777-7796) is preserved and unaffected; it just steps ports on the chosen interface. When unset, behaviour is unchanged. The display host defaults to bindHost when host is omitted and it isn't the wildcard.
875b901 to
a0085ef
Compare
ericdallo
reviewed
May 30, 2026
Member
ericdallo
left a comment
There was a problem hiding this comment.
Interesting, I don't have that in my linux.
I wonder if we should just use config.host here, I created it which that purpose WDYT?
Contributor
Author
|
There is legitimate use for the two existing separately. Eg. If you had a DNS name you wanted to use for /remote but it didn't necessarily resolve as bindable on the host (eg. Behind a nginx server or something) I don't have too strong of an opinion though so I defer to your judgement. This approach also just maintains backward compatibility with the existing behavior. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With multiple ECA instances on macOS, the remote server alternates between
binding the
0.0.0.0wildcard and falling back to127.0.0.1on the sameport. Loopback-bound instances are unreachable over a VPN (e.g. Tailscale),
and there's no way to control which interface an instance binds to —
remote.hostis display-only.Environment
net.inet6.ip6.v6only=0)remote.enabled: true, default port 7777, Tailscale activeCurrent behavior
The bind logic tries
0.0.0.0first per port, falling back to127.0.0.1onBindException. Since the first instance binds the IPv6 wildcard (*:7777), asecond can still bind
127.0.0.1:7777, producing an alternating pattern:0.0.0.0:7777(wildcard)127.0.0.1:77770.0.0.0:7778(wildcard)127.0.0.1:7778Confirmed via
lsof:Only the wildcard-bound instance is reachable over Tailscale; loopback ones
silently fail from other devices, with no indication which instance won.
Steps to reproduce
remote.enabled: true.lsof -iTCP:7777 -sTCP:LISTEN -n -P→ one on*:7777, one on127.0.0.1:7777.<tailscale-ip>:7777from another device — works or silentlyfails depending on which instance won the wildcard.
Proposed fix
Add a
remote.bindHostoption to pin the server to a specific interface (e.g.the Tailscale IP, or
0.0.0.0to always claim the wildcard), with no loopbackfallback. Port discovery still applies on the chosen interface.
Notes
macOS/dual-stack specific — on a stricter stack the
127.0.0.1fallback wouldfail with
EADDRINUSEand jump to the next port instead.