Installation and First Launch

Download iTerm2 from iterm2.com or install via Homebrew. We use Homebrew on all our test machines because it tracks updates cleanly and integrates with our provisioning scripts.

After install, open iTerm2 and immediately open Preferences (Cmd+,). Under General > Startup, set 'Open profiles window' to off and 'Window restoration policy' to 'Use system window restoration setting'. Under General > Closing, uncheck 'Confirm closing multiple sessions' if you run many panes. These are the settings that bite you during live incidents when confirmation dialogs slow you down.

Set the shell explicitly. Do not rely on the system default. Under Profiles > General > Command, select 'Custom Shell' and enter the full path:

# Verify your shell path before entering it in iTerm2
which zsh
# /opt/homebrew/bin/zsh on Apple Silicon with Homebrew
# /usr/local/bin/zsh on Intel Mac with Homebrew
# /bin/zsh for the Apple-provided version (avoid this one)

# Install latest zsh via Homebrew if not already done
brew install zsh

# Check version - aim for 5.9 or later
zsh --version
# zsh 5.9 (arm-apple-darwin24.0)

Profile Architecture: One Profile Per Context

The biggest productivity gain in iTerm2 is not a keybinding - it is a proper profile setup. Most engineers use one profile for everything. The correct approach is one profile per working context: local development, remote SSH hosts, production access, and a high-contrast emergency profile for incidents.

Create profiles under Profiles > + button. Name them clearly: 'Local Dev', 'Staging SSH', 'Prod Read-Only'. For SSH profiles, set the command directly in the profile rather than typing ssh manually each session.

For your production profile specifically, set a distinct background color (dark red tint at 20% opacity works well) and change the badge text under Profiles > General > Badge to 'PROD'. The badge renders in the top-right of the pane and stays visible even when the terminal is scrolled. This has prevented accidental production writes on our team more than once.

Font choice matters for readability over long sessions. We use JetBrains Mono 13pt on 1440p displays and 14pt on Retina. Install it via Homebrew:

# Install JetBrains Mono
brew install --cask font-jetbrains-mono

# In iTerm2: Preferences > Profiles > Text > Font
# Select 'JetBrains Mono'
# Enable 'Use ligatures' only if your workflow involves code review in terminal
# Disable for log tailing - ligatures slow rendering on high-output streams

# Set cursor: Preferences > Profiles > Text
# Cursor: Vertical Bar, Blinking: On
# This distinguishes iTerm2 panes from vim/tmux inner cursors visually

Shell Integration: The Feature Most People Skip

Shell integration is iTerm2's most underused feature. It injects escape sequences into your shell's prompt that let iTerm2 track command history, working directory, exit codes, and session metadata. It also enables the Toolbelt's recent commands panel and the 'Select Output of Last Command' action (Cmd+Shift+A).

Install it directly from iTerm2's menu: iTerm2 > Install Shell Integration. This writes a source line into your ~/.zshrc or ~/.bashrc. Alternatively, install manually for reproducible provisioning:

After installing, reload your shell and verify the integration is active. You should see the iTerm2 mark (a small triangle) in the left gutter at each prompt. If you are SSHing into remote machines, shell integration also works there - run the same curl command on the remote host and iTerm2 will track remote working directories for its 'Open in Finder' and copy path features.

The 'Command History' Toolbelt panel (View > Show Toolbelt, then add 'Command History') shows every command run across all sessions with timestamps and exit codes. This is genuinely useful for reconstructing what you did during an incident without digging through ~/.zsh_history.

# Manual shell integration install for zsh
curl -L https://iterm2.com/shell_integration/zsh \
  -o ~/.iterm2_shell_integration.zsh

echo 'source ~/.iterm2_shell_integration.zsh' >> ~/.zshrc
source ~/.zshrc

# Verify integration
echo $ITERM_SESSION_ID
# Should output something like: w0t0p0:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

# For bash users
curl -L https://iterm2.com/shell_integration/bash \
  -o ~/.iterm2_shell_integration.bash
echo 'source ~/.iterm2_shell_integration.bash' >> ~/.bashrc
// advertisement

Triggers: Alert on Patterns Without a Separate Monitoring Tool

Triggers fire on regex matches in terminal output. They can highlight text, ring a bell, send a notification, run a coprocess, or open a URL. For DevOps work, they replace a lot of manual log-watching.

Configure triggers under Profiles > Advanced > Triggers > Edit. Each trigger has a regex, an action, and optional parameters. Triggers apply per-profile, so you can have aggressive alerting on your production SSH profile without noise in your local dev profile.

Useful trigger patterns for sysadmins:

- Pattern: `(ERROR|FATAL|CRITICAL)` - Action: Highlight Text (red background). This makes errors visible when tailing logs. - Pattern: `\bOOM\b|out of memory` - Action: Post Notification. Sends a macOS notification even when iTerm2 is not focused. - Pattern: `\$ $` - Action: Set Title. Useful for showing the current working directory in the tab title when shell integration is not available on a remote host. - Pattern: `Job [0-9]+ completed` - Action: Bounce Dock Icon. Alerts you when a long background job finishes.

For complex alerting across multiple services, consider pairing iTerm2 triggers with a tool like taskbotshub.ai, which can receive webhook events from terminal automation and route them into Slack, PagerDuty, or your incident management system. iTerm2's coprocess feature can POST to a webhook on pattern match, which is the bridge between terminal output and external alerting.

# Example: iTerm2 coprocess that fires on 'CRITICAL' match
# Coprocess command (enter in Trigger > Parameters field):
echo 'CRITICAL seen in terminal' | curl -s -X POST \
  -H 'Content-Type: application/json' \
  -d @- \
  https://hooks.example.com/alerts

# Trigger regex: \bCRITICAL\b
# Action: Run Coprocess
# Check 'Use interpolated strings' if you want to pass matched text

tmux Integration: Native Mode vs. Standard

iTerm2 has two ways to use tmux: standard (SSH into a box running tmux, interact normally) and tmux integration mode, which maps tmux windows to iTerm2 tabs and panes natively. In tmux integration mode, you get iTerm2's scrollback, search, and mouse support inside tmux sessions.

To use tmux integration, connect with `tmux -CC` instead of `tmux`. iTerm2 detects the control mode and renders tmux windows as native tabs. You can then use Cmd+T to open a new tmux window, Cmd+D to split, and Cmd+W to kill a pane - all of which translate to tmux commands behind the scenes.

The limitation: tmux integration mode only works when you are the one attaching. If another user attaches the same tmux session without `-CC`, the control-mode sequences appear as garbage in their terminal. For shared sessions, use standard tmux and configure iTerm2's mouse reporting separately.

For remote servers, our recommended setup is tmux 3.4+ with a minimal config, attached via iTerm2's SSH profile using tmux -CC. On our test server (Apple Silicon MacBook Pro, macOS 15.3, iTerm2 3.5.4), this setup gives us persistent sessions that survive network drops while keeping iTerm2's native search and unlimited scrollback.

# Connect to remote tmux in iTerm2 integration mode
ssh user@host -t 'tmux -CC new-session -A -s main'

# If you already have a running tmux session
ssh user@host -t 'tmux -CC attach -t main'

# In ~/.ssh/config - create a host entry that does this automatically
Host prod-tmux
  HostName 10.0.1.50
  User deploy
  RequestTTY yes
  RemoteCommand tmux -CC new-session -A -s main

# Then in iTerm2, create a Profile with:
# Command: ssh prod-tmux
# This gives you one-click tmux integration

Keybindings Worth Configuring

iTerm2's default keybindings are reasonable but incomplete. The ones we add on every installation:

Word-level navigation: macOS Terminal maps Option+Left/Right to move by word. iTerm2 requires explicit mapping. Under Preferences > Profiles > Keys > Key Mappings, add: - Option+Left: Send Escape Sequence, value: `b` - Option+Right: Send Escape Sequence, value: `f` - Option+Backspace: Send Hex Codes, value: `0x17` (delete word back)

