By default, zsh's SSH completion has several shortcomings when it comes to practical usage:
- Ignores
~/.ssh/config
host definitions - Requires username specification before host completion
- Presents fragmented completion lists from different sources
Add this to your ~/.zshrc
to enable better completion:
# Improved SSH completion setup
zstyle ':completion:*:(ssh|scp|sftp):*' hosts off
zstyle ':completion:*:(ssh|scp|sftp):*' users off
_ssh_config_hosts() {
local configfile
local -a hosts
for configfile in ~/.ssh/config(N) /etc/ssh/ssh_config(N); do
[ -r $configfile ] && hosts+=(${${${(@M)${(f)"$(< $configfile)"}:#Host *}#Host }:#*[*?]*})
done
print -u2 -l $hosts
}
zstyle -e ':completion:*:(ssh|scp|sftp):*' hosts 'reply=(
${=${${(f)"$(_ssh_config_hosts 2>/dev/null)"}:#*[*?]*}}
${=${${${(f)"$(cat ~/.ssh/known_hosts{,2} /etc/hosts 2>/dev/null)"}%%\ *}%%,*}
)'
This solution combines multiple host sources:
- Parses
~/.ssh/config
and/etc/ssh/ssh_config
forHost
entries - Includes
known_hosts
(andknown_hosts2
if exists) - Incorporates system
/etc/hosts
entries
For username completion from config:
zstyle ':completion:*:(ssh|scp|sftp):*' users \
$(awk '/^Host / {print $2}' ~/.ssh/config | xargs -n1 ssh -G 2>/dev/null | awk '/^user / {print $2}')
After reloading your zsh (exec zsh
), test with:
ssh [TAB][TAB]
You should now see a unified list of all available hosts.
When working with ZSH's default SSH completion, you'll notice several pain points:
# Current behavior example:
$ ssh [TAB]
user1@
user2@
# Then need to press TAB again to see hosts
Key issues include:
- No integration with ~/.ssh/config Host entries
- Forces username-first completion
- Separates host sources into multiple lists
We want a unified completion that:
- Reads Host entries from ~/.ssh/config
- Includes known_hosts entries
- Incorporates /etc/hosts aliases
- Shows usernames as optional suggestions
- Displays everything in one list
Add this to your .zshrc:
# Enhanced SSH completion for ZSH
zstyle ':completion:*:(ssh|scp|sftp):*' hosts \
${${${(@M)${(f)"$(<~/.ssh/config)"}:#Host *}#Host }:#*[*?]*}
zstyle ':completion:*:(ssh|scp|sftp):*' users \
$(awk '/^Host / {print $2}' ~/.ssh/config | sort -u)
zstyle ':completion:*:(ssh|scp|sftp):*' known-hosts-files \
~/.ssh/known_hosts /etc/ssh/ssh_known_hosts
# Combine all sources
zstyle ':completion:*:(ssh|scp|sftp):*' tag-order \
'hosts:-host hosts:-user:host users hosts:-domain hosts:-ipaddr' \
'hosts:local-host hosts:local-domain hosts:remote-host hosts:remote-domain'
The configuration works by:
1. Extracting Host entries from SSH config:
${${${(@M)${(f)"$(<~/.ssh/config)"}:#Host *}#Host }:#*[*?]*}
2. Adding known_hosts files:
~/.ssh/known_hosts and system-wide /etc/ssh/ssh_known_hosts
3. Merging completion sources with proper ordering:
tag-order controls the display sequence
For power users, consider adding:
# Include /etc/hosts entries
local -a hosts
hosts=($(awk '/^[^#]/ {print $2}' /etc/hosts))
zstyle ':completion:*:hosts' hosts $hosts
# Add AWS/GCP host completion (if using those clouds)
if [[ -f ~/.aws/config ]]; then
zstyle ':completion:*:hosts' aws \
$(aws ec2 describe-instances --query 'Reservations[].Instances[].Tags[?Key==Name].Value[]' --output text)
fi
After reloading your shell (exec zsh), test with:
$ ssh [TAB]
# Should now show combined list including:
# - Host entries from ~/.ssh/config
# - Entries from known_hosts
# - /etc/hosts aliases
# - Usernames (optional)