Ghostty + tmux + Neovim + LazyVim on macOS: A Practical Setup Guide

I’ve used Vim and tmux for years, but always in its plain, unadorned form. On a recent work trip I decided to build a modern terminal setup on my laptop: Ghostty as the terminal, tmux for persistent sessions, and Neovim with the LazyVim distribution as the editor. This post is what I learned along the way. It also doubles as my checklist for replicating the setup on my home machine.

Why this combination?

Each piece does one job well:

  • Ghostty is a fast, GPU-accelerated terminal that uses native macOS UI. It has sensible defaults, needs very little configuration, and bundles a Nerd Font, so icon-heavy tools render correctly out of the box.
  • tmux is a terminal multiplexer. Its real superpower is persistence: your work keeps running even after you close the terminal window (more on this below).
  • Neovim is a modernized fork of Vim. Everything you know from Vim still works.
  • LazyVim is a preconfigured Neovim distribution that turns it into an IDE-like editor (fuzzy file finding, project-wide search, language servers, git integration) without months of hand-configuration.

Step 1: Install Ghostty

With Homebrew:

brew install --cask ghostty

I use the fish shell (brew install fish), so all commands in this post work in fish. They’ll work in zsh too, apart from the fish-specific function near the end. This also assumes tmux is installed.

Step 2: Verify colors

Open Ghostty and run ls -al in your home directory. You should see directories in blue, symlinks in magenta, and so on. One thing that surprised me: a directory shown as black text on a yellow background isn’t a rendering glitch. It’s macOS’s ls warning you that the directory is writable by everyone (drwxrwxrwx). Worth a look from a security standpoint.

The ls colors only test the basic 16-color palette. Modern Neovim themes need true color (24-bit). Test it:

printf "\x1b[38;2;255;100;0mTRUECOLOR\x1b[0m\n"

If the word prints in a distinct orange, true color works. Run it again inside tmux (start tmux with tmux), because tmux is where colors usually break.

Step 3: Configure tmux

Add these lines to ~/.tmux.conf:

# True color support for Ghostty
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-ghostty:RGB"

# Remove the delay after Escape (makes leaving insert mode in Neovim instant)
set -sg escape-time 10

# Let Neovim know when files change outside the editor
set -g focus-events on

# Let yanks reach the macOS clipboard
set -g set-clipboard on

After editing, restart tmux completely with tmux kill-server and start a fresh session. Reloading inside an existing session doesn’t always pick up terminal changes.

Step 4: Install Neovim and LazyVim

Install Neovim and the tools LazyVim depends on:

brew install neovim git ripgrep fd fzf lazygit tree-sitter-cli
xcode-select --install

LazyVim needs a recent Neovim (0.11 or newer), which Homebrew provides. ripgrep and fd power search, fzf drives the pickers, tree-sitter-cli builds syntax parsers, lazygit gives you a git interface, and the Xcode command line tools supply the C compiler for Treesitter.

Back up any existing Neovim config (if these say “No such file or directory,” there was nothing to back up):

mv ~/.config/nvim ~/.config/nvim.bak
mv ~/.local/share/nvim ~/.local/share/nvim.bak

Install the LazyVim starter:

git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git

Run nvim. On first launch, LazyVim installs all its plugins. Let it finish, press q, quit with :qa, and reopen.

Step 5: Run the health check (and don’t panic)

Inside Neovim, inside tmux, run :LazyHealth (or :checkhealth for everything). You’ll get a long, colorful report. Green OK lines are fine, yellow WARNING lines are usually optional, and red ERROR lines deserve a look. Tip: type /ERROR and press Enter to jump to the red lines, then n for the next one.

