Skip to content

Instantly share code, notes, and snippets.

@w3cj
Last active January 5, 2026 19:51
Show Gist options
  • Select an option

  • Save w3cj/cdd447b1a10ce741e4ee968fa6b75553 to your computer and use it in GitHub Desktop.

Select an option

Save w3cj/cdd447b1a10ce741e4ee968fa6b75553 to your computer and use it in GitHub Desktop.
# This config was written for Ubuntu 22.04
# If you are using a more recent version, see the comments of this gist for fixes
#cloud-config
users:
- name: cj
ssh_authorized_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBlfqermlV44zAU+iTCa5im5O0QWXid6sHqh2Z4L1Cm cj@null.computer"
sudo: ALL=(ALL:ALL) ALL
groups: sudo
shell: /bin/bash
chpasswd:
expire: true
users:
- name: cj
password: changeme
type: text
runcmd:
- sed -i '/PermitRootLogin/d' /etc/ssh/sshd_config
- echo "PermitRootLogin without-password" >> /etc/ssh/sshd_config
- sed -i '/PubkeyAuthentication/d' /etc/ssh/sshd_config
- echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
- sed -i '/PasswordAuthentication/d' /etc/ssh/sshd_config
- echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
- systemctl restart sshd
- echo "\$nrconf{kernelhints} = -1;" > /etc/needrestart/conf.d/99disable-prompt.conf
- apt update
- apt upgrade -y --allow-downgrades --allow-remove-essential --allow-change-held-packages
- reboot
@Lenostatos
Copy link

Lenostatos commented Jan 5, 2026

I dug way too deep into cloud-init 😅 and used the following result in the end:

#cloud-config

# Keyboard settings
keyboard:
  layout: de # might be useful if you have a non-english keyboard layout

# System timezone
# (might help, e.g. with correctly timing Coolify updates)
timezone: Europe/Berlin

# SSH config
disable_root: false # coolify needs the root user SSH login
allow_public_ssh_keys: true
ssh_genkeytypes: [ed25519] # modify this, if you use other SSH key types

# User config
user:
  name: lenos
  sudo: ALL=(ALL:ALL) ALL # google "sudoers" file or syntax to understand this
  groups: sudo
  shell: /bin/bash
  ssh_authorized_keys:
    - ssh-ed25519 AAAAC3NzaC1l...

users:
  - name: root
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1l...

# Password setup

# disable password authentication, allow only SSH key authentication
ssh_pwauth: false

# set expired passwords that have to be reset on first login
chpasswd:
  expire: true
  users:
    - name: root # will only need root pw for rescue operation via vps provider terminal
      password: changeme
      type: text
    - name: lenos
      password: changeme
      type: text

# Extra SSH security settings
# Source: https://community.hetzner.com/tutorials/basic-cloud-config
# sshd config docs at: https://man.openbsd.org/sshd_config
# Sidenote: The docs say the following on AllowAgentForwarding: "Note that disabling agent forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders."
write_files:
  - path: /etc/ssh/sshd_config.d/ssh-hardening.conf
    content: |
      Port 2222
      AllowUsers root lenos
      PermitRootLogin prohibit-password
      KbdInteractiveAuthentication no
      ChallengeResponseAuthentication no
      MaxAuthTries 3
      AllowTcpForwarding yes
      X11Forwarding no
      AllowAgentForwarding no
      AuthorizedKeysFile .ssh/authorized_keys

# Package management
packages:
  - fail2ban
  - ufw # uncomplicated firewall | Caveat (!): Coolify docs mention that ufw doesn't work with Docker applications https://coolify.io/docs/knowledge-base/server/firewall#closing-ports-using-a-firewall
package_update: true
package_upgrade: true
package_reboot_if_required: true

# Commands to run at the end of the cloud-init process
runcmd:
  - printf "[sshd]\nenabled = true\nport = 2222\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
  - systemctl enable fail2ban
  - ufw allow 2222 # randomly selected SSH port with https://monocalc.com/tool/networking/random_port_generator
  - ufw allow 80 # SSL certificate generation via reverse proxy
  - ufw allow 443 # HTTPS
  - ufw allow 8000 # HTTP access to the Coolify dashboard (close this after setting up custom domain)
  - ufw allow 6001 # Real-time communications (close this after setting up custom domain)
  - ufw allow 6002 # Terminal access (close this after setting up custom domain)
  - ufw enable
  - echo "\$nrconf{kernelhints} = -1;" > /etc/needrestart/conf.d/99disable-prompt.conf # <- Claude 4.5 says: "Creates a config file that prevents `needrestart` from prompting about kernel/service restarts during package updates"
  - apt update
  - apt -y --with-new-pkgs upgrade
  - reboot # ! without this, the SSH service won't be properly configured
##############################
# TODO-list after first login:
# - cloud-init status --wait
# - check if reboot is required with `ls /var/run/reboot-required`
# - change user pw
# - change root pw by switching to root user and executing `passwd`

# Simulate machine for cloud-init to run on: https://cloudinit.readthedocs.io/en/latest/tutorial/qemu.html

# Python server serving cloud-init config files is started in those files' folder with:
# `python3 -m http.server --directory .`

# Custom startup command from folder containing cloud image:
# qemu-system-x86_64                                            \
# -net nic                                                    \
# -net user,hostfwd=tcp::2222-:2222                          \
# -machine accel=kvm:tcg                                      \
# -m 512                                                      \
# -smp 4                                                      \
# -nographic                                                  \
# -hda noble-server-cloudimg-amd64.img                        \
# -smbios type=1,serial=ds='nocloud;s=http://10.0.2.2:8000/'

# Comments:
# -net user,hostfwd=tcp::2222-:2222 -> forwarding your custom SSH port. Change the right port number on this line to your own custom SSH port number if you use one
# -m 512 -> allocate 512 MB of system memory to qemu VM
# -smp 4                                                      \ # allocate 4 CPU cores to qemu VM
# -smbios type=1,serial=ds='nocloud;s=http://10.0.2.2:8000/'  \ # last part is address of server serving user-data file

# Validate cloud-init.yaml: https://cloudinit.readthedocs.io/en/latest/howto/debug_user_data.html
# (`cloud-init schema -c path/to/your/user-data/file --annotate`)
# Rerun local cloud-init tests: https://cloudinit.readthedocs.io/en/latest/howto/rerun_cloud_init.html#

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment