Skip to main content

ssh-config-deep-dive


title: "SSH Config Deep Dive — Mastering ~/.ssh/config" date: 2026-05-05 difficulty: intermediate-to-advanced tags: [ssh, ssh-config, remote-access, linux, macos, networking, security, hpc]

Overview

The ~/.ssh/config file is one of the most powerful tools in a remote user's arsenal. It's a simple text file where you define named SSH hosts, their connection parameters, identity files, and behaviors—so instead of typing ssh -i ~/.ssh/special_key -p 2222 -l deploy user.example.com, you just type ssh myhost and OpenSSH reads your config to handle all the details.

This is more than a shortcut. A well-crafted config transforms SSH from a command-line chore into a seamless experience. It enables connection multiplexing (reusing connections), intelligent key selection across multiple accounts, jump-host chains, dynamic forwarding, and graceful timeouts. For power users—developers juggling GitHub accounts, sysadmins managing clusters, DevOps engineers orchestrating infrastructure with Ansible, or HPC researchers—a thoughtful config is non-negotiable.

This reference is the deep dive companion to [[ssh-tutorial|SSH Tutorial]], which covers SSH basics. Here we focus on config file mastery: parsing rules, every commonly-used directive, real-world recipes, and troubleshooting.

Prerequisites

  • SSH basics: familiarity with key pairs, -i flag, port redirection. See [[ssh-tutorial|SSH Tutorial]] if needed.
  • A working SSH client: OpenSSH 7.3+. Check with ssh -V.
  • At least one remote host to test against (a personal server, GitHub account, or cloud instance).
  • Text editor: vim, nano, VS Code, or your preference.
  • File permissions knowledge: understanding of chmod and file modes (see [[linux-permissions-beginner-guide|Linux Permissions Beginner Guide]]).

Key Concepts

First-Match-Wins Parsing

When OpenSSH processes ~/.ssh/config, it reads directives top to bottom and applies the first value it finds for each directive. If you define User alice in a Host block and later define User bob in another block that also matches, OpenSSH uses alice. This is crucial: order matters. Put specific hosts near the top, general patterns (like Host *) at the bottom.

Exception: Some directives (like IdentityFile, LocalForward, RemoteForward) can appear multiple times and are all used; OpenSSH tries each one in order.

Host Blocks vs Match Blocks

Host blocks match against the alias you type on the command line. They're the standard way to define named hosts.

Match blocks are more powerful but less commonly used. They match against criteria like the target hostname, the executing user, or output of an exec command. Use them when you need conditional logic beyond simple name matching.

Example:

Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work

Match host github.com user git
AddKeysToAgent yes

How Command-Line Flags Interact with Config

Command-line arguments override config file directives. For example:

ssh -i ~/.ssh/other_key myhost

will override the IdentityFile from ~/.ssh/config for myhost. This is useful for one-off scenarios but dangerous if you're not paying attention—it's easy to accidentally leak a connection attempt with the wrong key.

File Structure and Parsing Rules

Basic Anatomy

Host alias1
HostName hostname1.example.com
User myuser
Port 2222

Host alias2
HostName hostname2.example.com
User otheruser
IdentityFile ~/.ssh/special_key

Pattern Matching in Host Blocks

Host patterns support wildcards:

  • * matches zero or more characters
  • ? matches exactly one character
  • ! prefix negates a pattern

Examples:

Host github-*
HostName github.com
User git

Host *.example.com
Port 2222
User deploy

Host !*.internal
StrictHostKeyChecking accept-new

Match Blocks and Criteria

Match blocks use criteria like:

  • host — target hostname
  • user — logged-in username
  • exec — result of a shell command (exit 0 = true)
  • originalhost — what you typed on the command line
  • final — rarely used; matches after config is fully parsed

Example:

Match host github.com exec "[ -f ~/.ssh/github_work ]"
User git
IdentityFile ~/.ssh/id_ed25519_work

This uses your work key only if the file ~/.ssh/github_work exists—a neat way to switch identities without editing config.

Include Directives

Break up a large config into multiple files:

Include ~/.ssh/config.d/personal
Include ~/.ssh/config.d/work
Include ~/.ssh/config.d/hpc

Host *
ServerAliveInterval 60

Each file is parsed in order. This keeps credentials and hostnames organized.

File Permissions: Must Be 600

chmod 600 ~/.ssh/config

OpenSSH refuses to read a config file that's world-readable (or group-writable). It's a security boundary. See [[linux-permissions-beginner-guide|Linux Permissions Beginner Guide]] for deeper explanation.

