Configurar un servidor root nuevo: checklist para los primeros 30 minutos

Publicado el 17 min de lectura

Los primeros 30 minutos en un servidor root nuevo son los que deciden. Nueve pasos en el orden correcto, con las trampas de Debian 13 y Ubuntu 24.04 que faltan en la mayoría de las guías.

Un servidor root nuevo es accesible desde el primer segundo y recibe escaneos desde el primer minuto. Por experiencia, los intentos de inicio de sesión automatizados contra el puerto 22 empiezan antes de que tú mismo hayas entrado por primera vez. Esta lista deja un servidor recién entregado, en aproximadamente media hora, en un estado en el que puedes dejarlo funcionando con tranquilidad.

Todos los comandos dan por supuesto que trabajas como root, tal como ocurre justo después del aprovisionamiento. En cuanto hayas iniciado sesión con tu nuevo usuario, antepón un sudo a cada comando. El orden está probado en Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS y Ubuntu 22.04 LTS. Donde los cuatro sistemas se diferencian, se indica.

Paso 0: asegura la vía de vuelta antes de cambiar nada

El único error de esta lista que no puedes reparar por SSH es justamente el que te quita el SSH. Por eso, durante los próximos 30 minutos rige una única regla de hierro: abre una segunda ventana de terminal con una conexión SSH activa y no la cierres. Una sesión SSH ya establecida sobrevive tanto a un reinicio del servicio SSH como a la activación del firewall. Si tras un cambio deja de funcionar una conexión nueva, deshaz ese cambio en la sesión que sigue abierta.

Además conviene que sepas dónde encontrar el acceso por consola a tu servidor antes de necesitarlo. En KernelHost lo tienes en el área de cliente, independiente de SSH e independiente del firewall. Quien empieza a buscar ese camino cuando ya está bloqueado fuera, pierde tiempo.

Hay dos mensajes de error que deberías saber distinguir, porque apuntan a causas completamente distintas:

  • ssh: connect to host 203.0.113.10 port 22: Connection refused significa que el paquete llegó, pero nadie escucha en ese puerto. El servicio SSH no está en marcha o escucha en otro puerto.
  • ssh: connect to host 203.0.113.10 port 22: Connection timed out significa que el paquete se descartó. Casi siempre es el firewall.
  • Permission denied (publickey) significa que el servicio funciona y el firewall te deja pasar, solo que tu clave no encaja.

Paso 1: actualizar el sistema

Una imagen recién instalada rara vez está al día. Entre la creación de la imagen y tu pedido suelen pasar semanas en las que han aparecido actualizaciones de seguridad.

cat /etc/os-release
apt update
apt full-upgrade -y

Usar full-upgrade en lugar de upgrade es una decisión consciente: en un sistema recién instalado, apt puede eliminar paquetes si una dependencia lo exige. En un sistema en producción ya en marcha, primero comprobarías qué se va a eliminar.

En Ubuntu 22.04 y 24.04, needrestart viene preinstalado e interrumpe la actualización con una pantalla completa de colores que pregunta qué servicios hay que reiniciar. Si no quieres eso, por ejemplo dentro de un script:

NEEDRESTART_MODE=a DEBIAN_FRONTEND=noninteractive apt full-upgrade -y

Después toca limpiar y comprobar si hace falta un reinicio:

apt autoremove --purge -y
apt list --upgradable
test -f /var/run/reboot-required && echo "reinicio necesario" || echo "no se requiere reinicio"

Diferencia entre distribuciones: El archivo /var/run/reboot-required solo lo crea Ubuntu de forma fiable, procede del paquete update-notifier-common. Debian, por defecto, no avisa en absoluto de que haga falta un reinicio. En Debian instala para eso needrestart, que al ejecutarlo te dice si hay un kernel nuevo esperando. Debian 13 trae además una generación más reciente de apt con salida en color y formateada por columnas, lo cual no es un fallo, solo resulta poco habitual.

Comprobación: apt list --upgradable ya no muestra nada aparte de la línea de cabecera Listing.... Si durante la actualización aparece el mensaje Release file for ... is not valid yet, el reloj de tu servidor va mal: salta al paso 5 y repite después el paso 1.

