2026-08-02
Sharing an X Server Across Hosts with FamilyWild
Ever tried running an X11 application from inside a container, a chroot, or over
ssh with a bind-mounted .Xauthority — only to be greeted by the ever-helpful
Authorization required, but no authorization protocol specified?
The file is right there, mounted read-only where the client expects it, and yet
X refuses the connection. The reason is subtle, and the fix is a single line of
sed.
Why the cookie is rejected
An .Xauthority file is a list of cookies, and every entry is keyed by a
family and a hostname. When a client connects, it doesn't just grab the
first cookie it finds — it looks for the entry whose hostname matches the
machine the client believes it's running on.
That's exactly what breaks the moment the client runs somewhere other than where the cookie was minted. Inside a container the hostname is different; over an un-forwarded socket the client resolves a different name entirely. The cookie is present and valid, but its hostname doesn't match, so the client never offers it and X falls back to "no authorization protocol."
You can see the family/hostname keying for yourself:
$ xauth list
myhost/unix:0 MIT-MAGIC-COOKIE-1 a1b2c3d4e5f6...
That leading myhost/unix:0 is the problem — it pins the cookie to myhost.
FamilyWild to the rescue
X has a wildcard family, FamilyWild, whose numeric value is 0xffff. A cookie
in this family matches any hostname. So instead of trying to make the client's
hostname match the cookie, we rewrite the cookie to match every host.
xauth nlist prints entries in a numeric format where the first field is the
4-hex-digit family. Overwrite it with ffff and merge the result into a fresh
file:
: > /tmp/portable.Xauthority && xauth nlist :0 | \
sed 's/^..../ffff/' | xauth -f /tmp/portable.Xauthority nmerge -
# Replace :0 with your $DISPLAY value
The change is a single field
It's worth seeing just how small the edit is. An .Xauthority entry is a packed
binary record: a 2-byte family, then length-prefixed address, display number,
auth name, and the cookie itself. Dump the original file and the family sits in
the very first two bytes — 0100, i.e. FamilyLocal:
00000000: 0100 0006 6d79 686f 7374 0001 3000 124d ....myhost..0..M
00000010: 4954 2d4d 4147 4943 2d43 4f4f 4b49 452d IT-MAGIC-COOKIE-
00000020: 3100 10a1 b2c3 d4e5 f607 1829 3a4b 5c6d 1..........):K\m
00000030: 7e8f 90 ~..
Now dump the FamilyWild version we just built. Everything is byte-for-byte
identical — same myhost address, same cookie — except those first two bytes,
now flipped to ffff:
00000000: ffff 0006 6d79 686f 7374 0001 3000 124d ....myhost..0..M
00000010: 4954 2d4d 4147 4943 2d43 4f4f 4b49 452d IT-MAGIC-COOKIE-
00000020: 3100 10a1 b2c3 d4e5 f607 1829 3a4b 5c6d 1..........):K\m
00000030: 7e8f 90 ~..
That one field is the whole trick: the address bytes still spell myhost, but X
no longer cares, because family 0xffff matches unconditionally.
Bind-mount (or scp) /tmp/portable.Xauthority wherever the client runs, point
XAUTHORITY at it, and the connection is accepted regardless of the hostname
mismatch.
What about xhost +?
You'll often see xhost + suggested as the "just make it work" answer, and it
does — by turning host-based access control off entirely. Every client from
every host can then connect to your display without any cookie at all.
On a single-user machine that sounds harmless, but X has no isolation between
clients: anyone who can reach the server can read your keystrokes, grab the
contents of any window, and inject synthetic input. xhost + hands that
capability to every local user and, if your server listens on TCP, to the
network. Even the narrower xhost +local: still trusts every UID on the box.
The FamilyWild cookie keeps the door locked — a client still has to present the
secret — while removing only the hostname constraint that was getting in the
way. Reach for it instead of xhost +, and keep the cookie file at 0600.
One caveat
A FamilyWild cookie is deliberately less specific than the one it replaces: anyone who can both reach your X socket and read this file can talk to your display. Hand it only to the environments that actually need it, and don't leave copies lying around on shared machines.
This trick is one small piece of a larger setup — I use it to forward X into unprivileged LXC containers, which I wrote up in detail in Enhancing x11 Application Security with LXC.