Here’s what I saw, and what it meant:

  • luarocks errors (lazy section). The line just above says “no plugins require luarocks, so you can ignore any warnings below.” Harmless. To silence it, open ~/.config/nvim/lua/config/lazy.lua and add rocks = { enabled = false }, inside the require("lazy").setup({ ... }) block.
  • “found existing packages at ~/.local/share/nvim/site/pack/core”. On my machine this was an empty folder left by Neovim’s built-in plugin manager. Remove it with rmdir ~/.local/share/nvim/site/pack/core/opt ~/.local/share/nvim/site/pack/core. rmdir only deletes empty directories, so it’s a safe test.
  • ast-grep missing (grug-far section). Only needed for an advanced structural search mode. Optional: brew install ast-grep.
  • Snacks.image errors (magick, gs, tectonic, mmdc). The first line says the feature is disabled, so the errors don’t apply. Snacks.image can display images, PDFs, LaTeX math, and Mermaid diagrams inside Neovim, and Ghostty and tmux both support it. If you want it later, install the converters (brew install imagemagick ghostscript tectonic and npm install -g @mermaid-js/mermaid-cli) and enable it in your config.
  • Missing Treesitter languages. LazyVim installs parsers as you open each file type, so these fill in on their own.

Getting oriented in LazyVim

The biggest reassurance: underneath, it’s still Vim. Every motion and command (hjkl, dd, ciw, :%s/, macros, registers) works exactly as before. LazyVim adds a layer on top, triggered mostly by the spacebar (the “leader” key).

A handful of shortcuts covers most daily use:

Keys Action
Space (pause) Popup menu of every available command. Your safety net.
Space Space Fuzzy-find files in the project
Space / Search text across all files
Space e Toggle the file explorer
Space , Switch between open buffers
Space g g Open lazygit
Space u (pause) UI toggles (line numbers, wrapping, spell check…)

:Lazy opens the plugin manager and :Mason manages language servers.

Commands and scratch buffers

:LazyHealth is an Ex command, the same kind as :w. Its output opens in a scratch buffer: a temporary window of text, not a file on disk. You can scroll, search, and yank from it, and closing it with :q discards it. To tell what you’re looking at:

  • The statusline shows a filename for real files, and a label like health: or [No Name] otherwise.
  • Ctrl-g prints the buffer’s name and path.
  • :echo &buftype prints nothing for a normal file, or nofile, help, terminal, etc.

Relative line numbers

LazyVim shows relative line numbers: the cursor line shows its real number, and every other line shows its distance from the cursor. This makes motions like 5j or d3k trivial, because the count is right there on screen.

To jump to an absolute line, use :42 or 42G, exactly as in Vim. gg goes to the top and G to the bottom. You rarely need to do arithmetic: for anything visible, a relative jump gets you there, and absolute numbers come from error messages and colleagues. Space u L toggles relative numbering off and on.

Ghostty’s own tabs and splits

I didn’t know Ghostty had native windows, tabs, and splits:

Keys Action
Cmd-N / Cmd-T New window / new tab
Cmd-D / Cmd-Shift-D Split right / split down
Cmd-[ / Cmd-] Cycle through splits
Cmd-Option-arrows Move between splits by direction
Cmd-Shift-Enter Zoom a split
Cmd-W Close split or tab

Run ghostty +list-keybinds to see the exact bindings in your version.

Ghostty’s splits feel smoother than tmux’s (native views, natural mouse resizing, Mac-style scrollback), but they vanish when the window closes and only exist on your local machine. My rule of thumb: Ghostty splits for quick, disposable local work; tmux for anything that should survive.

Working with tmux sessions

What persistence means

tmux separates where your programs run from the window you view them through. Starting tmux creates:

  1. A tmux server, a background process that owns your sessions and everything running in them.
  2. A tmux client, the display you see in Ghostty, which shows a session and forwards your keystrokes.

Close Ghostty and only the client disappears. The server, and everything in it (Neovim with unsaved buffers, a long-running job), carries on. Reattach later and you’re exactly where you left off. Think of a security camera feed: switching off the monitor doesn’t empty the room.

This matters most over SSH. Run a long job on a remote server inside tmux, and a dropped Wi-Fi connection no longer kills it. Reconnect, tmux attach, and it’s still running.

The limit: sessions survive disconnects, not reboots. The tmux-resurrect plugin can restore session layouts after a restart, but not the live state of programs.