Más sobre el tema, también sobre el manejo de paquetes retenidos y repositorios de terceros: Actualizar un servidor Linux con apt.

Paso 2: crear un usuario en lugar de trabajar como root

Como root no se trabaja, porque cualquier errata afecta de inmediato a todo el sistema y porque cualquier atacante ya conoce el nombre de usuario root. En las imágenes mínimas de Debian, sudo a menudo ni siquiera está instalado:

apt install -y sudo
adduser --disabled-password --gecos "" kernel
usermod -aG sudo kernel

La opción --disabled-password crea el usuario sin contraseña, que es justo lo correcto para una autenticación solo por clave. Si además quieres asignar una contraseña, por ejemplo para usar sudo desde la consola, hazlo con passwd kernel.

Diferencia entre distribuciones: En Debian y Ubuntu, el grupo de administración se llama sudo. Solo si vienes de un sistema tipo RHEL buscarás wheel, que aquí no existe.

Ahora la clave pública. Crea el directorio con los permisos correctos, porque unos permisos incorrectos son la causa más frecuente de que se rechace la autenticación por clave:

mkdir -p /home/kernel/.ssh
chmod 700 /home/kernel/.ssh
touch /home/kernel/.ssh/authorized_keys
chmod 600 /home/kernel/.ssh/authorized_keys
chown -R kernel:kernel /home/kernel/.ssh

El contenido de tu clave pública lo añades a authorized_keys, aunque resulta más cómodo hacerlo desde tu equipo de trabajo con ssh-copy-id kernel@203.0.113.10.

Comprobación, y además antes de tocar la configuración de SSH:

id kernel
sudo -l -U kernel

La segunda línea tiene que contener (ALL : ALL) ALL. Inicia sesión después en una tercera ventana como kernel y ejecuta una vez sudo -v. Solo cuando eso funcione, sigue adelante. Detalles: Crear un usuario y configurar sudo y Crear y registrar claves SSH.

Paso 3: asegurar SSH

En las imágenes de Debian muy ligeras, el servidor SSH ni siquiera está instalado, así que primero tendrás que instalarlo:

apt install -y openssh-server

Los cuatro sistemas tratados aquí leen configuración adicional desde /etc/ssh/sshd_config.d/. Así que no edites el gran sshd_config, crea tu propio archivo. Ese sobrevive a las actualizaciones de paquetes sin preguntar nada.