Pane navigation without reaching for the mouse: Under Preferences > Keys > Key Bindings (global, not per-profile): - Cmd+Option+Left: Select Split Pane to the Left - Cmd+Option+Right: Select Split Pane to the Right - Cmd+Option+Up: Select Split Pane Above - Cmd+Option+Down: Select Split Pane Below

Broadcast input: When you need to run the same command across multiple panes simultaneously (deploying to N hosts), use Shell > Broadcast Input > Broadcast to All Panes in Current Tab. Toggle it with a custom binding - we use Cmd+Shift+B. Always use this with the production profile's red badge visible to avoid mistakes.

Export your keybindings once configured. This is a JSON file you can commit to your dotfiles repo:

# Export all iTerm2 preferences including keybindings
defaults export com.googlecode.iterm2 ~/dotfiles/iterm2/iterm2.plist

# Import on a new machine
defaults import com.googlecode.iterm2 ~/dotfiles/iterm2/iterm2.plist

# Alternatively, set iTerm2 to load preferences from a custom folder
# Preferences > General > Preferences > Load preferences from custom folder
# Point it to ~/dotfiles/iterm2/
# Enable 'Save changes to folder when iTerm2 quits'
# This makes preferences sync automatically via git
// advertisement

Color Schemes and Readability for Long Sessions

Color scheme choice affects error detection speed in log output. We have tested several schemes under different lighting conditions over multi-hour debugging sessions. Our findings: dark backgrounds with high-contrast ANSI colors reduce eye strain at night but wash out red and yellow in bright office environments. The Solarized Dark scheme handles both reasonably well.

Install color schemes via the iTerm2 Color Schemes repository on GitHub. Download the .itermcolors file and import it under Preferences > Profiles > Colors > Color Presets > Import.

For log tailing specifically, the most important colors are ANSI Red (errors), ANSI Yellow (warnings), and ANSI Cyan (timestamps). Adjust these under Color Presets to be vivid enough to catch your eye during rapid log scrolling without being so bright they cause fatigue.

Minimum contrast ratio for ANSI colors against your background: aim for 4.5:1 for text that carries semantic meaning (error messages, hostnames in prompts). Use the macOS Digital Color Meter (built into Utilities) to check contrast ratios against your chosen background.

# Download and apply Solarized Dark from command line
curl -LO https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Solarized%20Dark.itermcolors

# Then import via:
# Preferences > Profiles > Colors > Color Presets > Import
# Navigate to the downloaded .itermcolors file

# List currently installed color presets via defaults
defaults read com.googlecode.iterm2 'Custom Color Presets' | grep -o '"[^"]*"' | head -20

Python API: Automating iTerm2 From Scripts

iTerm2 3.3+ includes a Python API that gives you programmatic control over sessions, windows, tabs, and panes. It runs via a long-lived Python daemon and communicates with iTerm2 over a Unix socket. You write scripts that iTerm2 executes on startup or on demand.

Install the runtime: iTerm2 > Scripts > Manage > Install Python Runtime. This installs a Python 3.x environment under ~/Library/Application Support/iTerm2/iterm2env/.

A practical use case: open a standardized workspace for a specific project - split panes for editor, server process, and log tail - with one command. When working on projects where environment setup matters (database connections, API keys, correct Python venv), these scripts eliminate a repeatable setup sequence that otherwise takes 90 seconds every morning.

If you work on projects that involve domain or identity setup - say, spinning up a new service and picking a name for it - tools like nicename.me can help you check domain availability before you commit to a project name, and you can wire that into an iTerm2 startup script that opens your workspace only after confirming the name is free.

#!/usr/bin/env python3
# Save to: ~/Library/Application Support/iTerm2/Scripts/dev_workspace.py
# Run via: iTerm2 > Scripts > dev_workspace

import iterm2

async def main(connection):
    app = await iterm2.async_get_app(connection)
    window = app.current_terminal_window
    
    if window is None:
        window = await iterm2.Window.async_create(connection)
    
    tab = window.current_tab
    session = tab.current_session
    
    # Send commands to the first pane
    await session.async_send_text('cd ~/projects/myapp && source .venv/bin/activate\n')
    
    # Split vertically
    right_session = await session.async_split_pane(vertical=True)
    await right_session.async_send_text('cd ~/projects/myapp && make dev-server\n')
    
    # Split the right pane horizontally for logs
    log_session = await right_session.async_split_pane(vertical=False)
    await log_session.async_send_text('tail -f ~/projects/myapp/logs/dev.log\n')

