cloud-init is the standard tool that configures Linux cloud and VPS images on first boot: it sets the hostname, creates users, installs SSH keys, configures networking, and can run your own setup instructions. Most Ubuntu, Debian, AlmaLinux and Rocky images for clouds and many VPS platforms include it.
Even if you never write cloud-init configuration yourself, knowing it exists explains things like “why did my Netplan change disappear after reboot?”.
How it works
At boot, cloud-init reads configuration from a datasource — a metadata service, a config drive or a small disk attached by the platform — and runs in several stages:
- init-local / init — find the datasource, set up networking, hostname, SSH host keys.
- config — apply configuration modules (users, packages, files…).
- final — run user scripts,
runcmd, package upgrades.
Some modules run only on the first boot of an instance; others run every boot.
user-data: your own instructions
Platforms that support cloud-init usually let you pass user-data when creating a server. The most common format is #cloud-config YAML:
#cloud-config
hostname: web1
timezone: UTC
users:
- name: alice
groups: [sudo]
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
ssh_authorized_keys:
- ssh-ed25519 AAAAC3Nza… alice@laptop
ssh_pwauth: false
disable_root: true
package_update: true
package_upgrade: true
packages:
- nginx
- ufw
- fail2ban
- unattended-upgrades
write_files:
- path: /etc/ssh/sshd_config.d/10-hardening.conf
content: |
PermitRootLogin no
PasswordAuthentication no
runcmd:
- ufw allow OpenSSH
- ufw allow 'Nginx Full'
- ufw --force enable
- systemctl enable --now fail2ban
The server comes up with your user, keys, packages, firewall and hardening already in place — the first 10 minutes on a new VPS, automated.
Validate syntax before using it:
cloud-init schema --config-file user-data.yaml
User-data can also be a plain shell script (starting with #!/bin/bash), but cloud-config is declarative and easier to read.
Checking what happened
cloud-init status --long
sudo cat /var/log/cloud-init.log # detailed module log
sudo cat /var/log/cloud-init-output.log # output of commands and scripts
sudo cloud-init query userdata # the user-data the instance received
cloud-init status --wait blocks until cloud-init finishes — useful in scripts that run right after boot.
Re-running
For testing, you can reset cloud-init and reboot to run everything again:
sudo cloud-init clean --logs
sudo reboot
Only do this on test machines — it re-applies first-boot configuration.
When cloud-init overrides your changes
On some images, cloud-init regenerates configuration at every boot:
- Networking —
/etc/netplan/50-cloud-init.yamlmay be rewritten. To manage networking yourself:
echo "network: {config: disabled}" | sudo tee /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
- Hostname — set
preserve_hostname: truein a file under/etc/cloud/cloud.cfg.d/. /etc/hosts— controlled bymanage_etc_hosts.
See Netplan static IP.
Disabling cloud-init
If the platform doesn’t use it and it slows boot, or you’ve finished setup and want full manual control:
sudo touch /etc/cloud/cloud-init.disabled
Remove the file to enable it again.
cloud-init vs Ansible
They complement each other:
- cloud-init — first boot only: get a fresh server to a known, reachable, secure baseline.
- Ansible — ongoing configuration management, re-run any time.
A common pattern is minimal cloud-init (user + SSH key + Python) and Ansible for everything else. See infrastructure as code.
On VPS.DO
Our VPS servers are delivered ready to use with the credentials in your welcome email, and you can reinstall the OS from the control panel. For automated setups across several servers, an Ansible playbook run right after provisioning gives the same result as cloud-init user-data — and works on any server.