>_ josetilabs.com
← blog

14 Oct 2026 · security, linux, ssh · 6 min

How to stop typing your key passphrase on every command

With a passphrase-protected key, every new ssh asks for the passphrase again. ssh-agent holds the already-decrypted key in memory for as long as the session lasts, and ssh uses that copy by itself. The trick in the script below is -t: the key does not sit in the agent forever, only for as long as you asked for.

What happens without it

I tested with a fresh passphrase-protected ed25519 key against a test server listening on 127.0.0.1:22222. With BatchMode=yes, which forbids the client from asking anything, the login fails with exit code 255, and the reason is the same both times: there is no loaded key for the client to offer.

With the agent in place, ssh-add -l lists the identity and the same command gets in without asking for anything.

Terminal with three states of the same SSH access: without an agent the key login fails with code 255, with an agent ssh-add -l shows one identity and the login gets in without a passphrase, and after ssh-add -d it fails again with the agent holding no identities
All three captures are the same access with the same key, changing only the agent.

Notice that the successful login used -o BatchMode=yes. If the script runs where there is no passphrase to type, that is how automation discovers the agent solved the problem.

The script

Save it as ssh-agente.sh. It installs nothing, creates no service, and does not touch /etc/ssh: each run starts its own agent and it dies on its own when the lifetime expires.

bash
#!/usr/bin/env bash
# ssh-agente.sh: sobe um ssh-agent com prazo, carrega a chave uma vez so
# e mostra o que conferir depois. Nao instala nada e nao cria servico.
#
#   CHAVE=/caminho/minha.chave DURACAO=3600 bash ssh-agente.sh github.com

set -uo pipefail

DURACAO="${DURACAO:-3600}"                # segundos que a chave fica carregada
CHAVE="${CHAVE:-$HOME/.ssh/id_ed25519}"   # chave que entra no agente
DESTINO="${1:-github.com}"                # host usado so para conferir a configuracao
BASE="${XDG_RUNTIME_DIR:-/tmp}"           # onde mora o socket do agente

echo "== ssh-agente.sh =="

# 0. o que existe nesta maquina
faltando=""
for prog in ssh-agent ssh-add ssh-keygen ssh; do
  command -v "$prog" >/dev/null 2>&1 || faltando="$faltando $prog"
done
if [ -n "$faltando" ]; then
  tem_cliente=0
  echo "aqui nao tem:$faltando  (em Debian e Ubuntu isso e o pacote openssh-client)"
else
  tem_cliente=1
  echo "cliente ssh encontrado, versao $(ssh -V 2>&1 | cut -d' ' -f1)"
fi

# 1. a chave precisa existir. Sem senha nao ha nada para o agente guardar.
if [ ! -f "$CHAVE" ]; then
  echo "chave $CHAVE ainda nao existe"
  if [ "$tem_cliente" = 1 ] && [ -t 0 ]; then
    mkdir -p "$(dirname "$CHAVE")" 2>/dev/null
    ssh-keygen -t ed25519 -a 100 -C "$(id -un)@$(hostname 2>/dev/null || echo exemplo)" -f "$CHAVE"
  else
    echo "gere na mao:  ssh-keygen -t ed25519 -a 100 -f $CHAVE"
  fi
fi

# 2. sobe o agente, se ainda nao houver um vivo neste caminho
# SSH_AGENT_DIR deixa o socket num lugar escolhido, para nao disputar
# /run/user/1001 com o desktop e para o script poder rodar em lote.
SOCK_DIR="${SSH_AGENT_DIR:-$BASE}"
SOCK="$SOCK_DIR/ssh-agente-$(id -u).sock"
mkdir -p "$SOCK_DIR" 2>/dev/null
export SSH_AUTH_SOCK="$SOCK"
if [ -S "$SOCK" ] && ssh-add -l >/dev/null 2>&1; then
  echo "agente ja responde em $SOCK"
elif [ "$tem_cliente" = 1 ]; then
  [ -e "$SOCK" ] && rm -f "$SOCK"     # socket deixado por uma sessao que morreu
  ssh-agent -a "$SOCK" -t "$DURACAO" >/dev/null
  echo "agente novo em $SOCK, expira sozinho em ${DURACAO}s"
fi

# 3. a senha e pedida uma vez, aqui. -t e o prazo da chave no agente.
if [ "$tem_cliente" = 1 ] && [ -f "$CHAVE" ]; then
  if ssh-add -t "$DURACAO" "$CHAVE"; then
    echo "chave carregada por ${DURACAO}s"
  else
    echo "a chave nao entrou: senha errada ou agente sem terminal para perguntar"
  fi
  echo "== ssh-add -l =="
  ssh-add -l
fi

# 4. o que o cliente vai dizer para o outro lado
echo "== ssh -G $DESTINO =="
if [ "$tem_cliente" = 1 ]; then
  ssh -G "$DESTINO" 2>/dev/null | grep -iE '^(identityagent|forwardagent|addkeystoagent|identityfile) ' \
    || echo "nenhuma linha de identidade: o cliente vai tentar a senha"