Un detalle que casi todas las guías hacen mal: en la configuración de SSH gana el primer valor encontrado, no el último. En Debian y Ubuntu, la línea Include /etc/ssh/sshd_config.d/*.conf está justo al principio, y los archivos de ese directorio se leen en orden alfabético. En las imágenes de Ubuntu suele haber ya un 50-cloud-init.conf con PasswordAuthentication yes. Un archivo llamado 99-... quedaría por tanto sin ningún efecto. Mira primero qué hay:

ls -l /etc/ssh/sshd_config.d/
cat > /etc/ssh/sshd_config.d/10-kernelhost.conf <<'EOF'
PermitRootLogin prohibit-password
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
EOF

prohibit-password en lugar de no es una decisión consciente: root sigue pudiendo entrar con clave, pero nunca con contraseña. Eso te salva si algo sale mal con el usuario sudo. Quien quiera asegurar el sistema con más dureza pone no, pero entonces debería haber probado de verdad el acceso por consola.

Antes de cada reinicio del servicio, comprueba la sintaxis:

ssh-keygen -A
sshd -t && echo "configuración ok"
sshd -T | grep -E "^(permitrootlogin|passwordauthentication|pubkeyauthentication|port) "

sshd -T muestra los valores realmente efectivos, tras resolver todos los archivos incluidos. Es la única prueba fiable de que tu cambio ha llegado. Si el comando informa de sshd: no hostkeys available -- exiting, faltan las claves de host y ssh-keygen -A las genera. Si en cambio la comprobación se interrumpe con Missing privilege separation directory: /run/sshd, el servicio no se ha ejecutado ni una sola vez desde el arranque del sistema y falta el directorio de tiempo de ejecución. Un mkdir -p /run/sshd o un systemctl restart ssh lo crea, y después sshd -t vuelve a evaluar tu configuración.

Hay una salida que despista con frecuencia: para PermitRootLogin prohibit-password, sshd -T devuelve la línea permitrootlogin without-password. Es el mismo valor con su nombre antiguo y no indica que tu ajuste no haya surtido efecto.

Solo después de eso:

systemctl restart ssh

La trampa del socket en Ubuntu 24.04 y Debian 13

Desde Ubuntu 22.10, y por tanto también en 24.04, SSH se arranca mediante activación por socket. La consecuencia: una línea Port en la configuración de sshd se ignora, el puerto viene de ssh.socket. Quien quiera cambiar el puerto necesita un override de systemd:

systemctl edit ssh.socket

Ahí dentro va lo siguiente, teniendo en cuenta que la primera línea vacía borra el valor predeterminado:

[Socket]
ListenStream=
ListenStream=0.0.0.0:2222
ListenStream=[::]:2222

Después, systemctl daemon-reload y systemctl restart ssh.socket. Ubuntu 22.04 todavía no conoce este mecanismo, allí basta con la línea Port en la configuración.

En Debian 13 aparece una curiosidad emparentada. Algunas imágenes nuevas tienen ssh.socket activo, y los sistemas actualizados desde Debian 12 no. Si ambos están activos a la vez, un reload falla con fatal: Cannot bind any address., porque el servicio y el socket se pelean por el puerto 22. Comprueba y, en caso de duda, decide:

systemctl is-enabled ssh.socket
systemctl disable --now ssh.socket
systemctl enable --now ssh.service

Comprobación: ss -tlnp | grep ssh muestra el puerto esperado, y un nuevo intento de conexión desde una ventana recién abierta funciona. En detalle: Asegurar SSH y desactivar el acceso root y Cambiar el puerto SSH.

Paso 4: activar el firewall

En Ubuntu, ufw está instalado pero inactivo. En las imágenes mínimas de Debian falta por completo. Aquí el orden es vital: primero permitir SSH, después activar.

apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

El último comando es el más importante de toda la lista. Las reglas y las políticas por defecto, por sí solas, no filtran nada: solo ufw enable pone el firewall en marcha. Quien se lo salta acaba con un firewall completamente configurado, pero sin ningún efecto.

Al activarlo, ufw pregunta: Command may disrupt existing ssh connections. Proceed with operation (y|n)?. Esa advertencia va en serio, pero si la regla para el puerto 22 (o para el puerto que hayas cambiado) está antes, no pasa nada, y justo por eso ufw allow 22/tcp aparece en la lista por encima de ufw enable. Si en el paso 3 cambiaste el puerto, aquí tiene que ir ufw allow 2222/tcp, o te quedarás fuera. En un script o en una sesión no interactiva usa ufw --force enable, que se salta la pregunta.

Comprobación:

ufw status verbose

Se espera Status: active, debajo Default: deny (incoming), allow (outgoing) y, en la lista de reglas, una línea 22/tcp ALLOW IN para tu puerto SSH. Si ahí sigue poniendo Status: inactive, falta ufw enable y no hay nada protegido, por completas que parezcan las reglas. Abre después una ventana nueva y conéctate antes de cerrar la antigua.

A esto hay que sumar un mecanismo de bloqueo contra los intentos de inicio de sesión:

apt install -y fail2ban python3-systemd
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
backend = systemd
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
EOF

Por qué backend = systemd: Debian ya no instala rsyslog desde la versión 12, así que no existe ningún /var/log/auth.log. El valor por defecto backend = auto busca exactamente ese archivo y entonces fail2ban ni siquiera arranca, con el mensaje Failed during configuration: Have not found any log file for sshd jail. El paquete python3-systemd es el requisito para que el acceso al journal funcione siquiera. En Ubuntu 22.04 y 24.04, el archivo todavía existe gracias a rsyslog, pero la variante de systemd funciona allí igual de bien y es la opción con futuro. Puedes comprobarlo así:

test -f /var/log/auth.log && echo "auth.log presente" || echo "sin auth.log, hace falta backend systemd"
fail2ban-client -t

Comprobación: fail2ban-client status sshd muestra una línea Currently banned. Si en su lugar aparece Sorry but the jail 'sshd' does not exist, la configuración no se ha cargado. Más en Configurar el firewall UFW y Configurar Fail2ban.

Paso 5: zona horaria y hora del sistema

Un reloj mal ajustado deja los logs sin valor, hace fracasar las comprobaciones de certificados y puede bloquear apt con Release file is not valid yet. En un servidor real:

timedatectl set-timezone Europe/Vienna
timedatectl status

En la salida tienen que cuadrar dos líneas: Time zone: Europe/Vienna y System clock synchronized: yes, además de NTP service: active. Si ahí pone NTP service: inactive, no hay ninguna sincronización horaria en marcha. Ubuntu trae systemd-timesyncd de serie, las imágenes mínimas de Debian a menudo no:

DEBIAN_FRONTEND=noninteractive apt install -y systemd-timesyncd tzdata
date

Muchos operadores dejan sus servidores en UTC a propósito, para que los logs de distintas ubicaciones sigan siendo comparables. Ambas opciones son defendibles, lo decisivo es que tú lo sepas. Si timedatectl no está disponible en un entorno de contenedores, también se puede hacer a la manera clásica:

ln -sf /usr/share/zoneinfo/Europe/Vienna /etc/localtime
dpkg-reconfigure -f noninteractive tzdata

Para profundizar: Configurar la zona horaria y la sincronización de hora.

Paso 6: definir el hostname

El hostname aparece en los logs, en los correos salientes y en los avisos de monitorización. Defínelo pronto, o más adelante todos tus servidores se llamarán igual.

hostnamectl set-hostname srv01.tu-dominio.com
hostname -f

Después hace falta la entrada correspondiente en /etc/hosts, o cada llamada a sudo te recibirá con el mensaje sudo: unable to resolve host srv01: Name or service not known y un retardo perceptible. La línea es, en esencia, 127.0.1.1 srv01.tu-dominio.com srv01.

La trampa: En las imágenes con cloud-init, que es lo habitual en Ubuntu, el hostname se restablece en el siguiente reinicio. El interruptor para evitarlo:

command -v cloud-init || echo "cloud-init no instalado"
mkdir -p /etc/cloud/cloud.cfg.d
printf 'preserve_hostname: true\n' > /etc/cloud/cloud.cfg.d/99_hostname.cfg

Comprobación: Después de un reinicio, hostnamectl sigue devolviendo tu nombre. Detalles: Cambiar el hostname de forma permanente en Linux.

Paso 7: actualizaciones de seguridad automáticas

El servidor más peligroso es el que ya nadie toca. Las actualizaciones de seguridad automáticas son el paso individual más eficaz de esta lista.

apt install -y unattended-upgrades
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF

Instalar el paquete por sí solo no basta en todas partes, solo este archivo activa la ejecución diaria. Los ajustes propios van en un archivo con un número más alto que el 50unattended-upgrades incluido, para que ganen y no se sobrescriban en una actualización del paquete:

cat > /etc/apt/apt.conf.d/52unattended-upgrades-local <<'EOF'
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::MinimalSteps "true";
EOF

Por defecto, la herramienta solo toma paquetes del repositorio de seguridad en ambas distribuciones, no las actualizaciones normales. Es intencionado y para sistemas en producción suele ser justo lo adecuado. Si pones Automatic-Reboot "true", define sin falta también una hora, o el servidor se reiniciará cuando le venga bien al temporizador.

Comprobación: Una ejecución de prueba muestra qué paquetes entrarían en juego, sin instalar nada. Fíjate en el singular del nombre del comando:

unattended-upgrade --dry-run --debug
apt-config dump | grep -iE "unattended|periodic"

En el registro bajo /var/log/unattended-upgrades/ tiene que haber algo después de la primera ejecución. Si sigue vacío, la configuración no está surtiendo efecto. En detalle: Configurar actualizaciones de seguridad automáticas.

Paso 8: monitorización

Monitorización, en los primeros 30 minutos, no significa montar un Grafana. Significa que te enteres cuando el servidor se caiga o cuando el disco se llene.

apt install -y htop tmux curl
df -h /
free -m

Con tres cosas basta para empezar. Primera, una prueba de disponibilidad externa que compruebe desde fuera y te avise, porque un servidor que se ha caído ya no manda ninguna alerta sobre sí mismo. Segunda, un aviso de espacio en disco, porque un sistema de archivos lleno es la causa más frecuente de caídas que nadie vio venir. Tercera, una mirada al journal cuando algo resulte extraño:

journalctl -p 3 -b --no-pager | tail -n 30
systemctl --failed

systemctl --failed debería devolver 0 loaded units listed. Cada línea que aparezca ahí es un servicio que no arranca y que te conviene reparar ahora, no dentro de tres meses. Más sobre esto: Configurar la monitorización del servidor.

Paso 9: backup antes de que haya algo que perder

El mejor momento para el primer backup es antes de que haya datos. Así ensayas el procedimiento sin presión. Hay dos cosas que merece la pena guardar de inmediato, porque reconstruirlas es lo que más tiempo cuesta: la configuración bajo /etc y la lista de paquetes instalados.

tar -czf /root/etc-backup-$(date +%F).tar.gz /etc
dpkg --get-selections > /root/paquetes.txt
tar -tzf /root/etc-backup-$(date +%F).tar.gz | wc -l

Con eso todavía no tienes un backup, solo una copia en el mismo disco. Un backup está en otro sistema, idealmente en otra ubicación. Una herramienta con cifrado y deduplicación de bloques idénticos merece la pena desde el primer día:

apt install -y borgbackup
borg --version

La verdad incómoda: un backup del que nunca se ha restaurado nada es solo una suposición. Reserva una cita para la primera restauración y recupera un único archivo. El camino está en Estrategia de backup para servidores root.

Si te has quedado fuera

Pasa, casi siempre en el paso 3 o en el 4. La vuelta es siempre la misma: entrar por la consola del área de cliente, allí con nombre de usuario y contraseña en lugar de con clave. Después, según la causa:

  • Firewall demasiado estricto: ufw disable, corregir la regla, ufw enable.
  • Configuración de SSH rota: rm /etc/ssh/sshd_config.d/10-kernelhost.conf, después sshd -t y systemctl restart ssh.
  • Puerto equivocado tras cambiar el socket: systemctl revert ssh.socket deshace el override, después systemctl daemon-reload y systemctl restart ssh.socket.
  • Bloqueado por fail2ban: fail2ban-client set sshd unbanip 203.0.113.10. Para que no te vuelva a pasar, añade tu dirección fija bajo ignoreip en el jail.local.
  • La clave se rechaza: Casi siempre son los permisos. chmod 700 en el directorio, chmod 600 en el archivo, y ambos tienen que pertenecer al usuario, no a root.

La comprobación final

Que un comando no devuelva ningún error no significa que haya surtido efecto. Estas seis comprobaciones muestran el estado real:

  1. sshd -T | grep -E "^(permitrootlogin|passwordauthentication|port) " muestra los valores efectivos, no los deseados.
  2. ufw status verbose indica Status: active con una regla para tu puerto SSH.
  3. timedatectl status indica System clock synchronized: yes.
  4. systemctl --failed no lista nada.
  5. unattended-upgrade --dry-run --debug se ejecuta sin mensajes de error.
  6. Una conexión SSH nueva desde una ventana recién abierta funciona, con clave y sin que te pida contraseña.

Solo cuando el punto seis funcione puedes cerrar la ventana de terminal antigua.

Lo que viene después

Una palabra sobre la elección de la distribución, porque decide los próximos años. Debian 12 salió del soporte regular en julio de 2026 y el equipo LTS lo seguirá manteniendo hasta mediados de 2028, con un conjunto de paquetes limitado. Quien monta hoy un sistema nuevo elige Debian 13 o Ubuntu 24.04 LTS. Ubuntu 22.04 LTS sigue en soporte estándar hasta 2027, pero para un servidor pensado para funcionar durante años ya no es la primera opción. Debian 10 y Ubuntu 20.04 están definitivamente terminados desde junio de 2024 y mayo de 2025 respectivamente, y no tienen sitio en ningún servidor nuevo.

También son relevantes en la práctica las diferencias de versión en los repositorios. Debian no incluye mysql-server en absoluto, allí MariaDB es el estándar. Quien necesite una versión concreta de PHP, Node o Java hará bien en comprobar antes qué trae la distribución, en lugar de añadir después repositorios de terceros. Y si añades repositorios de terceros: apt-key está obsoleto, las claves van en /etc/apt/keyrings/ y se referencian en la línea del repositorio mediante signed-by.

Con esto tienes un servidor actualizado, que se mantiene al día por sí solo, que solo te deja entrar a ti y que avisa cuando algo no va bien. Todo lo demás, servidor web, base de datos, certificados, se construye encima de eso y no al lado.

Preguntas frecuentes

¿En qué orden debo configurar un servidor root nuevo?
Primero actualizar el sistema, después crear un usuario con permisos sudo y su clave SSH ya registrada, luego asegurar SSH y a continuación activar el firewall. Solo después vienen la zona horaria, el hostname, las actualizaciones de seguridad automáticas, la monitorización y el backup. Lo importante es una sola cosa: el usuario y la clave tienen que funcionar antes de que desactives la autenticación por contraseña, y la regla de SSH tiene que estar en el firewall antes de que lo actives.
¿Por qué no surte efecto mi cambio en sshd_config?
En Debian y Ubuntu, la línea Include /etc/ssh/sshd_config.d/*.conf está justo al principio de sshd_config, y en la configuración de SSH gana el primer valor encontrado. Por eso un archivo como 50-cloud-init.conf se impone tanto al archivo principal como a un archivo propio con un número más alto. Comprueba con sshd -T qué valores son realmente efectivos y ponle a tu archivo un número más bajo, por ejemplo 10-kernelhost.conf.
¿Por qué no cambia mi puerto SSH en Ubuntu 24.04?
Desde Ubuntu 22.10, SSH arranca mediante activación por socket. El puerto procede entonces de ssh.socket y la línea Port de la configuración de sshd se ignora. Necesitas un override de systemd creado con systemctl edit ssh.socket, con una línea ListenStream vacía que borre el valor predeterminado y después el valor que quieras. En Ubuntu 22.04 sigue bastando con la línea Port.
Fail2ban no arranca y avisa de que no ha encontrado ningún archivo de log para el jail sshd. ¿Qué hago?
Debian ya no instala rsyslog desde la versión 12, por eso no existe ningún /var/log/auth.log. Pon en /etc/fail2ban/jail.local, bajo [DEFAULT], la entrada backend = systemd e instala el paquete python3-systemd. A partir de ahí, fail2ban lee directamente del journal. Puedes comprobarlo con fail2ban-client -t y después con fail2ban-client status sshd.
¿Por qué mi hostname vuelve a desaparecer después del reinicio?
En las imágenes con cloud-init, que es lo habitual en Ubuntu, el hostname se vuelve a definir en cada arranque. Crea /etc/cloud/cloud.cfg.d/99_hostname.cfg con la línea preserve_hostname: true y tu ajuste se mantendrá. Añade además la entrada correspondiente en /etc/hosts, o sudo avisará en cada llamada de que no puede resolver el host.
¿Basta con apt upgrade o necesito apt full-upgrade?
En un servidor recién aprovisionado, full-upgrade es la mejor opción, porque también instala actualizaciones para las que hay que eliminar o sustituir paquetes. En un sistema en producción ya en marcha deberías comprobar primero con apt list --upgradable y una simulación qué pasaría, antes de ejecutar full-upgrade.

Servidor root Configurar servidor Linux SSH Firewall Debian Ubuntu Seguridad del servidor Checklist