Skip to content
API & automation

cloud-init recipes

Ship a server that is already hardened at first boot, so the window between “server exists” and “server is secured” never happens. Recipes you can paste.

6 min readUpdated

All Linux templates support cloud-init. Supply user_data at creation and the server configures itself before you first log in — which also means the window between "server exists" and "server is hardened" never happens.

A hardened baseline

#cloud-config
hostname: node-01
fqdn: node-01.example.com

users:
  - name: deploy
    groups: [sudo]
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...

package_update: true
package_upgrade: true
packages:
  - ufw
  - fail2ban
  - unattended-upgrades
  - nftables

write_files:
  - path: /etc/ssh/sshd_config.d/99-hardening.conf
    content: |
      PermitRootLogin no
      PasswordAuthentication no
      AuthenticationMethods publickey
      MaxAuthTries 3

runcmd:
  - ufw default deny incoming
  - ufw default allow outgoing
  - ufw allow 22/tcp
  - ufw --force enable
  - systemctl enable --now fail2ban
  - systemctl restart sshd

power_state:
  mode: reboot
  message: cloud-init complete
  timeout: 30

Docker host

#cloud-config
packages: [ca-certificates, curl, gnupg]
runcmd:
  - install -m 0755 -d /etc/apt/keyrings
  - curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
  - echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list
  - apt-get update
  - apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
  - usermod -aG docker deploy

WireGuard endpoint

#cloud-config
packages: [wireguard, nftables]
write_files:
  - path: /etc/wireguard/wg0.conf
    permissions: '0600'
    content: |
      [Interface]
      Address = 10.66.66.1/24
      ListenPort = 51820
      PrivateKey = SERVER_PRIVATE_KEY
      PostUp = nft add rule inet filter forward iifname wg0 accept
      PostUp = nft add rule inet nat postrouting oifname eth0 masquerade

      [Peer]
      PublicKey = CLIENT_PUBLIC_KEY
      AllowedIPs = 10.66.66.2/32
runcmd:
  - sysctl -w net.ipv4.ip_forward=1
  - echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
  - systemctl enable --now wg-quick@wg0

Verifying

cloud-init status --wait
cloud-init analyze show
journalctl -u cloud-final

If something did not apply, /var/log/cloud-init-output.log has the answer. YAML indentation is the cause about nine times out of ten.

Deploy in the next five minutes.

Pick a location, size the box, pay in crypto. No account signup wall, no ID, no waiting on a human.

7-day money-back guarantee · No KYC · Cancel any time from the panel