else
  echo "sem cliente ssh: esta conferencia fica para rodar na sua maquina"
fi

# 5. o bloco de configuracao que faz o agente ser encontrado em todo terminal
echo "== cole em ~/.ssh/config =="
cat <<CFG
Host *
  IdentityAgent $SOCK

# forwarding so onde a outra ponta precisa da sua chave.
# com yes no Host * voce entrega o agente a todo host que abre.
Host github.com
  ForwardAgent yes
CFG

# 6. como sair
echo "== para sair =="
echo "  ssh-add -d $CHAVE     tira so esta chave do agente"
echo "  ssh-agent -k          mata o agente inteiro"

Running it with a non-default key and a one-hour lifetime:

bash
CHAVE=~/.ssh/id_ed25519_producao DURACAO=3600 bash ssh-agente.sh github.com

What the test produced, in order: Lifetime set to 120 seconds and one identity in ssh-add -l (256 SHA256:hUVf... prova-agente (ED25519)), then the login getting in without a passphrase. The server logged 3 successful key authentications across that whole test, all with the same key. Changing the lifetime later takes one line:

bash
ssh-add -t 300 ~/.ssh/id_ed25519   # renova o prazo desta chave

How to verify

The command and the expected output, with a 120 second lifetime:

bash
CHAVE=/path/to/key DURACAO=120 bash ssh-agente.sh github.com
ssh-add -l
Identity added: /path/to/key (test-key)
Lifetime set to 120 seconds
256 SHA256:hUVfzTcYsuKhYcXyhw+PEHp6exXQy8OztVCk+2bl+HA test-key (ED25519)

The second line is the one that matters: it tells you how long the key is loaded for. If ssh-add -l says The agent has no identities, the agent started but the key never made it in.

A stricter check that does not depend on typing anything:

bash
ssh -o BatchMode=yes github.com 'echo got in'

If it prints got in, the agent is doing its job. If it complains about authentication, the key never got there.

When it goes wrong

Could not open a connection to your authentication agent. SSH_AUTH_SOCK points at a socket that no longer exists, often left behind by a dead session. Run SSH_AGENT_DIR=/tmp/ag bash ssh-agente.sh to start the agent on a clean path, or check echo $SSH_AUTH_SOCK.

It still asks for the passphrase with the agent running. The agent holds the key after it gets in; it cannot guess the passphrase of a key that has not been loaded yet. Run ssh-add /path/to/key once, beforehand.

It works in one terminal and not in another. Every new terminal needs SSH_AUTH_SOCK. That is exactly why the script prints the IdentityAgent block to paste into ~/.ssh/config: there it applies to every terminal instead of depending on your shell.

Agent forwarding: where to use it, where not to

ForwardAgent gives a remote server the power to ask for signatures with your keys. The ssh client warns in its documentation that anyone who can bypass file permissions on the remote side can use the forwarded agent as if it were theirs.

The risk is not the server stealing the key, it is the server being able to sign with it. That is why the block above enables forwarding only for the host that needs it, not for Host *. A safer route I prefer these days is a jump host with the ProxyJump option, which needs no agent at all:

bash
ssh -J user@jump-host user@final-server

Comparison of the three ways

approach what it fixes what stays open cost
key without passphrase nothing to type decrypted key on disk one less ssh-keygen
ssh-agent with lifetime one passphrase per lifetime decrypted key in memory a process to clean up
ssh-agent without lifetime one passphrase until you exit key stays in the agent forever the agent lingers on its own
forwarding the far end signs with your key signing power handed to the remote one specific host only

FAQ

Does the agent store the passphrase or the key? The key, decrypted, in the process memory. The passphrase is not kept anywhere after ssh-add. That is why the lifetime matters: while the key is in the agent, there is nothing to type.

Does the key vanish when I close the terminal? The agent is a separate process from the terminal. If your shell started it, the agent usually keeps running and the -t lifetime decides for how long. That is why the script prints how to shut everything down: ssh-agent -k.

Can I use the same key in two places at once? Yes. Each agent is independent, and each ssh process talks to whatever agent SSH_AUTH_SOCK points at. What you cannot do is have one agent pointed at two different places.

ssh-add -l shows the key but ssh still asks for the passphrase. That happens when the current terminal’s SSH_AUTH_SOCK points at a different agent, or when the loaded key is not the one the server accepts. Check with ssh -v and look at which identity file it tries.

Sources

If you have not replaced your server password with a key yet, start with the piece before this one. And if you want the passphrase itself to be something worth guessing, the cost of a weak hash is the measurement that convinced me.

Read next

// By e-mail

Posts published here also go out by e-mail. Delivery has not started yet: whoever leaves an address now joins the first batch.

I store only your e-mail, the date and the record of this consent. No cookies, no trackers, nothing shared with third parties. Details in the privacy policy.