⚠️ Warning: If you get "Bad permissions on config file," immediately check with ls -l ~/.ssh/config. Many tools and scripts accidentally relax permissions. Always reset to 600.

Most-Used Directives: Reference Table

DirectiveWhat It DoesExampleNotes
HostNameActual hostname/IP to connect toHostName server.example.comRequired for each Host block; can differ from the alias
UserRemote usernameUser deployOverrides ~/.ssh/config's global User or ssh default (your local username)
PortSSH port on remotePort 2222Defaults to 22
IdentityFilePrivate key fileIdentityFile ~/.ssh/id_ed25519Tried in order listed; multiple entries allowed. SSH tries each key until one works
IdentitiesOnlyOnly use listed IdentityFile(s)?IdentitiesOnly yesIf yes, SSH ignores default keys and agent keys. Critical for multi-account setups
ProxyJumpJump host (bastion)ProxyJump bastionModern, simple replacement for ProxyCommand. Alias must be defined earlier in config
ProxyCommandCustom command to establish tunnelProxyCommand ssh -q -W %h:%p bastionPowerful but verbose; prefer ProxyJump unless you need special logic
ForwardAgentPass SSH agent to remote?ForwardAgent yesRisky on untrusted servers; agent-forwarded keys can be abused
AddKeysToAgentAdd keys to agent on first use?AddKeysToAgent yesConvenient; agent remembers key passphrase so you type it once
UseKeychainmacOS: use system keychain?UseKeychain yesStores passphrases in Keychain; macOS only
ServerAliveIntervalSend keep-alive every N secondsServerAliveInterval 60Prevents timeouts on idle connections; 0 = disabled
ServerAliveCountMaxMiss N keep-alives before exit?ServerAliveCountMax 3Session dies after Interval × CountMax seconds of no response
ControlMasterEnable connection multiplexing?ControlMaster autoValues: no, yes, ask, auto. Auto = create master if none exists
ControlPathSocket file for multiplexed sessionsControlPath ~/.ssh/sockets/%r@%h-%pTokens: %r = remote user, %h = target host, %p = port. Create sockets dir first
ControlPersistKeep master alive for N secondsControlPersist 600After last client exits, master persists (huge speed boost for loops)
LocalForwardForward local port → remoteLocalForward 5432 db.internal:5432Syntax: LocalForward local_port remote_host:remote_port. Can list multiple times
RemoteForwardForward remote port → localRemoteForward 8080 localhost:3000Reverse tunnel; useful for exposing local dev server to remote
DynamicForwardSOCKS5 proxy via SSHDynamicForward 1080Turn SSH connection into SOCKS proxy for all traffic; great for privacy on untrusted networks
StrictHostKeyCheckingAccept unknown host keys?StrictHostKeyChecking accept-newValues: yes, no, accept-new. accept-new = safe default (accept first-time keys, reject changed ones)
UserKnownHostsFileAlternative known_hosts fileUserKnownHostsFile ~/.ssh/known_hostsRarely changed; defaults to ~/.ssh/known_hosts
PreferredAuthenticationsAuth methods to try (and order)PreferredAuthentications publickey,passwordComma-separated list; SSH tries in order. Usually keep pubkey first
PubkeyAcceptedAlgorithmsAllowed public key algorithmsPubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512Restrict to strong algorithms (ed25519 preferred). Filter weak ones out
RequestTTYAllocate terminal?RequestTTY yesValues: yes, no, force, auto. Needed for interactive shells
RemoteCommandCommand to run on connectRemoteCommand "cd /data && bash"Useful for jump hosts + command chaining
LogLevelVerbosity of SSH logsLogLevel DEBUGValues: QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, DEBUG3

💡 Tip: Use ssh -G hostname to see the effective resolved configuration for a host. This is invaluable for debugging.

Recipes / Patterns

Pattern 1: Per-Project Identity (GitHub / GitLab Multi-Account)

You have a personal GitHub account and a work GitHub account, each with its own key:

# Personal GitHub
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes

# Work GitHub
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes

When you clone a repo from work, use:

git clone git@github-work:company/repo.git

And set up Git to auto-match:

git config --global url."git@github-work:".insteadOf "git@github.com:"

Or configure per-directory with .git/config:

[url "git@github-work:"]
insteadOf = git@github.com:

Why IdentitiesOnly yes? Without it, OpenSSH tries your agent's keys first, which can be wrong. With it, only the listed IdentityFile is used—fast and predictable.

Pattern 2: Bastion / Jump Host with Downstream Targets

You have a bastion (jump) host; downstream servers only accept SSH from the bastion:

Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_bastion

Host internal-db
HostName db.internal
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_deploy

