<?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>Linux on inetshell</title><link>https://inet.sh/es/posts/linux/</link><description>Recent content in Linux on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Tue, 08 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/posts/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>ssh-agent persistente con systemd (servicio de usuario)</title><link>https://inet.sh/es/posts/linux/ssh-agent-systemd/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/linux/ssh-agent-systemd/</guid><description>Corre ssh-agent como servicio de usuario de systemd con un socket estable y carga tu llave id_ed25519 al iniciar sesión, sin arrancar un agente por terminal.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># ~/.config/systemd/user/ssh-agent.service</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>Unit<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">Description</span><span class="o">=</span>SSH authentication agent
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>Service<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">ExecStart</span><span class="o">=</span>/usr/bin/ssh-agent -a %t/ssh-agent.socket -D
</span></span><span class="line"><span class="cl"><span class="nv">Type</span><span class="o">=</span>simple
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>Install<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">WantedBy</span><span class="o">=</span>default.target
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemctl --user daemon-reload
</span></span><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> --now ssh-agent
</span></span></code></pre></td></tr></table>
</div>
</div><p>Y en tu <code>~/.bashrc</code> o <code>~/.zshrc</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">/ssh-agent.socket&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! ssh-add -l <span class="p">&amp;</span>&gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Adding keys...&#34;</span>
</span></span><span class="line"><span class="cl">  ssh-add -t 1d ~/.ssh/id_ed25519
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Cierra sesión y vuelve a entrar. Ahora todas tus shells comparten <strong>un solo</strong> agente con un socket estable, y no arrancas un <code>ssh-agent</code> nuevo por cada terminal.</p>
<p>El patrón clásico (<code>eval &quot;$(ssh-agent)&quot;</code> en el <code>.bashrc</code>) levanta un agente por shell, deja procesos huérfanos y pierde las llaves entre terminales. Dejar que <strong>systemd</strong> posea el agente resuelve las tres cosas: un proceso, un socket, un ciclo de vida.</p>
<h2 id="requisitos">Requisitos</h2>
<p><code>openssh</code> (trae <code>ssh-agent</code> y <code>ssh-add</code>) y una sesión de systemd de usuario (cualquier distro moderna con logind). Verifica:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh-agent -V 2&gt;/dev/null<span class="p">;</span> ssh-add -l<span class="p">;</span> <span class="nb">echo</span> <span class="s2">&#34;XDG_RUNTIME_DIR=</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Si <code>$XDG_RUNTIME_DIR</code> está vacío no tienes una sesión de usuario real (por ejemplo entraste con <code>su</code>); entra por SSH o en consola como tu usuario.</p>
<h2 id="1-crea-el-servicio-de-usuario">1. Crea el servicio de usuario</h2>
<p>Un servicio <strong><code>--user</code></strong> vive en <code>~/.config/systemd/user/</code> y corre con tu cuenta, sin root:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mkdir -p ~/.config/systemd/user
</span></span><span class="line"><span class="cl">cat &gt; ~/.config/systemd/user/ssh-agent.service <span class="s">&lt;&lt;&#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">[Unit]
</span></span></span><span class="line"><span class="cl"><span class="s">Description=SSH authentication agent
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">[Service]
</span></span></span><span class="line"><span class="cl"><span class="s">ExecStart=/usr/bin/ssh-agent -a %t/ssh-agent.socket -D
</span></span></span><span class="line"><span class="cl"><span class="s">Type=simple
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">[Install]
</span></span></span><span class="line"><span class="cl"><span class="s">WantedBy=default.target
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li><code>%t</code> se expande al runtime dir del usuario (<code>$XDG_RUNTIME_DIR</code>, típicamente <code>/run/user/&lt;uid&gt;</code>), así que el socket queda en <code>/run/user/&lt;uid&gt;/ssh-agent.socket</code>.</li>
<li><code>-a</code> fija esa ruta de socket (en vez de una aleatoria en <code>/tmp</code>), que es lo que luego exportamos.</li>
<li><code>-D</code> deja el agente en primer plano; es lo correcto para <code>Type=simple</code>.</li>
</ul>
<blockquote>
<p>Si prefieres que aplique a <strong>todos</strong> los usuarios de la máquina, pon el archivo en <code>/etc/systemd/user/ssh-agent.service</code> (requiere root). Para tu cuenta, <code>~/.config/systemd/user/</code> es lo más limpio. Elige una ubicación, no las dos.</p></blockquote>
<h2 id="2-exporta-el-socket-y-carga-la-llave-al-entrar">2. Exporta el socket y carga la llave al entrar</h2>
<p>Agrega esto a tu <code>~/.bashrc</code> o <code>~/.zshrc</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">/ssh-agent.socket&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! ssh-add -l <span class="p">&amp;</span>&gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Adding keys...&#34;</span>
</span></span><span class="line"><span class="cl">  ssh-add -t 1d ~/.ssh/id_ed25519
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>El <code>export</code> tiene que ir <strong>antes</strong> del <code>ssh-add</code>, o el cliente no sabrá con qué agente hablar.</li>
<li><code>ssh-add -l</code> sale con código 1 si el agente no tiene llaves y 2 si no puede conectarse al socket; el <code>if !</code> cubre ambos casos y (re)carga.</li>
<li><code>-t 1d</code> hace que la llave expire en un día. Quita el <code>-t</code> si quieres que dure hasta que reinicies el agente.</li>
</ul>
<h2 id="3-habilita-y-verifica">3. Habilita y verifica</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">systemctl --user daemon-reload
</span></span><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> --now ssh-agent
</span></span><span class="line"><span class="cl">systemctl --user status ssh-agent   <span class="c1"># active (running)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Abre una terminal nueva (o <code>source ~/.bashrc</code>) y comprueba que la llave esté cargada:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ssh-add -L   <span class="c1"># lista las llaves públicas en el agente</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="qué-mueve-cada-pieza">Qué mueve cada pieza</h2>
<table>
  <thead>
      <tr>
          <th>Pieza</th>
          <th>Qué hace</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>ssh-agent.service</code> (<code>--user</code>)</td>
          <td>Mantiene un agente vivo, dueño de systemd</td>
      </tr>
      <tr>
          <td><code>-a %t/ssh-agent.socket</code></td>
          <td>Socket en una ruta fija y predecible</td>
      </tr>
      <tr>
          <td><code>SSH_AUTH_SOCK</code> en el rc</td>
          <td>Apunta cada shell a ese socket</td>
      </tr>
      <tr>
          <td><code>ssh-add -t 1d id_ed25519</code></td>
          <td>Carga la llave al entrar, con caducidad opcional</td>
      </tr>
  </tbody>
