What cloud-init is and why you need it
cloud-init is the standard tool for automatically configuring a server on its first boot. It reads the configuration supplied when the VPS is created (known as user-data) and uses it to create users, install packages, add SSH keys, and run arbitrary commands — all without manually logging into the server after it is created.
The point is to avoid repeating the same steps by hand after the first SSH connection to a VDS: basic hardening, the needed users, and a system update all get applied automatically before the first login.
Where to find cloud-init on an already created VPS
Most current Ubuntu and Debian images from hosting providers already include cloud-init. You can check whether it is present and its version like this:
cloud-init --version
systemctl status cloud-init
Configuration files applied when the server was created are stored in /var/lib/cloud/instance/, and the raw data passed by the control panel is in /var/lib/cloud/instance/user-data.txt. This is useful when you need to figure out exactly what ran during the server's first boot.
User-data structure: a minimal working example
cloud-init configuration is written in YAML and must start with the line #cloud-config as the very first line of the file. A minimal example that updates packages and sets the timezone:
#cloud-config
package_update: true
package_upgrade: true
timezone: Europe/Berlin
Indentation in YAML matters: use spaces, not tabs, otherwise the configuration will not apply and the logs will show a parsing error.
Creating a user, SSH keys, and packages
A fuller example creates an unprivileged user with sudo access, adds their public SSH key, and installs the needed packages:
#cloud-config
users:
- name: deploy
groups: sudo
shell: /bin/bash
sudo: 'ALL=(ALL) NOPASSWD:ALL'
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user@laptop
packages:
- nginx
- fail2ban
- unattended-upgrades
runcmd:
- systemctl enable fail2ban
- systemctl start fail2ban
The runcmd section runs arbitrary shell commands after packages are installed — the basic steps from the article on first steps after buying a VDS can go there too, so the server is protected right after creation.
How to pass user-data when creating a server
A hosting provider's control panel usually has a "User data" or "Cloud-init script" field at the server configuration step when ordering a VPS — the entire YAML file goes there as is. If the server is created through a cloud CLI tool, the file is passed with a separate flag, for example:
--user-data-file ./cloud-config.yaml
Important: user-data by default only applies on the instance's first boot. Passing the configuration again on an already running server requires forcibly resetting the cloud-init state with cloud-init clean and rebooting — do this deliberately, since running it again can recreate users or reinstall packages.
Debugging: if cloud-init did not work
If the expected changes did not apply after the server was created, check the execution logs:
cat /var/log/cloud-init.log
cat /var/log/cloud-init-output.log
The second file contains the actual output of the runcmd commands and package installation — you can usually spot the specific error there. Checklist:
- Check the YAML indentation and syntax — a single stray tab breaks the whole file.
- Make sure the first line of the file is exactly
#cloud-config. - Check both logs:
cloud-init.logfor the process andcloud-init-output.logfor command output. - If you manually opened firewall ports, check the article on configuring UFW on a VDS in case runcmd changed the filtering rules.
- Use
cloud-init cleanand a reboot to reapply the configuration, rather than editing the system by hand.