---
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.