--- title: "Cómo configurar servidores con cloud-init en Ubuntu 24.04" description: "Escribe un cloud-config que crea usuarios, instala paquetes, escribe archivos y ejecuta comandos en el primer arranque, y pruébalo con LXD en Ubuntu 24.04." category: "Automatización e IaC" language: es source: https://cubepath.com/es/docs/automatización-e-iac/configuracion-de-cloud-init-para-provision-de-servidores updated: 2026-09-25 --- # Cómo configurar servidores con cloud-init en Ubuntu 24.04 cloud-init es el servicio que configura un servidor Linux en su primer arranque: crea usuarios, instala claves SSH, fija el nombre de host, instala paquetes y ejecuta comandos a partir de un archivo que recibe como *user-data*. Viene preinstalado en las imágenes cloud de Ubuntu, Debian, Rocky Linux y la mayoría de distribuciones, y es lo que usan casi todos los proveedores para preparar cada servidor nuevo. En este tutorial escribirás un archivo cloud-config que deja listo un servidor web con Nginx, un usuario administrador y un cortafuegos, lo validarás y lo probarás en un contenedor LXD en Ubuntu 24.04 antes de usarlo en un servidor real. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor o equipo con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con un usuario no root con privilegios `sudo`. Se usará para validar el archivo y lanzar contenedores de prueba. - Una clave SSH pública (por ejemplo, el contenido de `~/.ssh/id_ed25519.pub` de tu equipo). Si no tienes una, créala en tu equipo con `ssh-keygen -t ed25519`. - Unos 5 GB libres en disco para la imagen de prueba de LXD. ## Cómo funciona cloud-init En cada arranque, cloud-init busca una *fuente de datos* (datasource): el servicio de metadatos del proveedor, un CD con etiqueta `cidata`, la configuración de LXD, etc. De ella obtiene los metadatos de la instancia y el user-data. Si el identificador de instancia es nuevo, ejecuta sus módulos en cuatro etapas: | Etapa | Qué hace | |---|---| | `init-local` | Localiza la fuente de datos y aplica la configuración de red | | `init` | Configura el nombre de host, las claves SSH de host, usuarios y grupos | | `config` | Ejecuta módulos de configuración como `write_files` o `timezone` | | `final` | Instala paquetes, ejecuta `runcmd` y scripts de usuario | La mayoría de módulos se ejecutan una sola vez por instancia. Por eso, si cambias el user-data de un servidor ya creado, cloud-init no lo vuelve a aplicar: está pensado para el primer arranque, no para gestionar la configuración a lo largo del tiempo. El user-data puede ser un script (empieza por `#!`) o, lo más habitual, un documento YAML **cloud-config**, que debe empezar exactamente por la línea `#cloud-config`. ## Paso 1: Escribir el archivo cloud-config Crea un directorio de trabajo y el archivo: ```bash mkdir -p ~/cloud-init && cd ~/cloud-init nano user-data.yaml ``` Pega el siguiente contenido y sustituye la clave SSH por la tuya: ```yaml #cloud-config hostname: web01 timezone: Europe/Madrid users: - name: deploy gecos: Usuario de despliegue groups: [sudo] shell: /bin/bash sudo: "ALL=(ALL) NOPASSWD:ALL" lock_passwd: true ssh_authorized_keys: - ssh-ed25519 AAAA... tu_usuario@tu_equipo ssh_pwauth: false disable_root: true package_update: true package_upgrade: true packages: - nginx - ufw - fail2ban write_files: - path: /etc/ssh/sshd_config.d/10-hardening.conf permissions: "0644" content: | PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no - path: /var/www/html/index.html permissions: "0644" defer: true content: |

web01 aprovisionado con cloud-init

