La autenticación de dos factores (2FA) exige algo que tienes además de algo que sabes. En SSH, la combinación más práctica es una clave SSH más un código temporal de seis dígitos (TOTP) generado en el móvil: aunque alguien robe tu clave privada, no podrá entrar sin el teléfono. En este tutorial instalarás el módulo PAM de Google Authenticator en Ubuntu 24.04, lo configurarás para tu usuario y ajustarás SSH para que pida clave y código. Los códigos funcionan con cualquier aplicación TOTP: Google Authenticator, Aegis, Microsoft Authenticator, 1Password o Bitwarden.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo que ya entra por clave SSH. Si aún usas contraseña, sigue primero la guía de gestión de claves SSH de esta sección.
  • Un móvil con una aplicación TOTP instalada.
  • Acceso a la consola web de tu proveedor por si te quedas fuera, y una sesión SSH abierta durante todo el proceso.

Paso 1: Comprobar la hora del servidor

Los códigos TOTP dependen de la hora: el servidor y el móvil calculan el mismo código a partir de un secreto compartido y del reloj. Si el reloj del servidor se desvía más de un minuto o dos, los códigos se rechazarán. Comprueba que está sincronizado:

timedatectl
               Local time: Fri 2026-09-25 10:40:12 UTC
           Universal time: Fri 2026-09-25 10:40:12 UTC
                 RTC time: Fri 2026-09-25 10:40:12
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active

Si System clock synchronized es no, activa la sincronización:

sudo timedatectl set-ntp true

Paso 2: Instalar el módulo PAM de Google Authenticator

El paquete libpam-google-authenticator incluye el módulo PAM que verifica los códigos y la herramienta google-authenticator que genera el secreto de cada usuario:

sudo apt update
sudo apt install libpam-google-authenticator

Verifica que el módulo está en su sitio:

ls /usr/lib/x86_64-linux-gnu/security/pam_google_authenticator.so

En servidores ARM la ruta es /usr/lib/aarch64-linux-gnu/security/.

Paso 3: Generar el secreto para tu usuario

Ejecuta google-authenticator con tu usuario, sin sudo: el secreto se guarda en ~/.google_authenticator del usuario que ejecuta el comando. Las opciones evitan las preguntas interactivas:

google-authenticator -t -d -f -r 3 -R 30 -w 3
OpciónEfecto
-tCódigos basados en tiempo (TOTP), los que usan las aplicaciones móviles.
-dCada código solo se puede usar una vez.
-fEscribe el archivo sin pedir confirmación.
-r 3 -R 30Máximo 3 intentos de inicio de sesión cada 30 segundos.
-w 3Acepta el código actual, el anterior y el siguiente para tolerar pequeñas desviaciones de reloj.

La herramienta muestra un código QR en la terminal, la clave secreta y te pide un código para confirmar:

Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/your_user@web01%3Fsecret%3D...

[código QR]

Your new secret key is: JBSWY3DPEHPK3PXPJBSWY3DPEH
Enter code from app (-1 to skip): 482913
Code confirmed
Your emergency scratch codes are:
  18273645
  90817263
  55120938
  71029384
  46382910

Escanea el código QR con la aplicación del móvil (si la terminal no lo muestra bien, amplía la ventana o introduce la clave secreta a mano). Después escribe el código de seis dígitos que muestra la aplicación.

Comprueba que el archivo existe y que solo tu usuario puede leerlo:

ls -l ~/.google_authenticator
-r-------- 1 your_user your_user 146 Sep 25 10:45 /home/your_user/.google_authenticator

Paso 4: Configurar PAM para SSH

PAM decide qué se pide durante la autenticación. En Ubuntu, /etc/pam.d/sshd incluye common-auth, que solicita la contraseña del sistema. Como el primer factor será la clave SSH, sustituye esa línea por el módulo de Google Authenticator. Haz primero una copia:

sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.bak
sudo nano /etc/pam.d/sshd

Comenta la línea @include common-auth y añade debajo el módulo:

# Standard Un*x authentication.
#@include common-auth
auth required pam_google_authenticator.so nullok

La opción nullok permite entrar sin código a los usuarios que todavía no han ejecutado google-authenticator. Es útil para desplegar 2FA de forma gradual en servidores con varios usuarios; la quitarás en el paso 7.

Paso 5: Configurar SSH para exigir clave y código

Ahora indica a sshd que use el método keyboard-interactive (el que pasa por PAM y pide el código) y que exija los dos métodos en orden: primero clave pública y después código. Crea un archivo de configuración adicional:

sudo nano /etc/ssh/sshd_config.d/10-2fa.conf
UsePAM yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

En Ubuntu 24.04 el archivo principal /etc/ssh/sshd_config incluye KbdInteractiveAuthentication no, pero los archivos de sshd_config.d/ se leen antes y, para cada opción, gana el primer valor encontrado. Si en ese directorio tienes otro archivo que se ordene antes que 10-2fa.conf y ponga KbdInteractiveAuthentication no, elimina esa línea de él.

Valida la sintaxis y comprueba la configuración efectiva:

sudo sshd -t
sudo sshd -T | grep -Ei '^(usepam|kbdinteractiveauthentication|authenticationmethods|passwordauthentication) '
usepam yes
kbdinteractiveauthentication yes
passwordauthentication no
authenticationmethods publickey,keyboard-interactive