The prefix key

Most tmux shortcuts start with the prefix, Ctrl-b by default: press it, release, then press the next key. (Check your .tmux.conf, since some people change it to Ctrl-a or Ctrl-Space.)

Name your sessions

If you just type tmux, sessions are numbered 0, 1, 2… and the numbers pile up quickly. On my laptop, the status bar showed [4] 0:nvim*: session 4, window 0, running Neovim. That meant sessions 0 to 3 had been created before it, some possibly still lurking in the background.

The better habit is to start tmux intentionally with a meaningful name, using -A (“attach if it exists, otherwise create”):

tmux new -A -s work

Typing it twice lands you in one work session, not two. Name sessions after projects or contexts, and use windows within a session (Ctrl-b c creates one) for Neovim, a shell, logs, and so on.

A small fish function saves keystrokes:

function t
    tmux new -A -s $argv[1]
end
funcsave t

Now t work takes you straight there.

Session commands

From a shell outside tmux:

Command Action
tmux new -A -s work Attach to or create the session “work”
tmux ls List running sessions
tmux attach -t work Reattach to “work”
tmux kill-session -t work End one session
tmux kill-server End all sessions

From inside tmux:

Keys Action
Ctrl-b d Detach (session keeps running)
Ctrl-b s Interactive session list with preview (Enter switches, x kills, q closes)
Ctrl-b w Same, expanded to show every window
Ctrl-b ( / Ctrl-b ) Previous / next session
Ctrl-b $ Rename the current session
Ctrl-b c New window

Creating a new session from inside tmux

Don’t run tmux new inside tmux, because that nests tmux within itself (tmux will refuse with a warning). Use the command prompt instead:

  1. Press Ctrl-b :
  2. Type new -A -s telemetry and press Enter.

tmux creates the session and switches you into it, and your previous session keeps running. From a shell, the two-step equivalent is tmux new -d -s telemetry followed by tmux switch-client -t telemetry.

Shutting down cleanly

tmux kill-server ends every session at once, and it works from inside tmux too (your window drops back to a plain shell). But it kills programs immediately, so the proper shutdown is:

  1. Ctrl-b s to see what’s open.
  2. Visit anything important, save and quit Neovim with :wqa, and let jobs finish.
  3. Then tmux kill-server.

To clear out leftovers while keeping your current session, use tmux kill-session -a. A session also ends naturally when you exit its last shell, and the server shuts itself down when the last session ends.

Should tmux start automatically with Ghostty?

It’s tempting, and many people do it. The upside is that nothing is ever lost when you close a window. The downsides: you largely give up Ghostty’s nicer native splits and scrollback, a naive setup spawns a new session per window, and SSHing into servers that also run tmux leads to tmux inside tmux.

My approach: start tmux manually for a while. If I find I’m typing it every single time, I’ll automate it with attach-or-create, so every window joins one session. The fish version, in ~/.config/fish/config.fish, has a built-in safety net:

if status is-interactive; and not set -q TMUX
    tmux new-session -A -s main
end

Leaving out exec means that if tmux fails (say, a typo in .tmux.conf), or I exit it, I land in a plain shell rather than a window that mysteriously closes. The alternative is Ghostty’s command = /opt/homebrew/bin/tmux new-session -A -s main setting in ~/.config/ghostty/config (check the path with which tmux), which is cleaner but offers no fallback.

Replicating the setup on another Mac

The fastest route on a second machine is to repeat the installs above and then copy the configuration files across:

  • ~/.tmux.conf
  • ~/.config/nvim/ (your LazyVim config, including any edits to lazy.lua and options.lua)
  • ~/.config/ghostty/config, if you’ve customized Ghostty
  • ~/.config/fish/functions/t.fish, if you created the t function

On first launch, LazyVim reinstalls all plugins automatically. Then run the true-color test and :LazyHealth inside tmux to confirm everything matches. Longer term, keeping these files in a git “dotfiles” repo makes every future machine a five-minute job.

Happy hacking.