Host internal-*
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_deploy

Now ssh internal-db transparently jumps through bastion. Under the hood, it's equivalent to:

ssh -J bastion internal-db

💡 Tip: Chain proxies with comma-separated aliases: ProxyJump bastion,internal-vpn. Handy for multi-hop topologies.

Pattern 3: Connection Multiplexing for Speed

Connection multiplexing is a game-changer for Ansible, rsync loops, and repeated SSH commands. The first SSH connection to a host opens a "master" socket; subsequent connections reuse it instead of negotiating new ones. In practice: 10x to 100x faster on high-latency networks.

Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600

Set it up:

mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets

Test it:

# First connection (slow, ~3-5s on remote server)
ssh myhost hostname

# Second connection (instant, <100ms)
ssh myhost hostname

# Master socket persists for 600s, so repeated commands are fast

Check active sockets:

ls -la ~/.ssh/sockets/

Pattern 4: Wildcard Defaults at the Bottom

Put a Host * block at the very end to set defaults for all hosts:

Host github-personal
...

Host bastion
...

Host *
ServerAliveInterval 60
ServerAliveCountMax 3
IdentitiesOnly no
AddKeysToAgent yes
UseKeychain yes
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600

Because of first-match-wins, the Host * directives apply only if they weren't defined earlier. This is how you set global defaults without repeating them in every block.

Pattern 5: HPC Cluster Login Alias

SSH to an HPC cluster often requires a specific key, a non-standard port, and aggressive keep-alives (cluster terminals time out quickly):

Host hpc-cluster
HostName hpc.university.edu
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519_hpc
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 10
RequestTTY yes

Now ssh hpc-cluster drops you into a robust shell on the HPC login node.

Pattern 6: Dynamic Forwarding (SOCKS Proxy)

Turn your SSH connection into a SOCKS5 proxy to route all traffic through a remote server (useful for privacy on untrusted WiFi):

Host socks-proxy
HostName proxy.example.com
User alice
DynamicForward 1080
ServerAliveInterval 60

Connect:

ssh -f -N socks-proxy # -f = background, -N = no command

Then configure your browser or app to use SOCKS5 proxy on localhost:1080. All traffic routes through the remote server.

Hands-On Exercises

Exercise 1: Create a Named Alias

  1. Edit ~/.ssh/config and add a new host (use a server you have access to):
Host my-test-server
HostName server.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519
  1. Test it:
ssh my-test-server
  1. Verify the config was applied:
ssh -G my-test-server | head -20

Expected outcome: You connect without typing the full hostname or -i flag.

Exercise 2: Set Up Connection Multiplexing and Benchmark

  1. Create the sockets directory:
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets
  1. Add multiplexing to your config (e.g., under Host *):
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
  1. Benchmark (pick any remote host):
time ssh myhost hostname
time ssh myhost hostname
time ssh myhost hostname

Expected outcome: First connection takes ~3-5s; second and third are instant (< 100ms).

Exercise 3: Configure a Jump Host Chain

If you have a bastion and an internal server:

  1. Define the bastion:
Host bastion
HostName bastion.example.com
User admin
  1. Define the internal target:
Host internal-server
HostName 10.0.0.5
User deploy
ProxyJump bastion
  1. Test:
ssh internal-server

Expected outcome: You're on the internal server without manually jumping through bastion first.

Exercise 4: GitHub Multi-Account Setup

