niri-wm/niri · error · anyhow::Error
no free X11 display found after 50 attempts
Error message
no free X11 display found after 50 attempts
What it means
When spawning Xwayland, niri picks a free X display number by trying to exclusively create the lock files /tmp/.X{n}-lock for n in start..start+50 (O_EXCL create). If all 50 attempts hit an existing lock file, it gives up with this error and Xwayland cannot start. Existing locks are usually stale — left by crashed X servers/Xwayland processes (the FIXME notes dead-process locks are not reused yet).
Source
Thrown at src/utils/xwayland/mod.rs:77
(x11_tmp.st_mode & 0o1000) == 0o1000,
"X11 directory is missing the sticky bit"
);
Ok(())
}
fn pick_x11_display(start: u32) -> anyhow::Result<(u32, OwnedFd, Unlink)> {
for n in start..start + 50 {
let lock_path = format!("/tmp/.X{n}-lock");
let flags = OFlags::WRONLY | OFlags::CLOEXEC | OFlags::CREATE | OFlags::EXCL;
let Ok(lock_fd) = rustix::fs::open(&lock_path, flags, 0o444.into()) else {
// FIXME: check if the target process is dead and reuse the lock.
continue;
};
return Ok((n, lock_fd, Unlink(lock_path)));
}
Err(anyhow!("no free X11 display found after 50 attempts"))
}
fn bind_to_socket(addr: &SocketAddr) -> anyhow::Result<UnixListener> {
let listener = UnixListener::bind_addr(addr).context("error binding socket")?;
Ok(listener)
}
#[cfg(target_os = "linux")]
fn bind_to_abstract_socket(display: u32) -> anyhow::Result<UnixListener> {
use std::os::linux::net::SocketAddrExt;
let name = format!("/tmp/.X11-unix/X{display}");
let addr = SocketAddr::from_abstract_name(name).unwrap();
bind_to_socket(&addr)
}
fn bind_to_unix_socket(display: u32) -> anyhow::Result<(UnixListener, Unlink)> {
let name = format!("/tmp/.X11-unix/X{display}");View on GitHub (pinned to 606284464d)
Solutions
- Verify no live X servers hold them: 'pgrep -a "Xwayland|Xorg|Xvfb"', then remove stale locks: 'rm /tmp/.X[0-9]*-lock' and stale sockets 'rm /tmp/.X11-unix/X*' (only for display numbers not in use).
- Kill and let niri respawn Xwayland cleanly (log out and back in) so it picks the freed display number.
- Free display numbers by shutting down unneeded nested X servers (Xvfb from CI, x11vnc) that squat low display numbers.
- As a last resort reboot (or remount tmpfs /tmp) to clear the lock directory entirely.
Example fix
# before: 50 lock files -> "no free X11 display found after 50 attempts" ls /tmp/.X*-lock # after: clean stale locks for dead servers and restart the session pgrep -a Xwayland # confirm which are alive rm -f /tmp/.X*-lock /tmp/.X11-unix/X* niri msg action do-screen-transition # or just relaunch the app / relogin
Defensive patterns
Strategy: fallback
Validate before calling
# shell: detect stale locks (dead PID) before launching Xwayland consumers for f in /tmp/.X*-lock; do [ -e "$f" ] || continue pid=$(cat "$f") if ! kill -0 "$pid" 2>/dev/null; then echo "stale lock: $f (pid $pid dead)"; fi done
Try / catch
match pick_x11_display(start) {
Ok(x) => x,
Err(_) => {
// fallback: sweep dead-owner lock files once, then retry the range
clean_stale_x11_locks();
pick_x11_display(start).context("no free X11 display even after cleaning stale locks")?
}
} Prevention
- Log out cleanly (not SIGKILL) so Xwayland removes its /tmp/.Xn-lock and /tmp/.X11-unix/Xn.
- Periodically reap dead-owner locks: the lock file contains the owning PID — verify with kill -0 before deleting.
- Do not run dozens of Xvfb/X servers with low display numbers on shared long-lived machines.
- Reboot or clear tmpfs /tmp after repeated compositor crashes to reset the display-number space.
When it happens
Trigger: 50 consecutive /tmp/.X*-lock files present: many crashed Xwayland/X sessions accumulating locks in a long-lived /tmp, another X server (or several) legitimately holding lock files in that range, or /tmp not being cleaned because the system has not rebooted and processes leaked locks.
Common situations: Long-running machines where compositor crashes or force-killed Xwayland leave /tmp/.X1-lock .. .X50-lock behind; nested X servers (Xvfb, x11vnc, distroboxes) consuming display numbers; /tmp on tmpfs cleared only at reboot on a system that never reboots.
Related errors
AI-assisted analysis of niri-wm/niri@606284464d (2026-08-16).
Data as JSON: /api/errors/1018e71d8d0ffdd8.
Report an issue: GitHub.