Usa nombres que lleven información
Elige un esquema y respétalo, por ejemplo rol, entorno y número:
web-prod-1
web-prod-2
db-staging-1
acme-web-prod-1
Un nombre así te dice qué vas a tocar y funciona con patrones. Un nombre como server3 o una dirección IP suelta no hace ninguna de las dos cosas. Añade un prefijo de proyecto o de cliente si te ocupas de más de uno.
Usa patrones, no líneas repetidas
Host *-prod-*
User deploy
IdentityFile ~/.ssh/prod_ed25519
IdentitiesOnly yes
Host web-prod-1
HostName 203.0.113.11
Host web-prod-2
HostName 203.0.113.12
Recuerda que gana el primer valor y que se aplican todos los bloques que coinciden, así que el orden importa. Mira la guía del archivo de configuración de ssh. Comprueba cualquier host con ssh -G web-prod-1.
Si tus nombres se resuelven en DNS, puedes prescindir de las líneas por host:
Host web-*
HostName %h.example.com
Un archivo por proyecto
# top of ~/.ssh/config
Include ~/.ssh/config.d/*
~/.ssh/config.d/
acme
internal
lab
Cada archivo contiene los hosts de un proyecto, así que puedes compartirlo con un compañero, guardarlo en el repositorio de ese proyecto o borrarlo cuando el proyecto termine. Mantén Include al principio del archivo principal y los valores por defecto de Host * debajo.
Grupos
ssh no tiene grupos como tales. Los patrones son la forma sencilla, así que los nombres hacen el trabajo. Las etiquetas (tags), desde OpenSSH 9.4, agrupan hosts cuyos nombres no encajan entre sí:
Host legacy-api billing-1
Tag prod
Match tagged prod
User deploy
Para ejecutar un comando de solo lectura en un grupo, un simple bucle de shell basta a pequeña escala:
for host in web-prod-1 web-prod-2; do
printf '%s: ' "$host"
ssh -n -o BatchMode=yes -o ConnectTimeout=5 "$host" uptime
done
-n mantiene ssh lejos de la entrada estándar, así el mismo bucle también funciona cuando los nombres vienen de un archivo. BatchMode hace que falle en lugar de esperar en un prompt, y ConnectTimeout evita que un servidor caído retenga a los demás. Para ejecutar en paralelo y desplegar con cuidado, consulta cómo ejecutar un comando en varios servidores.
Una clave por propósito
Usar una clave por propósito, como trabajo, un cliente o producción, significa que perder una no abre todo. También significa que puedes retirar el acceso de un cliente quitando una sola clave.
Host acme-*
IdentityFile ~/.ssh/acme_ed25519
IdentitiesOnly yes
Sin IdentitiesOnly yes, ssh ofrece también las claves de tu agente, lo que lleva a Too many authentication failures. No pongas IdentityFile en Host * si quieres esta separación: ese valor se añade a la lista y no la sustituye, así que también se prueba la otra clave.
Una clave por servidor suele dar más trabajo del que compensa. Si la lista de personas y servidores cambia constantemente, mira mejor los certificados SSH.
known_hosts con hash: una contrapartida
HashKnownHosts yes guarda los nombres de host de ~/.ssh/known_hosts como hashes, de modo que quien robe el archivo no obtiene una lista lista para usar de tus servidores. Debian y Ubuntu lo activan en /etc/ssh/ssh_config. El OpenSSH original lo deja desactivado.
- Lo que ganas: un archivo robado no lista tus servidores en texto plano. Los hashes aún se pueden comprobar contra una suposición, así que esto frena a un atacante y no lo detiene.
- Lo que pierdes: no puedes leer ni buscar en el archivo a simple vista, y el autocompletado de la shell basado en él deja de funcionar.
Las herramientas siguen funcionando con entradas con hash:
ssh-keygen -F web-prod-1 # find a host
ssh-keygen -R web-prod-1 # remove a host
ssh-keygen -H # hash an existing file
ssh-keygen -H conserva el original, con los nombres en claro, como known_hosts.old. Borra ese archivo si la privacidad era el motivo. Ten en cuenta que tu configuración de ssh, el historial de la shell y el inventario también contienen los nombres, así que el hash por sí solo oculta menos de lo que parece.
Mantén un inventario
Pasada una docena de servidores, mantén una lista que diga qué existe: nombre, dirección, rol, entorno, responsable. Un archivo de texto en un repositorio Git privado es suficiente. Que no contenga contraseñas ni claves. Esa lista es lo que consultas cuando alguien se va, o cuando te preguntas qué servidores siguen en uso.
Un inventario de Ansible es un buen formato para esto aunque todavía no uses Ansible:
[web]
web-prod-1
web-prod-2
[db]
db-prod-1 ansible_user=postgres
Si ya tienes una lista así, puedes generar a partir de ella los archivos de config.d, en lugar de mantener dos listas a mano. Los mismos archivos sirven también para una app que importa hosts desde ~/.ssh/config, incluidos los archivos indicados con Include.
Cuándo conviene una herramienta como Ansible
ssh es bueno para llegar a un servidor. No mantiene los servidores iguales. Pasa a Ansible o a una herramienta similar cuando hagas el mismo cambio en más de unos pocos servidores, cuando necesites repetir una configuración o cuando necesites un registro de lo que cambió.
ansible all -i inventory.ini -m ping
ansible web -i inventory.ini -a 'uptime'
Ansible usa el comando ssh por defecto, así que tu configuración, tus jump hosts y tus claves siguen funcionando. El destino solo necesita SSH y Python. Comprueba lo que hará un playbook con --check antes de ejecutarlo en producción, y lee cómo evitar errores en servidores de producción.
Preguntas
¿Cómo organizo muchos hosts en ~/.ssh/config?
Da a los hosts nombres que indiquen función y entorno, pon los ajustes comunes en patrones como Host web-* y guarda un archivo por proyecto en ~/.ssh/config.d/, cargado con Include.
¿Cómo ejecuto un comando en muchos servidores SSH?
Para pocos servidores basta un bucle de shell con ssh -n -o BatchMode=yes. Para cambios que repites, usa Ansible, que funciona sobre tu configuración de ssh.
¿Conviene una sola clave SSH para todos los servidores?
Usa una clave por propósito, como el trabajo, un cliente o producción, con IdentitiesOnly yes. Una clave por servidor suele dar más trabajo del que compensa.