If you have both a personal and work GitHub account:

  1. Create separate keys (if you haven't):
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_personal -C "personal@gmail.com"
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C "work@company.com"
  1. Add both public keys to their respective GitHub accounts.

  2. Configure SSH:

Host github-personal
HostName 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
  1. Clone and test:
git clone git@github-personal:myname/personal-repo.git
git clone git@github-work:company/work-repo.git

Expected outcome: Both clones succeed with the correct keys.

Troubleshooting Config Issues

Check Effective Config with ssh -G

Don't guess—ask SSH what config it actually resolved:

ssh -G myhost

Output:

host myhost
hostname server.example.com
user deploy
port 22
identityfile ~/.ssh/id_ed25519
identitiesonly yes
serveraliveinterval 60
...

If a directive is missing or wrong, you have a config parsing issue.

Common Pitfalls

1. Order of Host Blocks

Remember: first-match-wins. This config is wrong:

Host *
User alice

Host github.com
User git

SSH will use User alice for github.com, not User git. Fix by reordering:

Host github.com
User git

Host *
User alice

2. Forgotten IdentitiesOnly

Without IdentitiesOnly yes, SSH tries your agent's keys first—which might be the wrong one. Always add it for multi-account scenarios:

Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes

3. Permissions Issues

ls -l ~/.ssh/config

Must be 600 (read/write for owner only):

-rw------- 1 alice alice 1234 May 5 10:00 ~/.ssh/config

Fix with:

chmod 600 ~/.ssh/config

Debugging with ssh -vvv

For deep debugging, use verbose mode:

ssh -vvv myhost

Look for lines like:

debug1: Reading configuration data /home/alice/.ssh/config
debug1: Applying options for myhost
debug1: Offering public key: ~/.ssh/id_ed25519 ED25519 sha256:...

This shows which config block matched and which key was offered. If the key is wrong, backtrack to your IdentityFile directive.

See [[ssh-tutorial|SSH Tutorial]] for additional troubleshooting steps and common SSH errors.

Security Considerations

Don't Commit Config with Private Hostnames to Public Repos

Your ~/.ssh/config may contain internal hostnames, IP ranges, or proxy chains that reveal infrastructure. Never commit it to a public GitHub repo. If you must share config, scrub sensitive details:

# Save to a file, remove internal IPs and hostnames
sed 's/10\.0\..*/REDACTED/g' ~/.ssh/config > config-shareable

Be Intentional with StrictHostKeyChecking

StrictHostKeyChecking no is convenient but dangerous:

Host *
StrictHostKeyChecking no # ❌ Not recommended

This accepts any key from any server, including man-in-the-middle attacks. Use only in ephemeral CI/CD environments where you control the infrastructure:

Host ci-runner-*.example.com
StrictHostKeyChecking no # OK in CI, where hostname is ephemeral
UserKnownHostsFile /dev/null

For production and personal use, stick with the default (StrictHostKeyChecking accept-new), which accepts the first key but rejects changed ones:

Host prod-server
HostName prod.example.com
StrictHostKeyChecking accept-new # Safe default

Managing Config with Dotfiles Tools

Your ~/.ssh/config is part of your environment. Consider managing it with:

  • [[chezmoi-beginner-guide|Chezmoi]] — state-based, template-aware dotfiles manager
  • [[dotfiles-beginner-guide|Dotfiles]] — simple version-controlled symlinks

Both let you version-control your config while keeping secrets (passphrases, private keys) out of the repository.

Agent Forwarding Risks

ForwardAgent yes is convenient but risky:

Host work-server
ForwardAgent yes # SSH agent forwarded to remote

An attacker on work-server can abuse your agent to access other servers you can SSH to. Only enable it for truly trusted servers, or avoid it entirely and use ProxyJump instead:

Host internal-db
ProxyJump bastion # Safer than ForwardAgent

Complete Example Config

Here's a realistic, well-commented ~/.ssh/config pulling together multiple patterns:

# ============================================================================
# Personal SSH Config
# Generated: 2026-05-05
# Permissions: chmod 600 ~/.ssh/config
# ============================================================================

# GitHub — Personal Account
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
AddKeysToAgent yes

# GitHub — Work Account
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
AddKeysToAgent yes

# Work Bastion / Jump Host
Host work-bastion
HostName bastion.work.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
ServerAliveInterval 60

# Internal Servers (via Bastion)
Host work-internal-*
ProxyJump work-bastion
User deploy
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes

# Specific internal DB
Host work-db
HostName db.internal
User deploy
ProxyJump work-bastion
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
RemoteCommand "cd /data && bash"

# HPC Cluster
Host hpc
HostName hpc.university.edu
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519_hpc
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 10
RequestTTY yes

# SOCKS Proxy (for privacy on untrusted networks)
Host socks-proxy
HostName proxy.example.com
User alice
DynamicForward 1080
ServerAliveInterval 60

# Defaults for all hosts (at the bottom!)
Host *
# Connection reuse (multiplexing) — huge speed boost for loops
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600

# Keep alive
ServerAliveInterval 60
ServerAliveCountMax 3

# Auth
AddKeysToAgent yes
UseKeychain yes
PreferredAuthentications publickey,password
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

# Security
StrictHostKeyChecking accept-new

# Logging
LogLevel INFO

Setup:

# Create sockets directory for multiplexing
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets

# Set config permissions
chmod 600 ~/.ssh/config

# Verify
ssh -G github-personal | head -10

References

  • [[ssh-tutorial|SSH Tutorial]] — companion beginner guide

  • [[linux-permissions-beginner-guide|Linux Permissions Beginner Guide]] — understanding 600, 700, etc.

  • [[linux-permissions-deep-dive|Linux Permissions Deep Dive]] — deep dive on permissions

  • [[mosh-beginner-guide|Mosh Beginner Guide]] — mobile shell; pairs well with SSH config for unstable connections

  • [[mosh-deep-dive|Mosh Deep Dive]] — advanced Mosh usage

  • [[dotfiles-beginner-guide|Dotfiles Beginner Guide]] — version-controlling your config

  • [[dotfiles-deep-dive|Dotfiles Deep Dive]] — advanced dotfiles patterns

  • [[chezmoi-beginner-guide|Chezmoi Beginner Guide]] — templated dotfiles management

  • [[chezmoi-deep-dive|Chezmoi Deep Dive]] — secrets, templates, and sync strategies

  • [[gh-cli-beginner-guide|GitHub CLI Beginner Guide]] — GitHub authentication via SSH or token

  • [[gh-cli-deep-dive|GitHub CLI Deep Dive]] — advanced GitHub automation

  • [[docker-test-container-beginner-guide|Docker Test Container Guide]] — SSH into containers

  • [[kubernetes-beginner-guide|Kubernetes Beginner Guide]] — SSH tunnels to k8s clusters

  • [[honeymux-beginner-guide|Honeymux Beginner Guide]] — terminal multiplexer over SSH

  • [[sesh-beginner-guide|Sesh Beginner Guide]] — session management tool

  • [[openmux-beginner-guide|OpenMux Beginner Guide]] — alternative mux

  • [[hyperqueue-basics|HyperQueue Basics]] — HPC job scheduler; often uses SSH for cluster access

  • [[git-worktrees-worktrunk-beginner-guide|Git Worktrees Beginner Guide]] — multiple worktrees; often SSHed to

  • [[apache-nifi-hpc-sysadmin-beginner-guide|Apache NiFi HPC Sysadmin Guide]] — HPC workflows that rely on SSH

  • [[maestri-beginner-guide|Maestri Beginner Guide]] — Infinite canvas for orchestrating terminals and AI agents; SSH config is essential for creating cluster terminal nodes

  • [[maestri-deep-dive|Maestri Deep Dive]] — Advanced Maestri patterns including multi-cluster layouts and Floor-based isolation

  • [[dtach-beginner-guide|Dtach Beginner Guide]] — lightweight session detach for resilient SSH workflows

  • [[dtach-deep-dive|Dtach Deep Dive]] — dtach with ProxyJump, jump hosts, and scripted SSH sessions

  • [[headscale-beginner-guide|Headscale Beginner Guide]] — self-hosted Tailscale control server; Headscale tunnels complement SSH for secure mesh networking

  • [[headscale-deep-dive|Headscale Deep Dive]] — advanced Headscale ACLs, OIDC, and production deployment; SSH over Headscale tailnets

Summary

Key Takeaways:

  1. ~/.ssh/config is non-negotiable if you SSH regularly. It eliminates typing, enables multiplexing, and makes multi-account workflows painless.

  2. Order matters: first-match-wins. Specific hosts at the top, Host * defaults at the bottom.

  3. IdentitiesOnly yes is your friend for multi-account setups (GitHub, GitLab, etc.). Without it, SSH tries agent keys in unpredictable order.

  4. Connection multiplexing (ControlMaster auto, ControlPath, ControlPersist) is a game-changer. Set it globally under Host * and watch your loops accelerate 10-100x.

  5. ProxyJump replaces ProxyCommand for jump hosts. Simpler, safer, more readable.

  6. Verify with ssh -G hostname instead of guessing. This tool shows the effective resolved config.

  7. Permissions: 600. Always. Non-negotiable.

  8. Security is intentional: use StrictHostKeyChecking accept-new by default, reserve no for ephemeral CI only. Avoid ForwardAgent unless necessary; prefer ProxyJump.

Must-Know Directives (in priority order):

  • HostName, User, Port — basics
  • IdentityFile, IdentitiesOnly — key selection
  • ProxyJump — jump hosts
  • ControlMaster, ControlPath, ControlPersist — speed boost
  • ServerAliveInterval, ServerAliveCountMax — keep-alives
  • StrictHostKeyChecking — security

Next Steps:

  1. Review your current ~/.ssh/config (if it exists) against the recipes here.
  2. Set up connection multiplexing under Host * and benchmark it.
  3. If you have multiple GitHub/GitLab accounts, implement multi-account setup with IdentitiesOnly yes.
  4. For HPC or bastion access, add ProxyJump and appropriate keep-alives.
  5. Consider managing your config with [[chezmoi-beginner-guide|Chezmoi]] or [[dotfiles-beginner-guide|Dotfiles]] for version control and easy sync across machines.

Happy SSHing. Your future self will thank you.