<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ssh · wleeaf field notes</title><link>https://articles.wleeaf.dev/tags/ssh/</link><description>Hands-on guides and build logs from the wleeaf studio: self-hosting, Linux, networking, Docker, the command line and reverse engineering.</description><language>en-us</language><lastBuildDate>Thu, 26 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://articles.wleeaf.dev/tags/ssh/feed.xml" rel="self" type="application/rss+xml"/><item><title>SSH Config Tricks I Use Every Day</title><link>https://articles.wleeaf.dev/posts/ssh-config-tricks/</link><pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate><guid>https://articles.wleeaf.dev/posts/ssh-config-tricks/</guid><description>Stop typing full hostnames and passwords. Your SSH config file is more powerful than you think.</description><category>ssh</category><category>linux</category><category>productivity</category><content:encoded><![CDATA[<p>I SSH into servers maybe thirty times a day. Without a proper SSH config, that would mean typing out usernames, hostnames, ports, and key paths every single time. Life is too short for that.</p>
<h2 id="the-basics">The Basics<a class="anchor" href="#the-basics" aria-label="Link to “The Basics”">#</a></h2>
<p>The SSH config lives at <code>~/.ssh/config</code>. Here is a minimal example:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host myserver
  HostName 203.0.113.50
  User root
  IdentityFile ~/.ssh/id_ed25519</code></pre>
</div>
<p>Now <code>ssh myserver</code> is all you need. But it gets better.</p>
<h2 id="jump-hosts">Jump Hosts<a class="anchor" href="#jump-hosts" aria-label="Link to “Jump Hosts”">#</a></h2>
<p>If you need to reach a server through a bastion:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host bastion
  HostName bastion.example.com
  User admin

Host internal
  HostName 10.0.1.50
  User deploy
  ProxyJump bastion</code></pre>
</div>
<p><code>ssh internal</code> automatically tunnels through the bastion. No manual port forwarding, no two-step login.</p>
<h2 id="wildcard-hosts">Wildcard Hosts<a class="anchor" href="#wildcard-hosts" aria-label="Link to “Wildcard Hosts”">#</a></h2>
<p>This is the one most people miss:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host *.prod
  User deploy
  IdentityFile ~/.ssh/prod_key
  StrictHostKeyChecking accept-new

Host web.prod
  HostName 10.0.1.10

Host api.prod
  HostName 10.0.1.11

Host db.prod
  HostName 10.0.1.12</code></pre>
</div>
<p>The wildcard block sets defaults for anything ending in <code>.prod</code>. Individual host blocks just add the hostname. You get consistent configuration without repetition.</p>
<h2 id="keep-connections-alive">Keep Connections Alive<a class="anchor" href="#keep-connections-alive" aria-label="Link to “Keep Connections Alive”">#</a></h2>
<p>Add this to your <code>Host *</code> block:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host *
  ServerAliveInterval 60
  ServerAliveCountMax 3
  AddKeysToAgent yes
  IdentitiesOnly yes</code></pre>
</div>
<p><code>ServerAliveInterval</code> sends a keepalive every 60 seconds. This stops firewalls and NATs from killing idle connections. <code>AddKeysToAgent</code> automatically loads keys into ssh-agent after first use.</p>
<h2 id="connection-multiplexing">Connection Multiplexing<a class="anchor" href="#connection-multiplexing" aria-label="Link to “Connection Multiplexing”">#</a></h2>
<p>This is the real speed trick:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host *
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%r@%h-%p
  ControlPersist 600</code></pre>
</div>
<p>The first SSH connection to a host creates a socket. Subsequent connections to the same host reuse it. No new TCP handshake, no new key exchange. The second <code>ssh myserver</code> connects instantly.</p>
<p><code>ControlPersist 600</code> keeps the socket alive for 10 minutes after you disconnect. So if you SSH in, run a command, disconnect, and SSH back in within 10 minutes, the second connection is instant.</p>
<p>Create the socket directory first:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/.ssh/sockets</span></span></code></pre></div>
</div>
<h2 id="port-forwarding-shortcuts">Port Forwarding Shortcuts<a class="anchor" href="#port-forwarding-shortcuts" aria-label="Link to “Port Forwarding Shortcuts”">#</a></h2>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host db-tunnel
  HostName myserver.com
  User admin
  LocalForward 5432 localhost:5432
  RequestTTY no
  ExitOnForwardFailure yes</code></pre>
</div>
<p>Now <code>ssh db-tunnel</code> opens a tunnel to the remote Postgres. No remembering the <code>-L</code> syntax. <code>ExitOnForwardFailure</code> means if the port is already in use, SSH exits instead of connecting without the tunnel.</p>
<h2 id="sshfp-for-host-verification">SSHFP for Host Verification<a class="anchor" href="#sshfp-for-host-verification" aria-label="Link to “SSHFP for Host Verification”">#</a></h2>
<p>Tired of the &ldquo;Are you sure you want to continue connecting?&rdquo; messages? If you control the DNS, add SSHFP records:</p>
<div class="code">
<div class="code-head"><span class="code-lang">bash</span><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh-keygen -r myserver.com</span></span></code></pre></div>
</div>
<p>This outputs DNS records you can add to your zone. Then set:</p>
<div class="code is-plain">
<div class="code-head"><button type="button" class="code-copy" data-copy-code hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2.5"/><path d="M16 8V6.5A2.5 2.5 0 0 0 13.5 4h-7A2.5 2.5 0 0 0 4 6.5v7A2.5 2.5 0 0 0 6.5 16H8"/></svg><span>Copy</span></button></div>
<pre tabindex="0"><code>Host *
  VerifyHostKeyDNS yes</code></pre>
</div>
<p>SSH verifies the host key against DNS automatically. No more blindly accepting fingerprints.</p>
<p>The SSH config file is probably the highest-leverage dotfile you can invest time in. Five minutes of setup saves thousands of keystrokes.</p>
]]></content:encoded></item></channel></rss>