runcmd: - ufw allow OpenSSH - ufw allow "Nginx Full" - ufw --force enable - systemctl enable --now nginx fail2ban - systemctl try-reload-or-restart ssh final_message: "cloud-init terminó en $UPTIME segundos" ``` Qué hace cada bloque: - `hostname` y `timezone`: nombre de host y zona horaria. - `users`: crea `deploy` con sudo sin contraseña y acceso solo por clave. Al definir `users`, el usuario por defecto de la imagen (`ubuntu`) ya no se crea; si lo quieres también, añade `- default` como primer elemento de la lista. - `ssh_pwauth` y `disable_root`: desactivan el acceso SSH por contraseña y el login como root. - `package_update`, `package_upgrade` y `packages`: ejecutan `apt update`, actualizan el sistema e instalan los paquetes indicados. - `write_files`: crea archivos con su contenido y permisos. `defer: true` retrasa la escritura hasta la etapa `final`, después de instalar paquetes; sin eso, el paquete `nginx` sobrescribiría el `index.html`. - `runcmd`: comandos que se ejecutan una vez, al final, como root. Cada elemento se ejecuta con `sh`, así que escribe comandos simples o usa listas (`- [systemctl, restart, nginx]`) para evitar problemas de comillas. - `final_message`: aparece en el log cuando todo ha terminado. > Nota: `sudo: "ALL=(ALL) NOPASSWD:ALL"` es cómodo para automatización. Si prefieres que `sudo` pida contraseña, quita esa línea y asigna una contraseña al usuario después del primer acceso. ## Paso 2: Validar el archivo Un error de indentación en YAML hace que cloud-init ignore partes del archivo sin detener el arranque, así que valídalo siempre antes de usarlo. Ubuntu 24.04 incluye cloud-init con el comando `schema`: ```bash cloud-init schema --config-file user-data.yaml --annotate ``` ```output Valid schema user-data.yaml ``` Si hay un error, `--annotate` lo marca junto a la línea afectada. Por ejemplo, con `package_upgrade: si` en lugar de un booleano: ```output package_upgrade: si # E1 ... # Errors: ------------- # E1: 'si' is not of type 'boolean' ``` ## Paso 3: Preparar LXD para las pruebas LXD puede crear contenedores de Ubuntu con cloud-init en segundos y pasarles tu user-data, así que es la forma más rápida de probar sin crear servidores reales. Instálalo con snap y aplica la configuración por defecto: ```bash sudo snap install lxd sudo usermod -aG lxd "$USER" newgrp lxd lxd init --auto ``` Comprueba que funciona: ```bash lxc list ``` ```output +------+-------+------+------+------+-----------+ | NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS | +------+-------+------+------+------+-----------+ ``` ## Paso 4: Lanzar un contenedor con el user-data Crea un contenedor Ubuntu 24.04 y pásale el archivo con la clave `cloud-init.user-data`: ```bash lxc launch ubuntu:24.04 web01 --config=cloud-init.user-data="$(cat user-data.yaml)" ``` Espera a que cloud-init termine. El comando se queda bloqueado hasta entonces: ```bash lxc exec web01 -- cloud-init status --wait --long ``` ```output ..................... status: done extended_status: done boot_status_code: enabled-by-generator last_update: Thu, 25 Sep 2026 10:42:18 +0000 detail: DataSourceLXD errors: [] recoverable_errors: {} ``` `status: done` con `errors: []` indica que todos los módulos terminaron bien. Si ves `status: error`, pasa a la sección de depuración. ## Paso 5: Verificar el resultado Comprueba cada parte de la configuración dentro del contenedor. Nombre de host, zona horaria y usuario: ```bash lxc exec web01 -- hostname lxc exec web01 -- timedatectl show -p Timezone --value lxc exec web01 -- id deploy ``` ```output web01 Europe/Madrid uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo) ``` Cortafuegos y servicios: ```bash lxc exec web01 -- ufw status lxc exec web01 -- systemctl is-active nginx fail2ban ``` ```output Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere Nginx Full ALLOW Anywhere ... active active ``` Página web desde el host. Obtén la IP del contenedor y haz una petición: ```bash IP=$(lxc list web01 -c 4 --format csv | cut -d' ' -f1) curl -s "http://$IP" ``` ```output

web01 aprovisionado con cloud-init