Aplica los cambios sin cerrar tu sesión:

sudo systemctl reload ssh

Paso 6: Probar el acceso con 2FA

Abre una nueva terminal en tu equipo local, sin cerrar la sesión actual, y conéctate:

ssh your_user@your_server_ip

Tras autenticarse con la clave, SSH pedirá el código de la aplicación:

(your_user@your_server_ip) Verification code:
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-84-generic x86_64)

Comprueba en el registro del servidor que se usaron los dos métodos:

sudo journalctl -u ssh -n 20 --no-pager | grep Accepted
sshd[3120]: Accepted publickey for your_user from 198.51.100.7 port 52114 ssh2: ED25519 SHA256:mQ3b7l1xT0H9...
sshd[3120]: Accepted keyboard-interactive/pam for your_user from 198.51.100.7 port 52114 ssh2

Prueba también que un código incorrecto se rechaza. Si todo funciona, ya puedes cerrar la sesión antigua. Si algo falla, restaura la configuración desde la sesión que dejaste abierta:

sudo cp /etc/pam.d/sshd.bak /etc/pam.d/sshd
sudo rm /etc/ssh/sshd_config.d/10-2fa.conf
sudo systemctl reload ssh

Paso 7: Exigir 2FA a todos los usuarios

Cada usuario que entre por SSH debe ejecutar google-authenticator en su propia sesión (paso 3) y escanear su propio código. No generes los secretos por ellos: el secreto es personal y debe quedarse solo en su móvil.

Para ver qué usuarios con shell aún no tienen 2FA configurado:

for home in /home/*; do sudo test -f "$home/.google_authenticator" || echo "Sin 2FA: $home"; done

Cuando todos lo tengan configurado, quita nullok de /etc/pam.d/sshd para que ningún usuario pueda entrar sin código:

sudo nano /etc/pam.d/sshd
auth required pam_google_authenticator.so

Los cambios en PAM se aplican en el siguiente inicio de sesión, sin reiniciar SSH. Vuelve a probar desde una nueva terminal.

Paso 8 (opcional): No pedir código desde la red interna

Si administras el servidor desde una red privada de confianza, por ejemplo una VPN, puedes omitir el código para esas direcciones con pam_access. Crea el archivo de reglas:

sudo nano /etc/security/access-2fa.conf
+ : ALL : 10.8.0.0/24
- : ALL : ALL

Sustituye 10.8.0.0/24 por tu red interna. Después añade una línea en /etc/pam.d/sshd justo antes del módulo de Google Authenticator:

auth [success=1 default=ignore] pam_access.so accessfile=/etc/security/access-2fa.conf
auth required pam_google_authenticator.so

Si la IP de origen coincide con una regla +, PAM salta la línea siguiente y no pide el código; en cualquier otro caso continúa y lo exige. La clave SSH sigue siendo obligatoria siempre. Comprueba ambos casos: desde la red interna no debe pedir código y desde fuera sí.

Recuperar el acceso si pierdes el móvil

  • Con un código de emergencia: escríbelo cuando SSH pida Verification code. Cada código solo vale una vez; después genera un secreto nuevo con google-authenticator para tener códigos frescos.
  • Sin códigos de emergencia: otro administrador puede borrar tu archivo con sudo rm /home/your_user/.google_authenticator. Si nullok está activo entrarás solo con la clave; si no, el administrador debe regenerarlo contigo. Si no hay otro administrador, entra por la consola web del proveedor, que no pasa por SSH.

Solución de problemas

Los códigos se rechazan siempre. Casi siempre es la hora. Comprueba timedatectl en el servidor y que el móvil tiene la hora automática activada. Revisa el registro:

sudo journalctl -u ssh -n 30 --no-pager | grep -i google

Un mensaje Invalid verification code con la hora correcta indica que escaneaste un QR antiguo; genera el secreto de nuevo.

SSH pide la contraseña además del código. La línea @include common-auth sigue activa en /etc/pam.d/sshd. Coméntala como en el paso 4.

SSH no pide el código y entra solo con la clave. Comprueba con sudo sshd -T | grep -i -E 'authenticationmethods|kbdinteractive' que los valores son los del paso 5; otro archivo de sshd_config.d/ puede estar sobrescribiéndolos. Si los valores son correctos y el usuario no tiene ~/.google_authenticator, es el efecto de nullok.

Permission denied (keyboard-interactive) con el código correcto. El módulo no puede leer el secreto. El archivo debe pertenecer al usuario y tener permisos 400 o 600:

chmod 400 ~/.google_authenticator

Tras un código válido, el siguiente inicio de sesión falla. Con -d cada código se usa una sola vez; espera a que la aplicación muestre uno nuevo (30 segundos).

Conclusión

Has instalado el módulo PAM de Google Authenticator, generado tu secreto TOTP con códigos de emergencia y configurado SSH para exigir clave pública y código, con un despliegue gradual mediante nullok y una excepción opcional para la red interna. A partir de ahora, una clave privada robada no basta para entrar en el servidor.

Como siguientes pasos puedes:

  • Revisar el resto de la configuración del servidor con una auditoría de seguridad o con Lynis.
  • Instalar Fail2ban para bloquear las IP que acumulan intentos fallidos.
  • Sustituir el TOTP por llaves de seguridad FIDO2 con claves ed25519-sk.