Asegurar SSH: acceso por clave, bloquear el login root y desactivar la contraseña
ed25519 en Linux, macOS y Windows, la trampa de cloud-init en /etc/ssh/sshd_config.d, la activación por socket en Ubuntu 24.04, la prueba con sudo sshd -T y la vía de rescate por la consola.
Un servidor recién instalado aparece en las listas de los escáneres pocos minutos después de estar accesible por primera vez. Lo que llega es casi siempre lo mismo: pruebas automatizadas de nombres de usuario y contraseñas en el puerto 22. Si desactivas el inicio de sesión por contraseña y solo permites claves, le quitas la base a toda esa clase de ataques. No la debilitas, la eliminas por completo.
Los pasos básicos los conoce casi todo el mundo. Lo que falta en las guías habituales es la parte que viene después: por qué PasswordAuthentication no se queda sin efecto una y otra vez en las imágenes cloud, por qué un systemctl reload ssh en un servidor con ssh.socket activo ya no hace lo que esperas, y cómo demostrar en lugar de confiar en que el cambio ha surtido efecto. De eso trata este artículo. Todos los datos se refieren a Debian 13, Debian 12, Ubuntu 24.04 y Ubuntu 22.04.
La regla que lo salva todo: dos sesiones
Antes de cambiar nada en la configuración de SSH, abre una segunda ventana de terminal e inicia sesión también en el servidor desde ahí. Esa segunda sesión se queda abierta hasta que hayas probado con éxito la nueva configuración con una tercera conexión completamente nueva.
El motivo es técnico: reiniciar el servicio SSH no termina las sesiones existentes. Las conexiones en curso las atienden procesos hijo que ya se habían separado antes, y sobreviven al reinicio del proceso padre. Por eso de una configuración rota solo te enteras en el siguiente intento de conexión, y para entonces la sesión antigua es tu único camino de vuelta. Quien la cierra para "volver a entrar limpiamente una vez" se arrepiente con bastante regularidad.
Comprueba además qué versión de OpenSSH tienes delante, porque de ella dependen varios detalles:
ssh -V
| Sistema | OpenSSH | Listener por defecto |
| Debian 13 (trixie) | 10.0p2 | ssh.service |
| Debian 12 (bookworm) | 9.2p1 | ssh.service |
| Ubuntu 24.04 LTS | 9.6p1 | ssh.socket |
| Ubuntu 22.04 LTS | 8.9p1 | ssh.service |
Si falta por completo el servicio de servidor, por ejemplo en una imagen mínima, instálalo después:
sudo apt update
sudo apt install -y openssh-server
Generar la clave: ed25519 en Linux, macOS y Windows
Usa ed25519. Es corta, rápida de verificar, tiene un amplio margen de seguridad y todas las versiones de OpenSSH tratadas aquí la soportan. RSA solo lo necesitas para sistemas antiguos que no aceptan nada más, y en ese caso con 4096 bits como mínimo.
Linux y macOS
ssh-keygen -t ed25519 -a 100 -C "hani@notebook" -f ~/.ssh/id_ed25519
-a 100 sube el número de rondas KDF de la passphrase y encarece considerablemente los ataques offline contra el archivo de clave. -C establece un comentario que después aparece en authorized_keys y te indica qué clave viene de qué dispositivo. Asigna una passphrase. Una clave sin passphrase es un archivo que puede llevarse cualquiera que se siente un momento delante de tu ordenador.
Para no tener que teclear la passphrase en cada conexión, el agente se encarga de guardarla en memoria:
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
En macOS guardas la passphrase en el llavero. La antigua opción -K se llama --apple-use-keychain desde macOS 12:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
Para que eso sobreviva a un reinicio, esto va en ~/.ssh/config:
Host *
UseKeychain yes
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
Windows
Windows 10 a partir de la versión 1809, Windows 11 y Windows Server a partir de 2019 ya incluyen el cliente OpenSSH. En PowerShell:
ssh -V
ssh-keygen -t ed25519 -a 100 -C "hani@windows"
La clave acaba en C:\Users\TuNombre\.ssh\. Si falta el cliente, instálalo como característica de Windows:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
En Windows el agente es un servicio del sistema y viene desactivado de fábrica:
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent
ssh-add $env:USERPROFILE\.ssh\id_ed25519
Llevar la clave pública al servidor
Al servidor va únicamente el archivo con la extensión .pub. El archivo sin extensión es la clave privada y nunca sale de tu ordenador.
La vía cómoda en Linux
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@203.0.113.10
ssh-copy-id crea ~/.ssh, ajusta los permisos correctamente, añade la clave a authorized_keys y comprueba de paso si ya estaba presente. En macOS la herramienta no viene incluida en todas las versiones. Compruébalo rápido con command -v ssh-copy-id y, si no está, pasa a la vía manual.
La vía de Windows sin ssh-copy-id
El cliente OpenSSH de Microsoft no incluye ssh-copy-id. El original es un script de shell y nunca se portó. El mismo resultado lo consigues con una tubería:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Aquí acecha un detalle que cuesta muchas horas: al pasar datos a un programa externo, PowerShell añade los finales de línea en formato Windows. En authorized_keys queda entonces un retorno de carro invisible al final de la línea. Mientras hayas comentado la clave con -C, ese carácter cae en el campo de comentario y no molesta. Sin comentario se pega al bloque Base64 y el inicio de sesión falla sin ningún mensaje útil. Por eso: pon siempre un comentario y, en caso de duda, haz una limpieza en el servidor.
sed -i 's/\r$//' ~/.ssh/authorized_keys
La vía manual, que funciona en todas partes
mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
Después añade el contenido del archivo .pub como una única línea. Si el archivo ya está en el servidor, por ejemplo porque lo has subido, puedes hacerlo directamente:
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
A continuación comprueba qué ha acabado realmente en el archivo:
ssh-keygen -lf ~/.ssh/authorized_keys
La salida lista la huella digital y el comentario de cada entrada válida. Lo que no aparezca aquí está partido en varias líneas o dañado. Compara la huella con la de tu clave local:
ssh-keygen -l -f ~/.ssh/id_ed25519.pub
Si ambas coinciden, la transferencia ha ido bien. Ahora prueba, antes de desactivar nada, si el inicio de sesión por clave funciona siquiera. Solo cuando una conexión nueva pasa sin que te pida contraseña, sigues adelante.
La trampa: /etc/ssh/sshd_config.d y cloud-init
Este es el punto en el que la mayoría de las guías se detienen, y el motivo más frecuente de la frase "lo he desactivado y sigue funcionando".
Desde Debian 11 y Ubuntu 22.04 hay, justo al principio de /etc/ssh/sshd_config, una línea que lee un directorio entero. Compruébalo tú mismo, mira en qué posición está:
grep -n Include /etc/ssh/sshd_config
En los cuatro sistemas tratados aquí, la línea Include está al principio, no al final. Y ahora llega la particularidad de OpenSSH que no se conoce de casi ningún otro lenguaje de configuración: gana el primer valor encontrado, no el último. Lo que esté en un archivo bajo /etc/ssh/sshd_config.d/ se lee, por tanto, antes que el archivo principal y se impone a cualquier línea posterior de sshd_config.
Cloud-init usa exactamente ese directorio. En el primer arranque escribe /etc/ssh/sshd_config.d/50-cloud-init.conf, y según el despliegue ahí figura PasswordAuthentication yes. Después puedes poner PasswordAuthentication no en sshd_config tantas veces como quieras: el inicio de sesión por contraseña sigue abierto. Hazte primero una idea general:
ls -la /etc/ssh/sshd_config.d/
sudo grep -riE 'passwordauthentication|permitrootlogin|kbdinteractive' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
De ahí se deriva la recomendación real: no toques sshd_config en absoluto. Crea en su lugar un archivo propio cuyo nombre se ordene alfabéticamente antes que todo lo que la automatización deje ahí. Los archivos se leen en orden alfabético, 01- va antes que 50-, y como gana el primer valor, cloud-init puede reescribir su archivo después tantas veces como quiera sin anular tu hardening. Esa es la diferencia con el consejo muy extendido de crear un 99-hardening.conf: ese pierde frente a cloud-init, y además en silencio. Ahora bien, el mismo mecanismo también puede volverse en tu contra: un archivo con un número menor, por ejemplo 00-cloud.conf, gana frente a tu 01-, porque OpenSSH toma el valor que lee primero. Por eso también merece la pena echar un vistazo al directorio después del hardening.
Escribir el hardening, con un rollback temporizado como plan de retorno
Monta primero la red de seguridad. El siguiente comando elimina tu archivo nuevo automáticamente en diez minutos y reinicia SSH, salvo que lo hayas cancelado antes:
sudo systemd-run --on-active=10min --unit=ssh-rollback /bin/sh -c 'rm -f /etc/ssh/sshd_config.d/01-hardening.conf; systemctl try-restart ssh.socket ssh.service'
Ahora la configuración propiamente dicha:
sudo tee /etc/ssh/sshd_config.d/01-hardening.conf >/dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
PermitEmptyPasswords no
MaxAuthTries 3
EOF
sudo chmod 644 /etc/ssh/sshd_config.d/01-hardening.conf
Dos líneas merecen una explicación. KbdInteractiveAuthentication no no es un adorno: si se queda en yes, PAM puede seguir ofreciendo la petición de contraseña por el rodeo de la entrada interactiva de teclado, aunque PasswordAuthentication esté en no. Justo por eso hay servidores que siguen pidiendo contraseña pese a tener desactivado el inicio de sesión por contraseña.
Y PermitRootLogin prohibit-password en lugar de no: así se mantiene el acceso como root por clave, mientras que quedan excluidas las contraseñas para root. Cuando hayas creado un usuario propio con sudo y hayas comprobado de forma demostrable su inicio de sesión por clave, pon entonces PermitRootLogin no. Antes no.
Lo que no deberías adoptar, aunque aparezca en guías más antiguas: ChallengeResponseAuthentication. Desde OpenSSH 8.7 la opción no es más que un alias obsoleto de KbdInteractiveAuthentication y en las versiones actuales genera un mensaje de obsolescencia en el log. Déjala fuera.
Comprobación de sintaxis antes de activar, sin excepciones:
sudo sshd -t
Que no haya salida significa que el archivo es correcto sintácticamente. No significa que su contenido haga lo que tú quieres. De eso se encarga enseguida sudo sshd -T.
Si en cambio la prueba informa de Missing privilege separation directory: /run/sshd, es que sshd no se ha ejecutado ni una sola vez desde que arrancó el sistema, porque ese directorio solo lo crea ssh.service. Esto afecta sobre todo a Ubuntu 24.04 con activación por socket y se resuelve con sudo mkdir -p /run/sshd o sudo systemctl start ssh.service. En un servidor en el que estés conectado por SSH en ese momento no aparece de todos modos.
Activar: ssh.service, ssh.socket y las diferencias por distribución
Aquí los cuatro sistemas se separan, y la vieja costumbre de systemctl reload ssh es la respuesta equivocada en todos los casos en los que ssh.socket mantiene el puerto.
Ubuntu apuesta por la activación por socket desde la 22.10, y Ubuntu 24.04 la trae por defecto: allí ssh.socket está activado y ssh.service desactivado. Debian lo hace de fábrica justo al revés. Debian 13 y Debian 12 vienen con ssh.service activado y ssh.socket desactivado, en contra de la suposición extendida de que Debian 13 cambia al socket en una instalación nueva. Con activación por socket, systemd escucha él mismo en el puerto 22 y solo arranca un sshd nuevo cuando llega una conexión entrante. Eso tiene un efecto secundario agradable: los cambios en sshd_config surten efecto en la siguiente conexión de todas formas, porque cada proceso de conexión vuelve a leer la configuración. Solo necesitas recargar para ajustes que afectan al listener en sí, es decir Port y ListenAddress.
Así que no adivines, pregúntale al sistema qué unidad presta el servicio en tu caso:
systemctl is-enabled ssh.socket ssh.service
| Sistema | ssh.socket | ssh.service | Particularidad |
| Ubuntu 24.04 LTS | enabled | disabled | Activación por socket por defecto, ListenStream en 0.0.0.0:22 y [::]:22 |
| Debian 13 (trixie) | disabled | enabled | Accept=no |
| Debian 12 (bookworm) | disabled | enabled | Accept=no |
| Ubuntu 22.04 LTS (y también Debian 11) | disabled | enabled | Accept=yes, es decir un proceso propio por conexión a través de ssh@.service |
Y luego un comando que es correcto en los cuatro sistemas:
sudo sshd -t && sudo systemctl try-restart ssh.socket ssh.service
try-restart solo reinicia una unidad si está realmente activa, y deja la otra intacta. Así no tienes que adivinar. Ambas partes necesitan root: sshd está en /usr/sbin y en Debian no figura en el PATH de un usuario normal, y try-restart accede a systemd de todos modos. Sin sudo, la llamada termina, según el sistema, con sshd: command not found o con sshd: no hostkeys available, porque las claves de host bajo /etc/ssh solo son legibles para root.
Igual de deliberadamente, nada de un systemctl restart ssh.socket aislado, aunque ese comando aparezca en muchas guías. En Debian 13 y Debian 12 aborta con Job failed. See journalctl -xe for details., y en el journal figura ssh.socket: Socket service ssh.service already active, refusing. La nueva configuración no se activa con ello, y la contraprueba sigue informando de Permission denied (publickey,password). En Ubuntu 22.04 y Debian 11 sí se ejecuta sin mensaje de error, pero detiene ssh.service y pasa el host a activación por socket. Como ssh.socket sigue desactivado allí, en el siguiente reinicio vuelve a arrancar ssh.service, así que el modo de funcionamiento cambia de un lado a otro sin que se note. try-restart sobre ambas unidades evita las dos cosas.
Y también de forma deliberada, nada de reload: en un sistema en el que ssh.socket mantiene el puerto, un systemctl reload ssh responde con
fatal: Cannot bind any address.
A partir de ahí el servicio queda en estado de error y ha soltado su puerto. Un reinicio no tiene ese problema y tampoco termina las sesiones existentes.
Si quieres mover el puerto, llega la siguiente diferencia entre distribuciones. Ubuntu 24.04 genera la configuración del socket a partir de sshd_config mediante un generador de systemd, así que allí basta con esto tras cambiar el puerto:
sudo systemctl daemon-reload
sudo systemctl try-restart ssh.socket ssh.service
Si en Debian, en cambio, has cambiado deliberadamente a ssh.socket, el puerto está en la propia unidad y un cambio en sshd_config no tiene ningún efecto. Lo defines mediante un archivo drop-in con sudo systemctl edit ssh.socket, con un ListenStream= vacío para restablecer el valor y una segunda línea con el valor nuevo. Después comprueba el resultado contra la realidad, no contra la configuración:
sudo ss -tlnp
La prueba: sshd -T y la contraprueba
Un comando que se ejecuta sin errores no es una prueba. La prueba es la configuración global ya resuelta, en la que todos los archivos incluidos están tenidos en cuenta:
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|permitrootlogin|usepam|port|authorizedkeysfile)'
Se espera una salida de este tipo:
port 22
permitrootlogin prohibit-password
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
usepam yes
authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2
Si a pesar de tu archivo ahí pone passwordauthentication yes, en el directorio hay un archivo que se ordena alfabéticamente antes que el tuyo. Vuelve a la sección sobre el orden.
Para una consulta puntual basta el mismo comando con un filtro más estrecho:
sudo sshd -T | grep -i passwordauthentication
Hay dos razones a favor de ese sudo delante. Primero, sshd -T lee las claves de host, que bajo /etc/ssh solo son legibles para root. Segundo, sshd está en /usr/sbin, y en Debian ese directorio no forma parte del PATH de un usuario normal, razón por la cual sin sudo la llamada termina ahí con sshd: command not found. Quien quiera evitar el rodeo por sudo, escribe la ruta completa /usr/sbin/sshd.
A partir de OpenSSH 9.3, es decir en Ubuntu 24.04 (9.6p1) y Debian 13 (10.0p2), existe además sshd -G. La opción evalúa la misma configuración, pero no exige claves de host legibles ni un /run/sshd existente, lo que la hace útil para comprobaciones en automatización y contenedores. En Debian 12 (9.2p1), Ubuntu 22.04 (8.9p1) y Debian 11 (8.4p1) ese parámetro todavía no existe, y ahí sshd lo rechaza como opción desconocida. Para una guía que valga en todas partes, sudo sshd -T sigue siendo por eso la elección correcta.
Ahora la contraprueba, y además desde un terminal nuevo, mientras la sesión antigua permanece abierta. Fuerza un inicio de sesión por contraseña y desactiva las claves para este intento:
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password,keyboard-interactive root@203.0.113.10
Es correcto si te rechaza de inmediato y sin pedirte contraseña en ningún momento:
root@203.0.113.10: Permission denied (publickey).
Lo decisivo es el contenido del paréntesis. Si ahí pone (publickey,password) o aparece una petición de contraseña, el inicio de sesión por contraseña sigue abierto. Solo cuando la contraprueba falla limpiamente y una conexión normal por clave sigue funcionando, cierras la sesión antigua y detienes el rollback temporizado:
sudo systemctl stop ssh-rollback.timer
Mensajes de error al pie de la letra
Permission denied (publickey). El servidor solo acepta claves y la tuya no encaja. Ejecuta ssh -v y mira qué archivo se ofreció realmente. Causas más frecuentes: nombre de usuario equivocado, clave en el authorized_keys del usuario equivocado, o has bloqueado root y sigues iniciando sesión como root.
Authentication refused: bad ownership or modes for directory /home/hani/.ssh Esta línea no aparece en tu pantalla, sino en el log del servidor, visible con sudo journalctl -u ssh -n 50 --no-pager. No filtres con -t sshd: a partir de OpenSSH 9.8, en Debian 13 por tanto ya de fábrica, las sesiones corren en su propio proceso sshd-session, y bajo la etiqueta sshd quedan entonces solo mensajes del listener y ni un solo proceso de inicio de sesión. Quien quiera filtrar por etiquetas, usa sudo journalctl -t sshd -t sshd-session -n 50 --no-pager. OpenSSH rechaza las claves cuando el directorio personal, .ssh o authorized_keys tienen permiso de escritura para el grupo o para otros. La corrección:
chmod go-w ~ && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
WARNING: UNPROTECTED PRIVATE KEY FILE! o bien Load key "/home/hani/.ssh/id_ed25519": bad permissions. El mismo problema en el lado del cliente. chmod 600 ~/.ssh/id_ed25519 lo resuelve.
Too many authentication failures Tu agente ofrece por turnos todas las claves cargadas y con ello supera MaxAuthTries. Limita la conexión a una única clave: ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 hani@203.0.113.10.
sign_and_send_pubkey: no mutual signature supported Una clave RSA antigua con firma SHA-1 se encuentra con un servidor que ya no la acepta. Genera una clave ed25519 en lugar de andar tocando PubkeyAcceptedAlgorithms.
Bad owner or permissions on C:\Users\hani\.ssh\config El cliente de Windows comprueba los permisos de acceso de su archivo de configuración. En las propiedades del archivo, en Seguridad, quita la herencia y todas las entradas salvo tu cuenta de usuario y SYSTEM.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! La clave de host del servidor es distinta de la de la última vez. Después de una reinstalación es lo esperable, en otro caso no. Elimina la entrada antigua de forma selectiva con ssh-keygen -R 203.0.113.10, y solo si conoces el motivo.
Te has quedado fuera: la vía por la consola del área de cliente
Si las dos sesiones se han perdido y ya no se establece ninguna conexión, eso no es pérdida de datos, sino un rodeo. Cada servidor root KVM y cada servidor dedicado de KernelHost tiene en el área de cliente una consola que se apoya en la salida de pantalla del sistema y es independiente de la pila de red del servidor. Así que entras incluso cuando SSH ya no escucha en absoluto.
- Inicia sesión en el área de cliente, abre el servidor afectado y arranca la consola.
- En la petición de inicio de sesión, entra como root con la contraseña que se asignó durante el aprovisionamiento. El inicio de sesión por consola no pasa por SSH y no le afecta
PermitRootLogin. - Deshaz el cambio:
sudo rm /etc/ssh/sshd_config.d/01-hardening.conf - Comprueba y reinicia:
sudo sshd -t && sudo systemctl try-restart ssh.socket ssh.service - Si SSH no se ejecuta en absoluto, ayudan
sudo systemctl status ssh.socket ssh.servicey un vistazo asudo journalctl -u ssh -n 50 --no-pager. El código de retorno 3 destatussolo significa que una de las dos unidades está inactiva, y en Debian, conssh.socketdesactivado, ese es el caso normal.
Dos precauciones te ahorran este rodeo casi siempre. Apunta la contraseña de root antes de desactivar el inicio de sesión por contraseña, porque la consola la necesita. Y revisa si hay un firewall activo antes de mover el puerto SSH. Un cambio de puerto sin la regla correspondiente te deja fuera con la misma fiabilidad que un sshd_config roto, pero tiene otro aspecto: en lugar de Permission denied obtienes Connection timed out.
Resumen
- Deja abierta una segunda sesión hasta que una tercera conexión nueva funcione de forma demostrable.
- ed25519 con
-a 100y passphrase, la clave pública mediantessh-copy-id, en Windows mediante tubería. - Prueba el inicio de sesión por clave antes de que caiga el inicio de sesión por contraseña.
- El hardening va a
/etc/ssh/sshd_config.d/01-hardening.conf, no asshd_config. Gana el primer valor encontrado, de ahí el número bajo. - No olvides
KbdInteractiveAuthentication no, o el rodeo por PAM seguirá abierto. - Activa con
sudo sshd -t && sudo systemctl try-restart ssh.socket ssh.service, ni conreloadni con unrestart ssh.socketaislado. - Aclara antes con
systemctl is-enabled ssh.socket ssh.servicequé unidad está activa realmente. Ubuntu 24.04 usa el socket, Debian 13 y Debian 12 usan el servicio. - La prueba se hace con
sudo sshd -Ty un intento de contraseña forzado desde una sesión nueva. - La vía de emergencia es la consola del área de cliente, así que ten preparada la contraseña de root.
Las siguientes capas evidentes las describen nuestros artículos sobre fail2ban y sobre el firewall UFW. Más importante que ambas es lo que acabas de hacer.
Preguntas frecuentes
¿Por qué el inicio de sesión por contraseña sigue activo pese a PasswordAuthentication no?
¿Tengo que reiniciar el servicio después de cambiar sshd_config?
¿Por qué debería evitar reload?
¿Cómo llevo mi clave desde Windows al servidor si ahí no existe ssh-copy-id?
¿Cómo demuestro que el inicio de sesión por contraseña está realmente cerrado?
¿Qué hago si me he quedado fuera?
¿Es mejor PermitRootLogin no o prohibit-password?
2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