``` Por último, entra por SSH con tu clave como `deploy`. Desde el propio host, si la clave privada está ahí, o desde tu equipo si el host enruta hacia el contenedor: ```bash ssh deploy@"$IP" ``` ## Paso 6: Depurar cloud-init Cuando algo no se aplica, estas son las fuentes de información, de más a menos útil: ```bash lxc exec web01 -- cat /var/log/cloud-init-output.log ``` `cloud-init-output.log` contiene la salida de `apt`, de `runcmd` y de los scripts. Es donde aparecen los errores de tus comandos. ```bash lxc exec web01 -- grep -E "WARNING|ERROR|Traceback" /var/log/cloud-init.log ``` `cloud-init.log` es el log interno de cloud-init: qué módulos se ejecutaron, cuáles fallaron y por qué. Para ver el user-data que realmente recibió la instancia (útil si sospechas que la fuente de datos no lo entregó): ```bash lxc exec web01 -- cloud-init query userdata ``` Y para saber cuánto tardó cada etapa y módulo: ```bash lxc exec web01 -- cloud-init analyze blame ``` ```output -- Boot Record 01 -- 41.20300s (modules-final/config-package_update_upgrade_install) 03.11200s (modules-final/config-scripts_user) ... ``` ### Volver a ejecutar cloud-init Mientras ajustas el archivo, lo más rápido es borrar el contenedor y crear otro: ```bash lxc delete --force web01 ``` En un servidor real puedes forzar que cloud-init se ejecute de nuevo como si fuera el primer arranque. Esto vuelve a aplicar todos los módulos con el user-data actual del proveedor y reinicia el servidor, así que úsalo solo en servidores de prueba: ```bash sudo cloud-init clean --logs --reboot ``` ## Paso 7: Usar el archivo en un servidor real El mismo archivo sirve para cualquier plataforma que admita user-data: - Proveedores cloud: pégalo en el campo de user-data o datos de usuario al crear el servidor, si el proveedor lo ofrece. - Terraform y Pulumi: la mayoría de proveedores tienen un argumento `user_data` (por ejemplo, `user_data = file("user-data.yaml")`). - Máquinas virtuales locales o Proxmox: genera un disco NoCloud con `cloud-localds seed.iso user-data.yaml` (paquete `cloud-image-utils`) y adjúntalo como CD-ROM a una cloud image. Antes de desplegarlo, recuerda dos límites: - Muchos proveedores limitan el tamaño del user-data (16 KB es habitual). Si crece mucho, deja que cloud-init solo prepare el acceso y lance una herramienta de configuración como Ansible. - El user-data suele ser legible desde dentro del servidor a través del servicio de metadatos. No incluyas contraseñas ni tokens en él. ## Solución de problemas **El archivo se ignora por completo**: la primera línea no es exactamente `#cloud-config` (sin espacios delante ni después de `#`). Compruébalo con `cloud-init query userdata`. **Algunos módulos no se aplican y no hay error visible**: suele ser una clave mal escrita o mal indentada. Ejecuta `cloud-init schema --config-file user-data.yaml --annotate` y, dentro de la instancia, `sudo cloud-init schema --system --annotate` para validar lo que recibió. **El `index.html` es el de Nginx por defecto**: falta `defer: true` en `write_files`, y el paquete sobrescribió el archivo. **No puedes entrar por SSH con el usuario `ubuntu`**: al definir `users` sin `- default`, ese usuario no se crea. Usa `deploy` o añade `- default` a la lista. ## Conclusión Has escrito un archivo cloud-config que deja un servidor listo con usuario administrador, SSH endurecido, paquetes actualizados, Nginx y cortafuegos, lo has validado con `cloud-init schema` y lo has probado en LXD en menos de un minuto por iteración. Como siguientes pasos, pasa el mismo archivo como `user_data` desde Terraform, combina cloud-init para el primer arranque con Ansible para la configuración continua, y usa plantillas Jinja (primera línea `## template: jinja`) para adaptar el archivo a los metadatos de cada instancia.