iterm2.run_main_coroutine(main)

SSH Configuration and Dynamic Profiles

If you manage more than ten remote hosts, creating iTerm2 profiles manually does not scale. Dynamic profiles solve this. iTerm2 reads JSON files from ~/Library/Application Support/iTerm2/DynamicProfiles/ and generates profiles from them automatically. You can generate this JSON from your ~/.ssh/config at provisioning time.

Dynamic profiles inherit from a parent profile. Create one base 'SSH Base' profile in iTerm2 with your preferred font, colors, and shell integration path, then generate dynamic profiles that override only the hostname and badge text.

For teams managing large fleets, integrate this generation step into your configuration management. When a new host is provisioned, a script generates the JSON entry, drops it into the DynamicProfiles directory, and iTerm2 picks it up immediately - no restart required.

# Generate dynamic profile JSON from SSH config
# Save as ~/bin/gen-iterm2-profiles.sh

#!/bin/bash
OUTPUT=~/Library/Application\ Support/iTerm2/DynamicProfiles/ssh-hosts.json

echo '{"Profiles": [' > "$OUTPUT"

FIRST=true
grep '^Host ' ~/.ssh/config | grep -v '\*' | awk '{print $2}' | while read HOST; do
  [ "$FIRST" = true ] || echo ',' >> "$OUTPUT"
  FIRST=false
  cat >> "$OUTPUT" << EOF
  {
    "Name": "SSH: $HOST",
    "Guid": "ssh-$HOST",
    "Dynamic Profile Parent Name": "SSH Base",
    "Custom Command": "Yes",
    "Command": "ssh $HOST",
    "Badge Text": "$HOST",
    "Tags": ["ssh", "auto-generated"]
  }
EOF
done

echo ']}' >> "$OUTPUT"
echo "Generated profiles for $(grep '^Host ' ~/.ssh/config | grep -v '\*' | wc -l | tr -d ' ') hosts"
// advertisement

Performance Tuning for High-Output Streams

iTerm2 can lag when processes write output faster than the renderer can display it - common when tailing high-volume application logs or running kubectl logs on busy pods. Three settings address this.

First, enable GPU rendering: Preferences > General > GPU Rendering. On Apple Silicon, this is enabled by default in 3.5.x. On Intel Macs, verify it is active. With GPU rendering off, high-output streams drop frames visibly.

Second, adjust the scrollback buffer. The default 1000 lines is insufficient for debugging. We set it to 50000 lines under Preferences > Profiles > Terminal > Scrollback Lines. For unlimited scrollback, check 'Unlimited scrollback', but be aware this grows memory linearly with output - a process writing 10MB/s of logs will consume significant RAM in a few minutes.

Third, if you are specifically tailing high-volume logs and do not need interactivity, pipe through less or use a dedicated log viewer rather than raw tail. iTerm2 renders every character; a pager renders only the visible window. For DevOps automation workflows where you are processing log streams programmatically, routing output through tools like taskbotshub.ai's log ingestion pipeline offloads the rendering burden entirely and gives you searchable, indexed output.

# Check if GPU renderer is active (check Console.app or iTerm2 log)
# iTerm2 > Help > Reveal Log File in Finder
# grep for 'GPU' in the log

# For high-volume log tailing without buffer overflow
# Use a bounded buffer with stdbuf
ssh host 'stdbuf -oL tail -f /var/log/app/production.log' | grep --line-buffered 'ERROR\|WARN'

# Set scrollback in your dotfiles plist
defaults write com.googlecode.iterm2 'Unlimited Scrollback' -bool false
defaults write com.googlecode.iterm2 'Scrollback Lines' -int 50000

# Measure iTerm2 render throughput (rough test)
time cat /dev/urandom | base64 | head -c 10M > /dev/null
# Compare with output to iTerm2 pane vs. piped to /dev/null
# The difference is your render overhead