Qué ataca de verdad a un servidor nuevo — y qué es lo que realmente te lo quita
Apunta una IPv4 recién asignada a internet y el tráfico no solicitado llega casi de inmediato. No porque nadie te haya visto: todo el espacio de direcciones se barre de forma continua, y un host que responde en el puerto 22 simplemente se suma a una cola que ya estaba en marcha. Durante el primer día, un servidor nuevo típico registra entre varios cientos y varias decenas de miles de intentos de autenticación SSH, casi todos contra root, admin, ubuntu, test y una docena más de nombres predecibles, con contraseñas de una lista que apenas ha cambiado en una década. Parece un ataque. Se parece más al clima.
La distinción importa, porque decide qué controles merecen tus quince minutos. La adivinación de contraseñas a ese volumen no se reduce al desactivar la autenticación por contraseña — se elimina. No queda riesgo residual que gestionar, nada que ajustar, ninguna tasa que vigilar. Mientras tanto, las dos cosas que de verdad les quitan servidores pequeños a sus dueños son poco glamurosas: una aplicación expuesta a internet que ejecuta una versión con una vulnerabilidad publicada desde hace meses, y una credencial que se filtró en otro sitio y que aquí seguía funcionando. Ninguna de las dos es exótica, y ninguna tiene nada que ver con en qué puerto escucha SSH.
Hay un tercer modo de fallo, y en un servidor que nunca te preguntó quién eras es el más común de todos. Te quedas fuera tú mismo. En un proveedor convencional eso es un ticket de soporte molesto: respondes algunas preguntas, demuestras que eres la persona que figura en la factura, y alguien te restablece el acceso. Aquí ese camino no existe — no como una decisión de política tomada a regañadientes, sino porque la identidad que habría que comprobar nunca se recopiló. Lo que existe en su lugar es un acceso ligado a la cuenta y no a una persona: una consola de emergencia KVM-over-VNC en el panel, instantáneas cada hora con siete días de retención, y la posibilidad de arrancar con otra cosa completamente distinta. Con eso basta. Pero solo basta si ya las has usado una vez antes de necesitarlas.
Por eso la checklist de abajo está ordenada por consecuencia y no por moda. Los controles que eliminan toda una clase de ataque van primero, los que reducen el ruido van al final, y el paso que no cuesta nada y te salva la noche — comprobar que todavía puedes entrar — va antes que todos los demás.
| Qué preocupa a la gente | Cómo llega en realidad | Qué lo detiene | Qué no lo detiene |
|---|---|---|---|
| Adivinación de contraseñas SSH | Barridos automatizados, miles al día, listas de nombres de usuario genéricos | PasswordAuthentication no — por completo | Un puerto no estándar; una contraseña más larga |
| Servicio con vulnerabilidad conocida | Un escáner coincide con tu banner de versión semanas después de publicarse el parche | Actualizaciones de seguridad automáticas más una política de reinicio | Un cortafuegos, si el puerto tiene que estar abierto de todos modos |
| Credencial reutilizada o filtrada | Inicio de sesión válido al primer intento, desde una dirección que nunca has visto | Autenticación solo por clave; credenciales únicas por servicio | La limitación de velocidad — un solo intento no es una ráfaga |
| Servicio interno expuesto | Base de datos, caché o endpoint de métricas vinculado a la dirección comodín | Vincular a loopback; denegación por defecto del tráfico entrante en ambas familias | Suponer que es privado porque nunca lo enlazaste |
| Perder tu propio acceso | Bastionas, cierras sesión, y el cambio que hiciste estaba mal | Una segunda sesión abierta, una instantánea, una consola ya probada | La confianza |
Construye la salida antes de cerrar la puerta
Esta es la secuencia que deja a la gente tirada, y cada paso, por separado, es correcto: editar la configuración de SSH, cambiar el puerto, desactivar el inicio de sesión por contraseña, activar un cortafuegos, recargar el daemon, cerrar la terminal. El error no está en ningún cambio individual. Está en que la sesión desde la que estabas escribiendo era la única prueba de que alguno de ellos funcionaba, y la descartaste antes de comprobarlo.
La regla que hace segura toda la checklist cabe en una frase: mantén abierta la sesión que funciona y demuestra el cambio desde una segunda. Abre una terminal nueva, conéctate desde cero, ejecuta id, escala con sudo. Si funciona, el cambio es real y la primera sesión pasa a ser prescindible. Si falla, todavía tienes una shell con la que deshacerlo. Esto vale para el cortafuegos, para sshd, para la clave de un usuario nuevo, y para cualquier otra cosa que pueda rechazarte en la puerta.
Antes que nada, establece la vía fuera de banda. En el panel de BitVPS, cada fila de servidor tiene un botón Consola que abre una sesión KVM-over-VNC contra la pantalla de la máquina virtual. No es una shell por red — es la pantalla y el teclado de la máquina — así que sigue funcionando cuando sshd no arranca, cuando el cortafuegos descarta todos los paquetes entrantes, y cuando la configuración de red está mal. Ábrela una vez, ahora mismo, en un servidor que no hayas roto. Confirma que te aparece un prompt de inicio de sesión. Eso lleva treinta segundos y es la diferencia entre un error y una caída del servicio.
Toma también una instantánea. Son cada hora, con siete días de retención en todos los planes, y se restauran en unos treinta segundos, lo que reduce el "pegué la regla nft equivocada" de toda una noche de recuperación a una simple reversión. Y para los cambios de los que de verdad no estás seguro, un interruptor de hombre muerto cuesta una sola línea: systemd-run --on-active=10min /usr/sbin/ufw --force disable arma un temporizador que deshace el cortafuegos a los diez minutos a menos que lo canceles. Si las reglas nuevas funcionan, cancelas. Si te dejan fuera, la máquina te deja volver a entrar sin que tengas que estar allí.
SSH: las cuatro directivas que hacen el trabajo — y el archivo que las anula
Genera la clave en tu propia máquina, nunca en el servidor: ssh-keygen -t ed25519 -C "laptop-2026". Ed25519 es la opción por defecto correcta en 2026 — claves cortas, verificación rápida, sin parámetros que puedas elegir mal, y compatible con todos los OpenSSH compilados en la última década. RSA a 4096 bits no es inseguro, simplemente es más grande y más lento sin ninguna ventaja. Pon una frase de contraseña a la clave privada; ssh-agent hace que la escribas una sola vez por sesión, y es lo único que se interpone entre un portátil robado y todos los servidores que tienes.
Copia la mitad pública con ssh-copy-id y confirma que puedes iniciar sesión con ella antes de desactivar nada. Los permisos importan, y OpenSSH es estricto con ellos por diseño: ~/.ssh en 700, authorized_keys en 600, ambos propiedad del usuario. Una clave que falla en silencio casi siempre es un problema de permisos, y sshd -e -d en primer plano te lo dirá en una sola línea.
Después, cuatro directivas en /etc/ssh/sshd_config hacen prácticamente todo el trabajo. Todo lo demás en ese archivo es cuestión de preferencia.
| Directiva | Ponla en | Qué hace | Qué se rompe si te equivocas |
|---|---|---|---|
PasswordAuthentication | no | Elimina por completo las contraseñas como método de inicio de sesión — toda la avalancha de fuerza bruta se vuelve imposible, no solo más lenta | Te quedas fuera si tu clave nunca llegó a instalarse de verdad. Pruébalo antes, en una segunda sesión |
KbdInteractiveAuthentication | no | Cierra la segunda puerta a la misma habitación — la vía interactiva por teclado de PAM todavía puede aceptar una contraseña aunque la primera directiva esté activada | Rompe las configuraciones legítimas de doble factor que dependen de PAM. Déjala activada solo si usas una |
PermitRootLogin | prohibit-password | Root todavía puede iniciar sesión con una clave (útil para automatización) pero nunca con una contraseña | no es más estricto y está bien — siempre que tu usuario sudo funcione. Verifícalo primero |
PubkeyAuthentication | yes | El método que conservas. Aquí es mejor ser explícito que confiar en el valor por defecto | Nada, pero merece la pena dejarlo explícito para que una edición futura no lo elimine sin darte cuenta |
AllowUsers / AllowGroups | tu usuario o grupo | Opcional, y la línea opcional de mayor valor — el daemon rechaza a cualquier otra cuenta antes de la autenticación | Un error tipográfico deniega a todo el mundo. Configúrala en último lugar, y desde una segunda sesión |
Ahora viene la parte que atrapa incluso a gente con experiencia. En Debian 12, Ubuntu 22.04 y todo lo posterior, /etc/ssh/sshd_config empieza con Include /etc/ssh/sshd_config.d/*.conf, y OpenSSH usa el primer valor que obtiene para cada palabra clave. Las imágenes cloud traen archivos drop-in en ese directorio — normalmente uno que fija PasswordAuthentication yes — y como el include está al principio, ese drop-in gana a la línea que editaste con cuidado doscientas líneas más abajo. El archivo dice una cosa y el daemon hace otra.
La solución es no confiar nunca en el archivo. Pregúntale al daemon qué resolvió en realidad: sshd -T | grep -Ei 'passwordauth|permitrootlogin|kbdinteractive|pubkeyauth' imprime la configuración efectiva después de aplicar cada include, cada bloque match y cada valor por defecto. Ese único comando vale más que el resto de esta sección. Combínalo con sshd -t, que valida la sintaxis y sale con un código distinto de cero si hay un error, y solo entonces systemctl reload ssh — un reload no altera las sesiones existentes, así que incluso un reload equivocado deja viva tu shell actual.
En cuanto a mover SSH del puerto 22: elimina quizá el 95% de tu volumen de registros y cero por ciento de tu riesgo. Cualquier escáner que merezca preocuparte barre los 65,535 puertos completos, y los que no lo hacen de todas formas nunca iban a superar la autenticación solo por clave. Muévelo si el ruido te molesta o si mantiene callada una alerta de monitorización, pero no lo cuentes como un control de seguridad — y si lo mueves, mantenlo por debajo de 1024, porque solo los procesos con privilegios pueden vincularse a esos puertos, así que nada sin privilegios puede ocupar el puerto si el daemon llega a detenerse.
El cortafuegos: denegar el tráfico entrante por defecto, y la mitad que todo el mundo olvida
En un servidor de propósito único, el cortafuegos tiene un solo trabajo: hacer que el conjunto de puertos accesibles sea igual al conjunto de puertos para los que puedes nombrar un motivo. Denegar el tráfico entrante por defecto, permitir el saliente, permitir que vuelvan las conexiones establecidas y relacionadas, y luego abrir las dos o tres cosas que de verdad ejecutas. Si usas Debian o Ubuntu, ufw son cuatro comandos y es la opción correcta; si prefieres tener un único archivo de reglas legible, nftables es la respuesta moderna dentro del kernel, y es lo que hay debajo escribiendo las reglas de ufw.
El orden importa en un único punto: permite tu puerto SSH antes de activar la política. ufw allow 22/tcp, luego ufw default deny incoming, luego ufw enable. Si lo inviertes, el paso de activación corta la sesión desde la que estás escribiendo — algo sobrevivible si ya hiciste la sección anterior y todavía tienes una consola, y si no, es la forma clásica de terminar la noche antes de tiempo.
La mitad olvidada es IPv6. Aquí, todos los servidores vienen con un /64 enrutado, y los servicios se vinculan a ambas familias por defecto. Un conjunto de reglas escrito solo con iptables gobierna IPv4 y no dice absolutamente nada sobre IPv6, así que la base de datos que protegiste con tanto cuidado sigue siendo accesible desde todo internet a través del otro protocolo. ufw gestiona ambas cuando IPV6=yes está activado en /etc/default/ufw (el valor por defecto en las versiones actuales — compruébalo, no lo des por hecho), y nftables esquiva el problema de forma estructural: un conjunto de reglas table inet cubre ambas familias con un solo juego de reglas. Si vas a escribir a mano una sola línea de configuración de cortafuegos, que sea esa.
Luego está Docker, que no tanto evita tu cortafuegos como llega por debajo de él. Publicar un puerto con -p 5432:5432 inserta reglas DNAT en la tabla nat, y esas se evalúan antes de las cadenas de filtro que gestiona ufw. El resultado es un contenedor accesible desde internet mientras ufw status muestra el puerto como cerrado, y cada año esto sorprende a gente que ejecuta una base de datos en un contenedor detrás de lo que creían que era una política de denegar todo. Publica en loopback en su lugar — -p 127.0.0.1:5432:5432 — o pon el contenedor en una red interna y accede a él desde otro contenedor por nombre.
| Lo que crees | Lo que es cierto en realidad | Cómo comprobarlo con un comando |
|---|---|---|
| "El cortafuegos deniega todo lo que no permití" | Cierto solo para IPv4, si escribiste reglas exclusivas de IPv4 | ip6tables -L -n or nft list ruleset |
| "La base de datos no está expuesta, está en un contenedor" | Un puerto publicado es accesible sin importar la política de filtrado | docker ps --format '{{.Ports}}' |
| "No hay nada escuchando en ese puerto" | Algo se reinició y volvió a vincularse a él después de la última vez que miraste | ss -tulpn |
| "El puerto está cerrado, mi escaneo lo dice" | Un escaneo desde la propia máquina prueba loopback, no internet | Escanea desde un segundo host, o desde tu portátil |
Qué está escuchando en realidad — el comando que acaba con las suposiciones
Un solo comando responde a la pregunta más útil sobre la exposición de un servidor: ss -tulpn. Lista todos los sockets TCP y UDP a la escucha junto con el proceso que los posee, y la columna que importa es la dirección local. 127.0.0.1:5432 significa que solo esta máquina puede conectarse. 0.0.0.0:5432 significa que puede hacerlo cualquier dirección que alcance la máquina, sujeto solo al cortafuegos. [::]:5432 significa lo mismo por IPv6, y es la línea que la gente se salta de un vistazo.
Los culpables habituales son siempre el mismo puñado. Redis, que durante la mayor parte de su historia se vinculaba ampliamente y venía sin autenticación. PostgreSQL y MySQL, cuando alguien editó un archivo de configuración para aceptar conexiones "desde la app" y la app se mudó de sitio. MongoDB y Elasticsearch, que entre los dos han producido una década de titulares sobre exposición de datos exactamente por este motivo. Memcached, cuyo listener UDP se convirtió en un amplificador de ancho de banda muy popular. Prometheus node_exporter, que con gusto le contará los detalles internos de tu host a quien se lo pregunte. La API de Docker en el puerto 2375, que es ejecución remota de código disfrazada de interfaz REST. Y un servidor de desarrollo que alguien arrancó "solo un minuto" en un panel de tmux hace once meses.
La regla es simple y no exige criterio: si un servicio no necesita ser accesible desde internet, vincúlalo a loopback. Todo lo que tú necesitas para acceder a él sigue funcionando — ssh -L 5432:127.0.0.1:5432 user@server te da un puerto local que tunelea sobre la conexión en la que ya confías, y si varias máquinas necesitan acceso, una interfaz privada de WireGuard les da un rango de direcciones que nunca toca la internet pública. Ninguna de las dos opciones cuesta nada, y ambas eliminan el servicio de la superficie de ataque por completo en lugar de limitarse a defenderlo.
Verifica desde fuera, no desde dentro. Que tu propia máquina informe de un puerto como filtrado es una afirmación sobre tu propia pila loopback; que una segunda máquina no pueda alcanzarlo es una prueba. Conéctate desde tu portátil, o desde otro servidor, y comprueba los puertos que esperas que estén abiertos y uno que esperas que esté cerrado — una prueba que solo puede salir bien no es una prueba.
| Servicio | Valor por defecto habitual | Qué entrega una instancia expuesta | La solución |
|---|---|---|---|
| Redis | Vinculación amplia, históricamente sin contraseña | Lectura/escritura completa, y una vía muy conocida para escribir archivos como el usuario redis | Vincula a loopback; configura requirepass; mantén activado protected-mode |
| PostgreSQL / MySQL | Loopback, hasta que alguien lo edita | Todo tu conjunto de datos, si una credencial es débil o está reutilizada | Vincula a loopback; accede por SSH o WireGuard |
| Docker API | Desactivada — a menos que un tutorial la activara | Ejecución de código equivalente a root en el host | Nunca la expongas; usa un contexto SSH en su lugar |
| node_exporter / metrics | Todas las interfaces, puerto 9100 | Nombres de host, puntos de montaje, versiones, procesos en ejecución | Vincula a loopback; recoge las métricas por la interfaz privada |
| Envío de correo, paneles de administración web | Todas las interfaces por necesidad | Una superficie de adivinación de contraseñas que las claves no pueden proteger | Aquí es donde la limitación de velocidad de verdad tiene su sitio |
Fail2ban y CrowdSec: cuánto valen una vez que las contraseñas ya no existen
Este es el elemento que la mayoría de las checklists ponen cerca del principio y que la mayoría de los operadores sobrevaloran, así que merece la pena ser precisos. Con PasswordAuthentication no, una adivinación de contraseña no puede tener éxito — no raramente, no con dificultad, sino nunca. Bloquear una dirección tras cinco fallos no hace más imposible una autenticación que ya es imposible. Lo que fail2ban te da a nivel de SSH es un auth.log más silencioso, algo menos de CPU gastada en establecer conexiones, y gráficas de monitorización más fáciles de leer. Esos son beneficios reales. Son higiene operativa, no un control de seguridad, y catalogarlos bajo el epígrafe equivocado es la forma en que la gente termina con un archivo de registro bastionado y una aplicación sin parchear.
Donde esta clase de herramienta de verdad se gana su sitio es en cada capa donde todavía existe una contraseña: un formulario de inicio de sesión de una aplicación web, un puerto de envío de correo, un admin de Nextcloud o de WordPress, un servidor IMAP. Esas capas no se pueden pasar a autenticación por clave pública, así que la adivinación sigue siendo posible y la limitación de velocidad es la defensa real. CrowdSec añade encima una fuente de reputación compartida, que resulta genuinamente útil en la capa HTTP — te beneficias de direcciones que otras personas ya han visto comportarse mal — y casi irrelevante en la capa SSH una vez que las claves son obligatorias.
Merece la pena nombrar dos trampas típicas. La primera es bloquearte a ti mismo, normalmente mientras pruebas algo a las tres de la madrugada; configura ignoreip para incluir tus propios rangos, y recuerda que una dirección compartida o de NAT de operador significa que bloquear a "un atacante" también puede bloquear a varios cientos de personas ajenas al asunto. La segunda es que la herramienta tiene que poder leer los registros que está filtrando: en distribuciones que solo registran en el journal de systemd, el backend de archivo por defecto vigila en silencio un archivo que ya no recibe nada, y la jail se queda ahí, con buen aspecto, sin hacer nada.
La recomendación honesta: instálalo si el ruido te molesta, configúralo para la capa de tu aplicación donde de verdad importa, y no lo cuentes dos veces. Si quieres esa misma tranquilidad sin nada que mantener, MaxStartups y LoginGraceTime en el daemon SSH, junto con la autenticación solo por clave, te llevan la mayor parte del camino y no pueden dejarte fuera de tu propia máquina.
Los parches: el control aburrido que evita la mayoría de los compromisos reales
Lee cualquier informe de incidente que involucre a un servidor pequeño expuesto a internet y el patrón se repite: la vulnerabilidad tenía un parche, el parche llevaba semanas o meses disponible, y nadie lo aplicó. El catálogo de vulnerabilidades explotadas conocidas de CISA es la lectura útil aquí, precisamente porque no es una lista de gravedad teórica — es una lista de cosas que se están explotando activamente ahora mismo, y está dominada por problemas que ya tienen solución publicada. Las actualizaciones de seguridad automáticas son, en consecuencia, los quince segundos de mayor rendimiento de toda esta página.
En Debian y Ubuntu, instala unattended-upgrades, actívalo con dpkg-reconfigure -plow unattended-upgrades, y confirma que el origen de seguridad es el que está seleccionado en /etc/apt/apt.conf.d/50unattended-upgrades. Verifica que hace algo con unattended-upgrades --dry-run --debug, que imprime exactamente qué paquetes tomaría y por qué — la página de la wiki de Debian es la referencia. En Rocky y Alma, dnf-automatic con apply_updates = yes y el timer activado hace el mismo trabajo. Restringirlo al repositorio de seguridad es deliberado: es la configuración que casi nunca rompe nada, que es el único tipo de automatización que la gente deja activado.
Después, decide qué hacer con los reinicios, de forma explícita, porque aquí es donde el control suele filtrarse. Instalar un paquete de kernel nuevo no sustituye el kernel que está en ejecución, y sustituir una biblioteca compartida no reinicia los daemons que ya tenían la copia antigua mapeada en memoria. needrestart en Debian y Ubuntu indica con precisión qué servicios siguen sujetando bibliotecas ya eliminadas; una actualización de kernel necesita un reinicio, punto final, y el live-patching es una función de pago que no va a llegar a una instancia de $8.50. O bien configura Unattended-Upgrade::Automatic-Reboot con un Automatic-Reboot-Time discreto, o pon un reinicio mensual en tu calendario y trátalo como mantenimiento, no como un incidente.
Por último, entiende qué no cubren las actualizaciones automáticas, porque ahí es donde la gente cae. Cualquier cosa instalada fuera del gestor de paquetes le resulta invisible: un binario descargado de un release de GitHub, una dependencia de runtime de lenguaje instalada con pip, npm o cargo, un tema o plugin dentro de una aplicación web, y sobre todo las imágenes de contenedor. Un host que informa de cero actualizaciones pendientes puede estar ejecutando una imagen base construida hace dos años, y nada en la máquina te lo va a decir. Si ejecutas contenedores, reconstruir y volver a descargar según un calendario es parte del parcheo, no una actividad aparte.
| Sistema | Mecanismo | Qué cubre | Qué se le escapa en silencio |
|---|---|---|---|
| Debian / Ubuntu | unattended-upgrades, solo el origen de seguridad | Paquetes de la distribución, aplicados cada noche | El kernel en ejecución y las bibliotecas mapeadas, hasta un reinicio |
| Rocky / Alma | dnf-automatic con apply_updates = yes | Lo mismo, mediante un timer de systemd | El mismo hueco del reinicio; comprueba con dnf needs-restarting |
| Contenedores | Nada, por defecto | Nada | Todo — la imagen queda congelada en el momento de construirse |
| Dependencias del lenguaje | Nada, por defecto | Nada | La superficie de ataque real de tu aplicación |
Usuarios, sudo, y las puertas que se vuelven a abrir en silencio
Crea un usuario normal, añádelo a sudo o wheel, instala ahí tu clave, y deja de iniciar sesión como root. El motivo no es que root con una clave sea peligroso en sí mismo — es una cuenta de un solo factor con un nombre universalmente conocido y sin ningún registro de qué humano la usó. Una cuenta con nombre propio más sudo produce un rastro de auditoría, permite que haya más de un operador sin compartir una credencial, y hace que la eventual pregunta de "quién ejecutó eso" tenga respuesta. Eso sí, no le des NOPASSWD para todo y luego reutilices esa cuenta para un servicio web.
Los servicios tampoco deberían ejecutarse como root, y systemd hace que la versión barata de eso sea casi gratis. Un drop-in creado con systemctl edit <unit> que contenga NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes y PrivateTmp=yes confina a un daemon comprometido a una vista de sistema de archivos de solo lectura sin ninguna vía para escalar — cuatro líneas, descritas en la documentación de systemd.exec, que convierten muchas vulnerabilidades de aplicación de "root en el host" en "un error en un registro". systemd-analyze security puntúa cada unidad de la máquina y te dice cuáles merecen los cinco minutos.
Mantén authorized_keys honesto. Una clave por dispositivo, cada una con un comentario que nombre el dispositivo, y borra cualquier cosa que no puedas identificar. Vuelve a leer el archivo después de que se ejecute cualquier herramienta de aprovisionamiento, porque cloud-init, los pipelines de generación de imágenes y los paneles de control añaden todos sus propias entradas, y una clave que tú no añadiste es una clave que no puedes revocar. Lo mismo aplica a las cuentas: un usuario por defecto que dejó atrás una imagen, con una contraseña puesta por un script y nunca usada desde entonces, es un inicio de sesión que no estás vigilando.
El elemento más sutil de esta lista es el reenvío de agente. ForwardAgent yes permite que un proceso en el host remoto use tu clave privada local mientras estés conectado — eso es exactamente para lo que está diseñado, y por eso el root de una máquina que no controlas del todo puede tomar prestada tu clave para llegar a cualquier otro servidor que confíe en ella. Usa ProxyJump (ssh -J bastion target) en su lugar: te da el mismo alcance tunelizando la conexión, y tu clave nunca llega a ser utilizable en el host intermedio. Si tienes que reenviar un agente, hazlo por host en ~/.ssh/config, nunca de forma global.
Copias de seguridad: el control que convierte un mal día en solo una tarde
Todo lo anterior reduce la probabilidad de tener un mal día. Las copias de seguridad deciden cuánto cuesta ese mal día, y en un proveedor que deliberadamente no puede identificarte, importan más de lo habitual: no hay ninguna vía de soporte que reconstruya tu máquina a partir de la copia de otra persona, ningún flujo de recuperación de cuenta que te entregue discretamente una restauración, y ningún registro de facturación con un número de teléfono asociado. Lo que tienes es lo que tú mismo guardaste.
El esquema que funciona no tiene ningún misterio. restic o borg, cifrando en el servidor para que los datos ya sean ilegibles antes de cruzar la red, enviados a una segunda máquina — e idealmente a una segunda jurisdicción, que aquí es una casilla que marcar y no un proyecto, cuando el mismo panel te ofrece Islandia, los Países Bajos, Rumanía y Suiza. Las instantáneas por hora incluidas en todos los planes son una primera línea genuinamente buena y cubren a la perfección el caso de "rompí la configuración hace una hora". No cubren un archivo que borraste hace cinco semanas, y viven en la misma infraestructura que aquello que protegen. Las instantáneas y las copias de seguridad resuelven problemas distintos; usa las dos.
Haz que el repositorio sea append-only. Este es el detalle que separa una copia de seguridad de una simple copia: un servidor comprometido guarda las credenciales de su propio destino de copias de seguridad, y a todo lo que pueda borrar se le va a ordenar que borre. Tanto borg serve --append-only como el rest-server --append-only de restic lo imponen en el lado receptor, de modo que la máquina puede escribir instantáneas nuevas y no puede eliminar las antiguas. Haz la poda desde otro sitio, según un calendario, con una credencial que el servidor nunca ha visto.
Dos últimas cosas, y la gente se salta las dos. La frase de contraseña del repositorio no debe vivir solo en la máquina que protege — apúntala, guárdala en un gestor de contraseñas en otro sitio, trátala como lo que en la práctica es: una frase semilla. Y restáurala una vez. Una copia de seguridad que nunca has restaurado es una hipótesis; la forma habitual de descubrir que una regla de exclusión se saltó en silencio el volcado de la base de datos es durante la recuperación que no te puedes permitir que salga mal. Levanta una instancia Starter, restaura en ella, comprueba los datos, destrúyela. Eso cuesta $8.50 y una hora, y es la única forma de que el resto de esta página llegue a ser un ensayo en lugar de un primer intento.
Contra qué no te protege el bastionado
No protege el disco de alguien que tiene el disco en sus manos. Todos los controles de esta página asumen que el sistema operativo está en ejecución e imponiendo sus propias reglas; ninguno de ellos se aplica a una máquina apagada cuyo almacenamiento ha sido clonado. Ese es un problema distinto con una respuesta distinta — mira cifrado de disco completo, desbloqueo remoto, y qué es lo que realmente recupera una incautación — y el resumen honesto es que el bastionado y el cifrado defienden contra amenazas disjuntas. Hacer uno no hace parcialmente el otro.
No arregla la aplicación que ejecutas. Un servidor puede pasar todas las comprobaciones de esta página y aun así perderse por un foro desactualizado, un endpoint de administración sin autenticación, un motor de plantillas que evalúa la entrada del usuario, o un token subido por error a un repositorio público. El bastionado del host reduce el radio de impacto de un compromiso a nivel de aplicación — que es exactamente para lo que sirve el confinamiento con systemd de más arriba — pero no audita tu código, y la forma más habitual en que se pierde un servidor pequeño sigue siendo algo que instaló el propio operador.
No cambia quién sabe que alquilaste la máquina. Una caja perfectamente bastionada pagada desde una cuenta de exchange con identidad verificada, pedida con un correo personal, es una caja perfectamente bastionada con tu nombre puesto. La capa de seguridad y la capa de identidad son independientes, y la segunda se decide al pagar, no en la shell — lo rastreable que es en realidad el hosting cripto explica dónde se forman realmente esos vínculos.
Y no hace nada contra el análisis de tráfico. Un observador situado para vigilar tus conexiones aprende su forma — momento, volumen, quién habla con quién — sin importar lo bien configurados que estén los extremos. El cifrado esconde el contenido, no la conversación, y ningún ajuste de sshd cambia eso.
La checklist, en el orden en que debes ejecutarla
Ocho pasos. Los primeros cuatro son los quince minutos del título y eliminan categorías enteras de riesgo; los últimos cuatro llevan más tiempo y son lo que separa un servidor que puedes defender de uno que simplemente configuraste. Cada fila incluye cómo sabes que funcionó, porque un paso de bastionado que no verificaste es un paso de bastionado que no diste.
| # | Paso | Cómo sabes que funcionó |
|---|---|---|
| 1 | Abre la consola del panel, confirma que aparece un prompt de inicio de sesión, toma una instantánea | Viste el prompt en un servidor que no estaba roto |
| 2 | Crea un usuario no root, instala tu clave ed25519, prueba sudo | Una segunda terminal inicia sesión como ese usuario y escala privilegios |
| 3 | Desactiva la autenticación por contraseña e interactiva por teclado | sshd -T informa passwordauthentication no |
| 4 | Cortafuegos con denegación por defecto del tráfico entrante, cubriendo IPv4 e IPv6 | nft list ruleset muestra ambas familias; un escaneo desde otro host lo confirma |
| 5 | Audita los sockets a la escucha, revincula los servicios internos a loopback | ss -tulpn no muestra ninguna vinculación comodín que no puedas justificar |
| 6 | Activa las actualizaciones de seguridad automáticas y una política de reinicio | unattended-upgrades --dry-run --debug selecciona el origen de seguridad |
| 7 | Confina los servicios con systemd, ordena authorized_keys | las puntuaciones de systemd-analyze security mejoran; todas las claves tienen nombre |
| 8 | Copia de seguridad cifrada, append-only y externa, restaurada una vez | Restauraste en una instancia desechable y los datos estaban ahí |
Nada de esto es exótico, y esa es la cuestión. Los controles que importan en un servidor pequeño son los que eliminan una clase de problema en lugar de gestionarla: sin contraseñas, sin nada escuchando donde no debería, sin paquetes sin parchear, sin una única copia de los datos. Todo lo demás es preferencia, y la preferencia sale barata una vez que los cuatro cambios estructurales están hechos y verificados.
Si estás aprovisionando la máquina ahora mismo en lugar de leyendo por adelantado, la secuencia cabe cómodamente en la primera sesión: despliega una instancia, abre la consola una vez para comprobar que funciona, y ejecuta los ocho pasos en orden antes de instalar nada en absoluto. Un servidor bastionado antes de tener ningún servicio no tiene ningún legado con el que lidiar — que es la única ventaja que tiene una caja nueva, y dura aproximadamente un día.