Configure WezTerm with Lua
Configure WezTerm entirely in Lua: set fonts, colour schemes, key bindings, split panes and tabs, the built-in mux server, and SSH session integration.
Before you start
- ▸A working graphical session (X11 or Wayland) or SSH access to a headless server
- ▸Basic familiarity with a terminal and a text editor
- ▸A Nerd Font or other programming font installed for glyph support (optional but recommended)
WezTerm is a GPU-accelerated terminal emulator written in Rust with a first-class Lua configuration API. Unlike terminals that bolt on scripting as an afterthought, WezTerm exposes its entire configuration surface through ~/.config/wezterm/wezterm.lua (or ~/.wezterm.lua). That means fonts, colour schemes, keybinds, multiplexing, and SSH sessions are all controlled from one version-controllable file. This guide walks through a practical, production-ready config from scratch.
Installation
Debian/Ubuntu
curl -fsSL https://apt.fury.io/wez/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/wezterm-fury.gpg
echo 'deb [signed-by=/usr/share/keyrings/wezterm-fury.gpg] https://apt.fury.io/wez/ * *' \
| sudo tee /etc/apt/sources.list.d/wezterm.list
sudo apt update && sudo apt install -y wezterm
Fedora / RHEL / Rocky
sudo dnf copr enable wez/wezterm -y
sudo dnf install -y wezterm
Arch Linux
sudo pacman -S wezterm
Verify the install and note your version — some APIs discussed here require WezTerm 20240203 or later:
wezterm --version
The Config File Structure
WezTerm looks for config in ~/.config/wezterm/wezterm.lua first, then ~/.wezterm.lua. Create the preferred location:
mkdir -p ~/.config/wezterm
touch ~/.config/wezterm/wezterm.lua
Every config file must return a table. The minimal valid file:
cat ~/.config/wezterm/wezterm.lua
local wezterm = require 'wezterm'
local config = wezterm.config_builder()
-- settings go here
return config
wezterm.config_builder() is preferred over a bare table because it catches typos in key names at startup and prints a warning rather than silently ignoring them.
Fonts and Appearance
Set a font, fallbacks, size, and line height. WezTerm uses its own font resolution, so the name must match exactly what the font reports — run wezterm ls-fonts to see what is available.
config.font = wezterm.font_with_fallback {
{ family = 'JetBrains Mono', weight = 'Medium' },
{ family = 'Noto Color Emoji' },
'monospace',
}
config.font_size = 13.0
config.line_height = 1.2
Applying a Colour Scheme
WezTerm ships with over 1 000 built-in themes. List them with wezterm ls-color-schemes. Pick one by name:
config.color_scheme = 'Tokyo Night'
For a custom palette, override the colors table directly:
config.colors = {
foreground = '#cdd6f4',
background = '#1e1e2e',
cursor_bg = '#f5e0dc',
cursor_fg = '#1e1e2e',
selection_bg = '#585b70',
selection_fg = '#cdd6f4',
ansi = { '#45475a','#f38ba8','#a6e3a1','#f9e2af',
'#89b4fa','#cba6f7','#89dceb','#bac2de' },
brights = { '#585b70','#f38ba8','#a6e3a1','#f9e2af',
'#89b4fa','#cba6f7','#89dceb','#a6adc8' },
}
Window Decorations and Padding
config.window_decorations = 'RESIZE' -- removes title bar, keeps resize border
config.window_padding = { left = 12, right = 12, top = 8, bottom = 8 }
config.window_background_opacity = 0.95 -- 0.0–1.0; requires compositor
Wayland note: window_background_opacity requires a compositor that supports the wlr-layer-shell or xdg-shell blur protocol (e.g., sway with blur patches or KDE Plasma). On plain GNOME/Wayland without extensions it has no visible effect.
Key Bindings
WezTerm keybinds are a list of {key, mods, action} tables. The default leader key paradigm mirrors tmux if you prefer it:
config.leader = { key = 'a', mods = 'CTRL', timeout_milliseconds = 2000 }
config.keys = {
-- split pane horizontally
{ key = '|', mods = 'LEADER|SHIFT',
action = wezterm.action.SplitHorizontal { domain = 'CurrentPaneDomain' } },
-- split pane vertically
{ key = '-', mods = 'LEADER',
action = wezterm.action.SplitVertical { domain = 'CurrentPaneDomain' } },
-- navigate panes
{ key = 'h', mods = 'LEADER', action = wezterm.action.ActivatePaneDirection 'Left' },
{ key = 'l', mods = 'LEADER', action = wezterm.action.ActivatePaneDirection 'Right' },
{ key = 'j', mods = 'LEADER', action = wezterm.action.ActivatePaneDirection 'Down' },
{ key = 'k', mods = 'LEADER', action = wezterm.action.ActivatePaneDirection 'Up' },
-- close current pane
{ key = 'x', mods = 'LEADER', action = wezterm.action.CloseCurrentPane { confirm = true } },
-- new tab
{ key = 'c', mods = 'LEADER', action = wezterm.action.SpawnTab 'CurrentPaneDomain' },
-- switch tabs by number
{ key = '1', mods = 'LEADER', action = wezterm.action.ActivateTab(0) },
{ key = '2', mods = 'LEADER', action = wezterm.action.ActivateTab(1) },
}
Panes vs Tabs
Tabs are top-level containers inside a window — each tab has its own process tree. Panes subdivide a tab, sharing the tab's concept of a working directory domain. Use tabs when you want logically separate workspaces (e.g., one tab per project). Use panes when you need side-by-side views of the same context — logs next to a shell, or a file open in an editor next to a REPL.
Pane sizing is adjusted interactively with AdjustPaneSize or set programmatically via the SplitPane action's size field (a float 0.0–1.0 representing the fraction given to the new pane):
{ key = 'd', mods = 'LEADER',
action = wezterm.action.SplitPane {
direction = 'Right',
size = { Percent = 35 },
}
},
Multiplexing with the Built-in Mux
WezTerm has a native multiplexer that works over Unix sockets or TCP — no tmux required. Start a headless mux server:
wezterm start --daemonize
Connect from another terminal or another machine:
wezterm connect unix # local socket, default path ~/.local/share/wezterm/sock
To persist sessions across graphical logouts, run the mux under systemd as a user service. Create the unit file:
cat ~/.config/systemd/user/wezterm-mux.service
[Unit]
Description=WezTerm Mux Server
After=default.target
[Service]
ExecStart=%h/.cargo/bin/wezterm start --daemonize --front-end=Mux
Restart=on-failure
Environment=DISPLAY=:0
[Install]
WantedBy=default.target
systemctl --user enable --now wezterm-mux.service
Note: Adjust ExecStart to the correct binary path (which wezterm). The --front-end=Mux flag runs without a GPU window, suitable for headless or server contexts.
SSH Integration
WezTerm can multiplex inside a remote session without installing anything on the server — it uploads a static copy of its mux server binary over SSH automatically when you use an SshDomain. Configure remote hosts in wezterm.lua:
config.ssh_domains = {
{
name = 'my-server',
remote_address = '192.168.1.50', -- hostname or IP
username = 'ops',
-- WezTerm will use your SSH agent or ~/.ssh/id_ed25519 automatically
multiplexing = 'WezTermMux', -- uploads & runs wezterm-mux-server remotely
},
{
name = 'bastion',
remote_address = 'bastion.example.com',
username = 'admin',
multiplexing = 'None', -- plain SSH, no mux
},
}
Connect with a keybind or from the launch menu:
{ key = 's', mods = 'LEADER',
action = wezterm.action.AttachDomain 'my-server' },
With WezTermMux multiplexing, you get full pane/tab persistence on the remote: disconnect, reconnect, and your session is intact — equivalent to tmux but driven entirely from the local WezTerm window.
Dynamic Config Reloading and the Event System
WezTerm watches the config file and reloads automatically on save. You can also trigger a reload manually:
{ key = 'r', mods = 'LEADER',
action = wezterm.action.ReloadConfiguration },
Use the event system to react to config load, set a dynamic tab title, or change the colour scheme based on time of day:
wezterm.on('update-right-status', function(window, pane)
local info = pane:get_foreground_process_info()
local name = info and info.name or ''
window:set_right_status(wezterm.format {
{ Foreground = { Color = '#89b4fa' } },
{ Text = ' ' .. name .. ' ' },
})
end)
Verification
After editing the config, check for syntax errors before restarting:
wezterm --config-file ~/.config/wezterm/wezterm.lua ls-fonts 2>&1 | head -5
WezTerm also writes a debug log you can tail while it is running:
tail -f ~/.local/share/wezterm/wezterm.log
Open the built-in debug overlay inside a running WezTerm window with Ctrl+Shift+L — it shows Lua errors, event traces, and live config state.
Troubleshooting
- Blank window on Wayland: Try launching with
WAYLAND_DISPLAY=unset to fall back to XWayland while you diagnose GPU/driver issues:env -u WAYLAND_DISPLAY wezterm. - Font not found: Run
wezterm ls-fonts --text 'abc'to see exactly which font files are resolved. The family name in config must match the PostScript or family name, not the filename. - SSH mux binary upload fails: The remote must allow SCP or SFTP. Check
Subsystem sftpis enabled in/etc/ssh/sshd_configon the server. Also confirm the remote architecture — WezTerm ships mux binaries for x86_64 and aarch64 Linux. - Leader key not firing: Ensure no other application (e.g., a running tmux) is consuming the same chord before WezTerm sees it.
- Config errors silently ignored: Use
wezterm.config_builder(), not a bare{}table — only the builder validates keys.
Frequently asked questions
- Do I need tmux if I use WezTerm's built-in multiplexer?
- No. WezTerm's native mux provides persistent tabs and panes over SSH without tmux. The main reason to keep tmux is if you connect to a host with a non-WezTerm client or need tmux-specific plugins.
- Why does window_background_opacity have no effect on my system?
- Transparency requires a compositor that composites window backgrounds. On X11 you need a compositor like picom running. On Wayland, GNOME without Shell extensions does not expose the necessary protocol; sway or KDE Plasma work reliably.
- Can I split config across multiple Lua files?
- Yes. Use Lua's standard require or dofile. Place helper modules in ~/.config/wezterm/ and require them by relative name, e.g. local keys = require 'keys' to load ~/.config/wezterm/keys.lua.
- How do I use a different colour scheme for SSH sessions vs local ones?
- Use the update-status or window-config-reloaded event with pane:get_domain_name() to detect which domain is active, then call window:set_config_overrides({ color_scheme = '...' }) accordingly.
- Is wezterm.config_builder() available on older releases?
- config_builder() was introduced in early 2023 nightly builds and is stable from the 20230408 release onward. On older versions use a plain table, but you will lose the key-validation warnings.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.