Typing ssh -p 2222 [email protected] once is fine. Typing it twenty times a day, next to other servers with their own ports and user names, is where mistakes start. An ssh config alias stores all of that under one short name, so the command becomes ssh web. Nothing runs in the background: OpenSSH reads ~/.ssh/config every time it starts.
Every ssh config file example below was resolved with OpenSSH 9.6p1 using ssh -G, which prints the settings ssh would use without connecting anywhere.
SSH config generator: build a Host block
Fill in the alias and the connection details, and the generator writes the matching block for ~/.ssh/config, together with the long command it replaces, the short one, an scp line and the ssh -G check to run before the first connection. Port 22 is left out, since it is the default. Everything happens in the browser; nothing you type is sent anywhere.
The second part checks a file mode against the rules behind "Bad owner or permissions", tested on a real file with OpenSSH 9.6p1 and 10.0p2. ssh refuses ~/.ssh/config only when someone else can write it, but ignores a private key if the group or others hold any permission bit at all.
SSH config generator
runs in your browser · nothing is sent- Replaces this command
- Connect
- Copy a file
- Check before connecting
Will ssh accept the file?
An ssh config file example with Host, HostName, User and Port
Create the file with the mode ssh expects:
$ touch ~/.ssh/config
$ chmod 600 ~/.ssh/configIn this ssh config file example, each entry starts with a Host line naming the alias. The indented lines below it apply until the next Host line; ssh ignores the indentation itself.
Host web
HostName 203.0.113.10
User alice
Port 2222HostName is the real address, either an IP or a DNS name. User and Port are what you would otherwise type with user@ and -p. Now ssh web does the same as the long command, and ssh -G confirms it:
$ ssh -G web | grep -E '^(hostname|user|port) '
user alice
hostname 203.0.113.10
port 2222Keywords are case-insensitive, and ssh -G prints them in lower case. The alias is not: ssh -G WEB matched no block, fell back to port 22 and the local user name, and used the typed name itself, lowercased to web, as the address.

SSH config IdentityFile: one key per host
Without an ssh config IdentityFile line, ssh tries a list of default key names. In the ssh -G output for web, OpenSSH 9.6 listed ~/.ssh/id_rsa, id_ecdsa, id_ecdsa_sk, id_ed25519, id_ed25519_sk, id_xmss and id_dsa. Naming the key saves those attempts.
The classic case is two GitHub accounts, personal and work, each with its own key. GitHub identifies the account by the key, so a second ssh config alias that points at the same HostName does the job:
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes$ ssh -G github-work | grep -E '^(hostname|user|identityfile|identitiesonly) '
user git
hostname github.com
identitiesonly yes
identityfile ~/.ssh/id_ed25519_workLeave out IdentitiesOnly yes and the default no applies: ssh then also offers every key loaded in ssh-agent. If the agent holds the personal key and offers it first, GitHub accepts it and the push lands in the wrong account. Servers also stop accepting attempts after a limit, so an agent full of keys can end in "Too many authentication failures" before the right key is tried.
The git remote URL uses the alias
Git only needs the ssh alias host name in place of github.com:
$ git clone git@github-work:example-org/app.git
$ git remote set-url origin git@github-work:example-org/app.gitGit hands git@github-work to ssh unchanged (the table in the scp section shows the exact arguments), and ssh does the lookup. If Git itself is still new, the Githug practice game walks through remotes and pushes level by level.
Wildcard host patterns and Host * defaults
Host accepts patterns. * matches any run of characters and ? exactly one, so one block can cover a whole group of machines:
Host *.example.com
User deploy
Port 2200
Host *
ServerAliveInterval 60
AddKeysToAgent yes$ ssh -G app1.example.com | grep -E '^(hostname|user|port|serveraliveinterval) '
user deploy
hostname app1.example.com
port 2200
serveraliveinterval 60Without a HostName, the name you typed is the address. Host * matches everything and holds defaults such as a keep-alive every 60 seconds.
Host order: the first match wins
ssh reads the file from the top, and for each option it keeps the first value it finds. Later blocks can add options that are still unset, but they cannot change one that is already set. The ssh_config manual draws the conclusion itself: host-specific declarations near the beginning, general defaults at the end.
A two-block test file shows the effect:
Host *
User fallback
Host web
HostName 203.0.113.10
User alice$ ssh -G -F order.conf web | grep -E '^(user|hostname) '
user fallback
hostname 203.0.113.10HostName from the web block still applies, because Host * did not set one. User alice is silently ignored. Command-line options sit above the whole file: ssh -G -l bob -p 22 web reported user bob and port 22.
ProxyJump: reach a host behind another
A database server that only accepts connections from the web server can still get its own ssh alias host entry. ProxyJump names the hop, and it can be another alias:
Host db
HostName 10.0.0.5
User postgres
ProxyJump web$ ssh -G db | grep -E '^(hostname|user|proxyjump) '
user postgres
hostname 10.0.0.5
proxyjump webssh db then logs in to web (port 2222, user alice) and opens a forwarded connection to 10.0.0.5 from there. Several hops are separated by commas.

scp with ssh config alias, rsync and other tools
Every tool that runs the ssh binary picks up an ssh config alias for free, so scp with ssh config alias names needs no extra setup. With a stand-in for ssh that prints its arguments, the three common cases looked like this:
| command | what ssh received |
|---|---|
scp notes.txt web:/tmp/ | -x ... -s -- web sftp |
rsync -a conf.d web:backup/ | web rsync --server ... |
git ls-remote git@github-work:example-org/app.git | git@github-work git-upload-pack ... |
Since OpenSSH 9.0, scp transfers over SFTP by default, which is why ssh received -s ... sftp; the alias works the same with the legacy protocol. GUI clients and libraries with their own SSH code are a different matter, and many never read ~/.ssh/config.
Permissions, Include and debugging ~/.ssh/config
chmod 600 ~/.ssh/config is the mode to use. If others can write the file, ssh refuses it with "Bad owner or permissions", and every alias fails at once.
Long configs can be split with Include:
Include ~/.ssh/config.d/*.conf
Host web
HostName 203.0.113.10In the test, a lab alias defined only in an included file resolved normally. Keep the Include above the first Host line; written further down, it belongs to the block above it and is read only when that block matches.
One edge case ssh -G db does not reveal: according to the ssh manual, command-line options such as ssh -i other_key db apply to the destination, not to the jump host. The hop to web still follows the web block, so a jump host that needs a special key has to get it in the file.
