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:

  1. init-local / init — find the datasource, set up networking, hostname, SSH host keys.
  2. config — apply configuration modules (users, packages, files…).
  3. 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.yaml may 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: true in a file under /etc/cloud/cloud.cfg.d/.
  • /etc/hosts — controlled by manage_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.

  • cloud-init
  • Automation
  • Linux