</table>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>Usa el nombre real de tu llave.</strong> Muchas guías dicen <code>id_rsa</code>; las llaves modernas son <code>id_ed25519</code>. Si el archivo no existe, <code>ssh-add</code> falla en silencio dentro del <code>if</code>.</li>
<li><strong>Lingering:</strong> por defecto el servicio de usuario muere cuando cierras la última sesión. Si necesitas que el agente siga vivo sin login activo (cron, un runner de CI en la máquina), habilita el lingering: <code>loginctl enable-linger $USER</code>.</li>
<li><strong>El socket debe coincidir.</strong> El <code>-a %t/ssh-agent.socket</code> de la unit y el <code>SSH_AUTH_SOCK=&quot;$XDG_RUNTIME_DIR/ssh-agent.socket&quot;</code> del rc apuntan al <strong>mismo</strong> archivo. Si cambias uno, cambia el otro.</li>
<li><strong><code>&amp;&gt;/dev/null</code> es de bash/zsh.</strong> En un <code>/bin/sh</code> estricto (dash) usa <code>&gt;/dev/null 2&gt;&amp;1</code>.</li>
<li><strong>Passphrase:</strong> si tu llave tiene passphrase, el <code>ssh-add</code> del login te la pedirá una vez por caducidad. Con <code>-t 1d</code>, una vez al día.</li>
<li><strong>No mezcles agentes.</strong> Si algún profile hacía <code>eval &quot;$(ssh-agent)&quot;</code>, quítalo; si no, tendrás dos agentes y un <code>SSH_AUTH_SOCK</code> que baila entre ellos.</li>
</ul>
<h2 id="ver-también">Ver también</h2>
<ul>
<li>Hilo original en Server Fault: <a href="https://serverfault.com/questions/672346/straight-forward-way-to-run-ssh-agent-and-ssh-add-on-login-via-ssh">https://serverfault.com/questions/672346/straight-forward-way-to-run-ssh-agent-and-ssh-add-on-login-via-ssh</a></li>
<li>Gist de referencia (magnetikonline): <a href="https://gist.github.com/magnetikonline/b6255da90606fe9c5c25d3333c98c90d">https://gist.github.com/magnetikonline/b6255da90606fe9c5c25d3333c98c90d</a></li>
</ul>
]]></content:encoded></item></channel></rss>