| Tested on | RHEL 10.2 (kernel 6.12) |
|---|---|
| Package | openssh-clients-9.9p1-25.el10_2.x86_64 |
| Applies to | Any Linux host with OpenSSH client (openssh-client, openssh-clients) |
| Privilege | Normal user |
| Scope | OpenSSH client configuration in ~/.ssh/config and ssh_config—host aliases, keys, jump hosts, and ssh -G verification. Server hardening in sshd_config, cipher tuning, and general ssh usage are out of scope here. |
| Related guides | Fail2ban for SSH SSH SOCKS proxy Kill hung SSH sessions Reverse SSH port forwarding Fix SSH no route to host |
Stop retyping ssh -i … -p … user@long-hostname.example.com for every server. The OpenSSH client reads ~/.ssh/config and applies Host blocks automatically when you run ssh web or ssh internal. This walkthrough builds that file step by step: aliases, keys, wildcards, bastions, includes, and a quick way to print what SSH actually picked.
Quick reference: SSH config examples
| Task | Example Host block |
|---|---|
| Short alias | Host web → HostName server.example.com, User deploy, Port 22 |
| Private key | IdentityFile ~/.ssh/id_ed25519_web plus IdentitiesOnly yes |
| Jump host | ProxyJump bastion or ProxyJump user@bastion.example.com:22 |
| Wildcard defaults | Host *.example.com with shared User or IdentityFile |
| Split files | Include ~/.ssh/config.d/*.conf — order specific stanzas before defaults |
| Reuse one TCP session | ControlMaster auto, ControlPath, ControlPersist on Host * |
| Inspect effective config | ssh -G alias (no connection) |
| Check precedence | ssh -p 2222 -G alias — CLI flags override Port in config |
What is ~/.ssh/config?
Linux has two SSH configuration families that are easy to mix up:
- Client —
ssh,scp, andsftpread~/.ssh/config(per user) and/etc/ssh/ssh_config(system-wide). Keywords are documented inssh_config(5). - Server —
sshdreads/etc/ssh/sshd_config(sshd_config(5)). That file controls who may log in, not your personal shortcuts. For server-side authentication policy, see OpenSSH authentication and sshd_config.
When you run ssh web, the client merges settings from the command line, your user
file, and the system file. The next section lists that order and the exceptions
(Include, Match, and keywords that accumulate).
Modern OpenSSH defaults already prefer strong ciphers and MACs. This article does not pin legacy CBC or 3DES lists in client config; leave algorithm policy to defaults or to server-side sshd_config when you harden hosts.
Configuration precedence and where settings come from
OpenSSH documents client precedence in ssh_config(5) in three layers. For most
keywords, the highest layer that sets a value wins. Inside a single file,
the first obtained value wins as matching Host and Match stanzas are
read from top to bottom.
Three layers (highest priority first)
| Priority | Source | Examples |
|---|---|---|
| 1 | Command line | ssh -p 2222 web, ssh -i ~/.ssh/key, ssh -o User=admin |
| 2 | User config | ~/.ssh/config |
| 3 | System config | /etc/ssh/ssh_config |
A Port line in ~/.ssh/config does not override ssh -p 2222. Command-line
-o Keyword=value options sit in the same top layer as -p, -i, and -l.
First match inside a config file
Walk the file from the first line downward. Each Host pattern that matches the
name you typed (or passed to scp / sftp) can contribute settings. For most
keywords, OpenSSH keeps the first value obtained and ignores later duplicates
from other matching stanzas.
Put specific hosts above broad catch-alls:
Host webbeforeHost *- Negated patterns (
!host) on the sameHostline as a positive pattern that matches first
Match stanzas add another gate: declarations under Match apply only when
every criterion on the Match line is true (user, host, localnetwork,
exec, and others documented in ssh_config(5)). The same first-obtained rule
applies inside an active Match block.
When CanonicalizeHostname is enabled, OpenSSH may evaluate Match during
canonicalization and again against the final hostname. Treat Match as
conditional overlays, not a separate precedence layer above the command line.
Keywords that accumulate instead of stopping at the first line
Some directives append values rather than obeying first-wins:
IdentityFileandCertificateFile— each line adds another key or certificate to try, in orderLocalForward,RemoteForward, andDynamicForward— each line adds another forward
Use IdentitiesOnly yes when you want one IdentityFile and no extra keys from
agent or default paths.
Other ways settings enter the mix
| Method | What happens |
|---|---|
Include ~/.ssh/config.d/*.conf |
Files are read at that line; globs expand in lexical order; first obtained value wins—put specific stanzas before defaults in the parent file and in each included file |
Include inside Host or Match |
Snippet applies only when that block matches |
ssh -F /path/to/alternate |
Replaces ~/.ssh/config; /etc/ssh/ssh_config is not read |
ssh -F none |
Skips all configuration files |
| Environment in paths | Include and path arguments can expand ~, tokens such as %h, and environment variables per ssh_config(5) |
Relative paths in a user config file resolve under ~/.ssh/. Paths in the
system file resolve under /etc/ssh/.
Verify precedence without connecting
Use the same flags you plan at login time. ssh -G prints the merged client
configuration after Host, Match, and Include processing:
ssh -p 2222 -G localhostFilter the port line to confirm the command-line flag overrode config defaults:
ssh -p 2222 -G localhost | grep '^port 'Sample output:
port 2222port 2222 with -p 2222 on the command line shows CLI precedence even when
a Host block sets a different Port.
Create ~/.ssh/config and set permissions
~/.ssh/config must not be writable by other users. Mode 600 is a simple,
recommended choice. The 700 and 600 modes match the owner-only pattern for
sensitive paths in Linux file permissions.
mkdir -p ~/.ssh && chmod 700 ~/.sshchmod on an existing ~/.ssh directory exits silently when permissions are already correct.
Create an empty config file and restrict it to your user:
touch ~/.ssh/config && chmod 600 ~/.ssh/configConfirm the mode bits before you add host blocks:
ls -la ~/.ssh/configSample output:
-rw-------. 1 user user 0 Aug 15 19:16 /home/user/.ssh/configThe leading -rw------- is what you want: only your account can read or write the file.
Create a host alias
A Host line defines the alias you type on the command line. HostName is the real DNS name or IP—when names fail to resolve, check Linux hostname resolution before blaming SSH. Optional User and Port replace user@ and -p on every connect.
Add a block like this to ~/.ssh/config:
Host web
HostName server.example.com
User deploy
Port 22After you save the file, ask OpenSSH what it would use for the alias (no network connection):
ssh -G webSample output (trimmed):
host web
user deploy
hostname server.example.com
port 22The hostname and user lines confirm the alias maps to deploy@server.example.com on port 22, so ssh web is enough at the shell.
Use a different SSH key with IdentityFile
Servers that see many keys from your account may reject the connection before you reach the right one. Point SSH at one private key and stop offering the rest with IdentitiesOnly yes. Create the matching key with ssh-keygen first; see generate SSH keys on Linux for ed25519 examples.
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yesPrint the key SSH selected for that host:
ssh -G github.comSample output:
user git
hostname github.com
identitiesonly yes
identityfile ~/.ssh/id_ed25519_githubWhen identityfile lists only the path you set, SSH will not offer every key in ~/.ssh/ during authentication.
Configure multiple SSH hosts
Stack independent Host sections in one file. Each block applies only when its pattern matches the name you pass to ssh.
Host web
HostName web.example.com
User deploy
Host db
HostName db.internal.example.com
User admin
Port 2222
Host monitor
HostName 10.0.0.50
User root
IdentityFile ~/.ssh/id_ed25519_opsVerify one alias at a time so you can see which block matched:
ssh -G dbSample output:
user admin
hostname db.internal.example.com
port 2222The port 2222 line shows this block overrode the default SSH port without putting -p on the command line.
Use wildcards and negated host patterns
Host patterns support * and ? glob characters. A leading ! excludes a
host from the stanza. A negated pattern does not match anything by itself —
OpenSSH requires a positive pattern on the same Host line.
Host *.lab.example.com
User labadmin
IdentityFile ~/.ssh/id_ed25519_lab
Host *.lab.example.com !bastion.lab.example.com
ProxyJump bastion.lab.example.com
Host bastion.lab.example.com
HostName bastion.lab.example.com
User jumpHost *.lab.example.com !bastion.lab.example.com matches lab hosts except the
bastion. The first block still applies labadmin and the lab key to every name
under *.lab.example.com, including the bastion. The third block adds
HostName and User jump for the bastion hostname; keywords already set by an
earlier matching stanza are not replaced.
Check an internal lab host (not the bastion):
ssh -G app1.lab.example.comSample output:
user labadmin
hostname app1.lab.example.com
identityfile ~/.ssh/id_ed25519_lab
proxyjump bastion.lab.example.comproxyjump on app1.lab.example.com confirms the combined positive-and-negated
pattern applied the jump rule without affecting the bastion itself.
Connect through a jump host with ProxyJump
ProxyJump (OpenSSH 7.3+) opens SSH to a bastion first, then forwards the session to the target. It replaces the older ProxyCommand nc pattern for most bastion setups.
Host bastion
HostName bastion.example.com
User jumpuser
Host internal
HostName 10.0.0.20
User appuser
ProxyJump bastionInspect the jump setting without logging in:
ssh -G internalSample output:
user appuser
hostname 10.0.0.20
proxyjump bastionproxyjump bastion means SSH resolves the bastion Host block and chains through it before reaching 10.0.0.20. You can also write ProxyJump jumpuser@bastion.example.com when you do not need a separate alias.
Split SSH configuration with Include
Large configs stay readable when you split them. Include is processed where it
appears in the file—same first-obtained rule as any other stanza. Put
more-specific Host blocks before broader defaults in the parent file and
inside each file under config.d/.
Include ~/.ssh/config.d/*.conf
Host web
HostName web.example.comCreate a snippet file such as ~/.ssh/config.d/work.conf:
Host work-*
User deploy
Port 2222Paths without a leading / or ~ are resolved under ~/.ssh/ when they appear
in a user config file. Wildcards in the Include path expand in lexical order.
Confirm an included pattern:
ssh -G work-apiSample output:
user deploy
hostname work-api
port 2222user deploy and port 2222 came from the included file. If a later stanza in
the parent file tried to change User for work-api, OpenSSH would keep the
first value from the included block.
Reuse connections with ControlMaster and ControlPersist
Multiplexing keeps one TCP connection open and attaches later ssh sessions to it. That cuts repeated handshakes when you run many commands to the same host.
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%C
ControlPersist 10mControlPersist 10m keeps the master socket for ten minutes after the last
session closes. %C hashes connection attributes into the socket name, which
avoids long paths when hostnames or usernames are lengthy. OpenSSH also accepts
%r, %h, and %p in ControlPath if you prefer readable socket names.
See whether the options applied to a target:
ssh -G localhostSample output:
controlmaster auto
controlpath /home/user/.ssh/cm-b3edda29e4f0202401412aeb64ff97c3c2b8df60
controlpersist 600controlpersist 600 is ten minutes in seconds. Multiplexing is convenient on trusted networks; for idle disconnects without sharing sockets, see keep alive SSH sessions (ServerAliveInterval). Disable multiplexing (ControlMaster no) when connection sharing is not acceptable for your policy.
Check the effective SSH configuration with ssh -G
When a host picks the wrong key or ignores your port, print the merged configuration instead of guessing. ssh -G hostname evaluates Host, Match, and Include processing without opening a connection.
Filter the lines you care about:
ssh -G web | grep -E '^(hostname|user|port|identityfile|proxyjump|identitiesonly)'Sample output:
user deploy
hostname server.example.com
port 22Compare that to ssh -v web when you need a live handshake trace; -G is the fast check for static client keywords. It does not contact the server or prove your key is in authorized_keys on the remote side. For -v, port forwarding, and jump-host flags on the command line, see the ssh command.
Common SSH config problems
When Permission denied (publickey) persists after you set IdentityFile, the fault is often on the server key install or sshd policy—see SSH key authentication for authorized_keys, permissions, and pubkey troubleshooting.
| Symptom | Likely cause | Fix |
|---|---|---|
| Alias ignored; defaults used | Host * or broad wildcard listed before the specific Host |
Move specific blocks above catch-alls; run ssh -G alias |
| Config ignored entirely | ssh -F /path skips /etc/ssh/ssh_config; ssh -F none skips all files |
Drop -F to restore defaults, or copy needed system keywords into your -F file |
| Wrong username or port | First matching Host block already set User or Port |
Reorder blocks or rename the alias; first obtained value wins |
-p or -i seems ignored |
Unlikely—CLI beats config; check you are not wrapping ssh in a script that omits flags |
Re-run with ssh -p … -G host; compare to ssh -G host without flags |
Permission denied (publickey) with many keys |
SSH offers every key in ~/.ssh/ |
Set IdentityFile and IdentitiesOnly yes on that Host |
Bad owner or permissions on ~/.ssh/config |
File or directory writable by group or others | chmod 700 ~/.ssh and chmod 600 ~/.ssh/config |
ProxyJump fails or loops |
Bastion alias missing or wrong HostName |
Define Host bastion first; verify with ssh -G internal |
Include snippet ignored |
Wrong path or glob; relative path not under ~/.ssh/ |
Use Include ~/.ssh/config.d/*.conf; check glob expansion |
| Included value cannot be overridden | First obtained value already set in an earlier stanza or included file | Put specific Host blocks before defaults in every file; reorder Include |
| Multiplex socket errors | Stale ControlPath after crash or UID change |
Remove ~/.ssh/cm-* sockets or set ControlMaster no for that host |
References
Summary
~/.ssh/config is the OpenSSH client shortcut file. Each Host block turns a short name into HostName, User, Port, and key paths so daily ssh commands stay short. You practiced aliases, per-host keys with IdentitiesOnly, wildcards with negation, and ProxyJump for bastion access without embedding ProxyCommand shell.
Precedence runs in three layers: command line, then ~/.ssh/config, then /etc/ssh/ssh_config. Inside a file, most keywords keep the first value obtained from matching Host and Match stanzas read top to bottom; IdentityFile and port-forward directives are exceptions that accumulate. Broad Host * stanzas belong near the bottom, and split files under Include ~/.ssh/config.d/*.conf need the same specific-before-general ordering. When something looks wrong before you connect, ssh -G hostname (with the same -p, -i, or -F flags you use live) shows the merged client view faster than trial and error.
Connection multiplexing with ControlMaster and ControlPersist speeds up repeated sessions to the same machine; turn it off when shared sockets are not allowed. Server authentication policy lives in sshd_config, not in manually pinned client cipher lists. Next, copy files with the scp command using the same host aliases, or open tunnels with SSH port forwarding when config keywords are not enough.

