Seguridad SSH¶
El problema¶
Abres el puerto 22 en el router para llegar a tu servidor desde fuera, y en cuestión de horas /var/log/auth.log se llena de intentos de login como root, admin y ubuntu desde medio mundo. Con contraseña débil, es cuestión de tiempo. Y aunque nadie entre, una clave privada sin passphrase copiada en cinco portátiles y un authorized_keys que nadie revisa son una puerta trasera esperando su momento.
SSH es la llave maestra de la infraestructura: quien lo controla controla el resto. Asegurarlo es el primer paso crítico en cualquier servidor Linux, y casi todo se resuelve con un sshd_config meditado, claves modernas y un par de mecanismos de contención.
Antes de tocar sshd_config
Un error de configuración puede dejarte sin acceso. Mantén una sesión SSH abierta mientras pruebas y abre una segunda para verificar el cambio. Ten además una vía alternativa (consola del hipervisor, IPMI, pantalla física). Valida siempre con sudo sshd -t antes de recargar.
📋 Tabla de Contenidos¶
- Cómo funciona
- Claves SSH modernas
- Endurecer sshd_config
- Restringir por usuario con Match
- Acceso por jump host
- Certificados SSH
- Fail2ban
- Exposición de red
- Troubleshooting
- Buenas prácticas
- Referencias
Cómo funciona¶
La configuración vive en dos sitios distintos y conviene no mezclarlos:
| Fichero | Lado | Contenido |
|---|---|---|
/etc/ssh/sshd_config |
Servidor | Qué acepta el daemon sshd |
/etc/ssh/sshd_config.d/*.conf |
Servidor | Fragmentos que muchas distros incluyen desde el principal |
~/.ssh/config |
Cliente | Atajos, claves por host, jump hosts |
~/.ssh/authorized_keys |
Servidor | Claves públicas que pueden entrar como ese usuario |
En sshd_config, para cada directiva gana la primera aparición que el daemon lee. Por eso un fragmento incluido al principio puede anular lo que escribes más abajo. Para ver la configuración efectiva final:
sudo sshd -T
sshd -T vuelca los valores que realmente aplicará, y es la forma de comprobar que tu cambio no está siendo pisado por un fragmento de sshd_config.d/. Si quieres usar un fichero propio, empieza su nombre con un número bajo (por ejemplo 10-hardening.conf) para que se lea primero; comprueba antes si tu distribución incluye ese directorio desde el fichero principal.
Claves SSH modernas¶
Genera una clave por dispositivo y por finalidad, con ed25519 y passphrase:
ssh-keygen -t ed25519 -C "antonio@portatil-2026"
Pide ruta y passphrase; el comentario -C te sirve para identificarla luego en authorized_keys. RSA sigue funcionando, pero ed25519 es más corta, más rápida y no depende de elegir bien el tamaño. Si necesitas RSA por compatibilidad con equipos antiguos, usa 4096 bits.
Instalar la clave pública en el servidor:
ssh-copy-id -i ~/.ssh/id_ed25519.pub usuario@servidor
Para no teclear la passphrase en cada conexión sin renunciar a ella, usa ssh-agent: se carga una vez con ssh-add y las conexiones siguientes la reutilizan durante la sesión.
Los permisos importan, y sshd ignora silenciosamente los ficheros demasiado abiertos (con StrictModes yes, el valor por defecto):
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_ed25519
Claves de aplicación
Para una clave que usa un script o un runner de CI (despliegues, backups), crea una clave dedicada y limítala en authorized_keys con opciones como command="...", from="IP" o restrict. Así, si se filtra, solo sirve para lo único que debe hacer.
Revisa de vez en cuando qué claves tienes autorizadas: un authorized_keys que crece y nadie poda es una deuda de seguridad.
Endurecer sshd_config¶
Un punto de partida razonable, aplicable en /etc/ssh/sshd_config o en un fragmento propio:
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AllowUsers antonio deploy
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
- Deshabilitar root login:
PermitRootLogin no. Entra con un usuario normal y escala consudo. - Usar claves SSH:
PubkeyAuthentication yesyPasswordAuthentication no. Con contraseñas desactivadas, un ataque de fuerza bruta deja de tener sentido. Desactiva tambiénKbdInteractiveAuthentication, que en algunas configuraciones PAM puede reabrir la autenticación por contraseña. AllowUsers: lista blanca explícita. Quien no esté en ella no entra, aunque tenga cuenta y clave.MaxAuthTriesyLoginGraceTime: acotan los intentos por conexión y el tiempo para autenticarse.X11Forwarding no: sin motivo para tenerlo en un servidor.
Dependiente de la versión
El nombre KbdInteractiveAuthentication es el actual en versiones recientes de OpenSSH; en las más antiguas (antes de OpenSSH 8.7) se llamaba ChallengeResponseAuthentication, que hoy sigue como alias obsoleto. Comprueba con man sshd_config cuál reconoce tu versión; una directiva desconocida hace fallar sshd -t.
Aplica el cambio sin cortar las sesiones existentes:
sudo sshd -t && sudo systemctl reload ssh
El nombre de la unit es ssh en Debian y Ubuntu y sshd en la familia RHEL; consulta cómo se gestiona en Systemd.
Cambiar el puerto por defecto (usar uno distinto al 22) evita escaneos masivos. Es seguridad por oscuridad: reduce ruido en los logs pero no frena a un atacante dirigido, así que nunca sustituye a lo anterior. Si lo haces, actualiza también el firewall, el port de fail2ban y tu ~/.ssh/config.
Restringir por usuario con Match¶
El bloque Match aplica directivas solo a ciertos usuarios, grupos o direcciones, y va siempre al final del fichero porque todo lo que sigue pertenece a su condición hasta el siguiente Match.
# Un usuario de backups solo puede entrar desde la VLAN de gestión
Match User backup Address 10.0.10.0/24
PasswordAuthentication no
AllowTcpForwarding no
X11Forwarding no
ForceCommand /usr/local/bin/backup-receiver
# Usuarios del grupo sftponly: solo SFTP, enjaulados
Match Group sftponly
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp
AllowTcpForwarding no
ForceCommand obliga a ejecutar siempre ese comando, ignorando el que pida el cliente. Es la base para dar a un servicio externo acceso mínimo. ChrootDirectory exige que la ruta y todos sus padres pertenezcan a root y no sean escribibles por otros; si no, sshd rechaza la sesión.
Acceso por jump host¶
En un homelab o una red con segmentos, expón por SSH un solo host de entrada (bastión) y salta desde él al resto. En el cliente, ~/.ssh/config:
Host bastion
HostName bastion.ejemplo.lan
User antonio
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Host db-interna
HostName 10.0.20.15
User antonio
ProxyJump bastion
Con eso, ssh db-interna atraviesa el bastión sin que la base de datos tenga ningún puerto expuesto. Es preferible a ForwardAgent yes, que deja tu agente accesible para quien controle el host intermedio. Para llegar al bastión sin abrir puertos al exterior, la alternativa natural es una VPN: ver WireGuard.
Certificados SSH¶
A partir de cierto tamaño, repartir claves públicas a mano por authorized_keys no escala. OpenSSH soporta certificados: una CA propia firma las claves de los usuarios y los servidores solo confían en la CA.
Crear la CA y firmar la clave de un usuario:
ssh-keygen -t ed25519 -f ca_usuarios -C "CA SSH usuarios"
ssh-keygen -s ca_usuarios -I antonio -n antonio -V +1d ~/.ssh/id_ed25519.pub
En la firma, -I es un identificador que aparece en los logs, -n la lista de usuarios (principals) permitidos y -V +1d la validez. El resultado es id_ed25519-cert.pub junto a la clave. En el servidor, se declara en qué CA se confía:
TrustedUserCAKeys /etc/ssh/ca_usuarios.pub
Ventajas: los certificados caducan solos (validez de horas o días), no hay que editar authorized_keys en cada alta o baja, y los logs registran qué certificado se usó. Para custodiar la clave de la CA y emitir certificados bajo demanda hay soluciones que lo automatizan; ver gestión de secretos y autenticación y autorización.
Los certificados de host funcionan al revés: la CA firma las claves del servidor y el cliente confía en ella mediante @cert-authority en known_hosts, lo que elimina el aviso de huella desconocida en la primera conexión.
Fail2ban¶
Fail2ban escanea logs y banea IPs que muestran comportamiento malicioso. Si ya has desactivado las contraseñas el riesgo real es menor, pero sigue siendo útil para cortar el ruido y los escaneos persistentes.
Instalación (Debian/Ubuntu)¶
sudo apt install fail2ban
Configuración (Jail)¶
Crea /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
- Los cambios van en
jail.local, nunca enjail.conf, que un paquete puede sobrescribir. - Si cambiaste el puerto de SSH,
portdebe reflejarlo. logpathdepende de la distribución: en sistemas que solo usan el journal puede no existir/var/log/auth.log. En ese caso, usabackend = systemd(y quitalogpath); consulta la documentación de fail2ban.
Para aplicar y comprobar:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
El segundo comando lista las IPs actualmente baneadas. Para desbanear una IP que se bloqueó por error: sudo fail2ban-client set sshd unbanip IP. Para integrar el baneo con el firewall moderno, ver nftables y fail2ban.
Exposición de red¶
Lo más seguro es que SSH no sea accesible desde internet. Orden de preferencia:
- Solo por VPN (WireGuard) o red de gestión interna.
- Detrás de un bastión con jump host, con el resto de equipos sin puerto abierto.
- Abierto a internet, con claves, sin contraseñas,
AllowUsersy fail2ban.
Cualquiera de las tres necesita un cortafuegos que limite el origen: ver firewall de red y el resto de medidas en hardening de Linux.
Troubleshooting¶
| Síntoma | Causa probable | Arreglo |
|---|---|---|
Permission denied (publickey) |
Clave no autorizada, permisos de ~/.ssh demasiado abiertos o usuario fuera de AllowUsers |
Revisa authorized_keys, chmod 700/600 y la lista de AllowUsers |
Pide contraseña aunque desactivaste PasswordAuthentication |
Un fragmento de sshd_config.d/ o KbdInteractiveAuthentication lo reactiva |
sudo sshd -T y busca la directiva efectiva |
sshd -t falla tras editar |
Directiva desconocida en tu versión o error de sintaxis | Lee el mensaje; contrasta con man sshd_config |
| Te quedaste sin acceso tras el cambio | No probaste en una segunda sesión | Entra por consola, revierte y valida con sshd -t |
REMOTE HOST IDENTIFICATION HAS CHANGED |
Reinstalaste el servidor o hay un intermediario | Si fue tu reinstalación, ssh-keygen -R host; si no, investiga |
Connection refused tras cambiar el puerto |
El firewall o el cliente siguen en el 22 | Abre el nuevo puerto y usa -p o Port en ~/.ssh/config |
| El chroot de SFTP rechaza la sesión | Ruta del chroot no propiedad de root o escribible por otros | Corrige propietario y permisos de la ruta y sus padres |
| fail2ban no banea | logpath incorrecto o backend equivocado |
Revisa fail2ban-client status sshd y los logs de fail2ban |
| Te baneaste a ti mismo | Varios intentos fallidos desde tu IP | fail2ban-client set sshd unbanip IP y añade tu red a ignoreip |
| Certificado rechazado | Caducado, o el principal no coincide con el usuario | Reemite y comprueba -n y la validez; ssh-keygen -L -f id_ed25519-cert.pub lo muestra |
Para depurar desde el cliente, ssh -v usuario@host (más v para más detalle) muestra qué claves ofrece y por qué se rechaza cada método. En el servidor, el motivo suele aparecer en el log de autenticación o en journalctl -u ssh.
Buenas prácticas¶
- Claves ed25519 con passphrase, una por dispositivo, y baja inmediata de la que se pierda.
- Sin contraseñas ni root:
PasswordAuthentication noyPermitRootLogin noen cualquier servidor. AllowUsersoAllowGroupscomo lista blanca, no confiar en que "nadie más tiene cuenta".sshd -ty sesión de reserva antes de cada cambio; la configuración, en Git y desplegada con Ansible.- Un solo punto de entrada (VPN o bastión) en lugar de abrir SSH en cada host.
- Claves de automatización restringidas con
command=,from=yrestrict, nunca la clave personal. - Certificados cuando el número de usuarios o equipos haga inmanejable
authorized_keys. - Revisa los logs de autenticación y alerta de accesos fuera de patrón; ver monitoreo de seguridad.
Referencias¶
- sshd_config(5) · ssh_config(5)
- ssh-keygen(1) — claves y certificados
- Documentación de fail2ban
- Hardening de Linux · nftables y fail2ban
- Systemd · WireGuard