/cj-cloud-init.yml Secret
Last active
January 5, 2026 19:51
-
Star
(127)
You must be signed in to star a gist -
Fork
(111)
You must be signed in to fork a gist
-
-
Save w3cj/cdd447b1a10ce741e4ee968fa6b75553 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |
I created this script with the help of AI
This script hardens and automates your server setup in 3 quick steps:
- Hardens Access: Creates a new user, moves SSH to new port, and bans passwords/root login (keys only).
- Locks Firewall: Blocks all traffic except your SSH port and Cloudflare IPs (hides your server from the public).
- Deploys Coolify: Installs the app, injects automation keys, and patches the database to use your custom settings.
I am also using Cloudflare DNS and origin certificates by following this video: https://www.youtube.com/watch?v=HZFNt-Grw0U
Note: I am not an expert in Linux and self-hosting, please guide me if I am doing anything wrong, I will correct it.
#!/bin/bash
set -e
# ================= CONFIG =================
REMOTE_IP="23.92.65.231"
USERNAME="tolres2"
NEW_SSH_PORT="2311"
LOCAL_KEY_DIR="$HOME/Documents/coolify-new-imp/$USERNAME"
# =========================================
mkdir -p "$LOCAL_KEY_DIR"
# Generate Keys
[ ! -f "$LOCAL_KEY_DIR/id_ed25519_personal" ] && ssh-keygen -t ed25519 -f "$LOCAL_KEY_DIR/id_ed25519_personal" -N "" -C "$USERNAME-personal"
[ ! -f "$LOCAL_KEY_DIR/id_ed25519_coolify" ] && ssh-keygen -t ed25519 -f "$LOCAL_KEY_DIR/id_ed25519_coolify" -N "" -C "coolify-automation"
PERSONAL_PUB=$(cat "$LOCAL_KEY_DIR/id_ed25519_personal.pub")
COOLIFY_PUB=$(cat "$LOCAL_KEY_DIR/id_ed25519_coolify.pub")
COOLIFY_PRIV_DATA=$(cat "$LOCAL_KEY_DIR/id_ed25519_coolify")
echo "π Starting Deployment on $REMOTE_IP..."
ssh root@$REMOTE_IP "PERSONAL_PUB='$PERSONAL_PUB' COOLIFY_PUB='$COOLIFY_PUB' COOLIFY_PRIV='$COOLIFY_PRIV_DATA' USERNAME='$USERNAME' NEW_SSH_PORT='$NEW_SSH_PORT'" 'bash -s' <<'EOF'
set -e
# 1. USER SETUP
if ! id -u "$USERNAME" >/dev/null 2>&1; then useradd -m -s /bin/bash "$USERNAME"; fi
usermod -aG sudo "$USERNAME"
echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME
mkdir -p /home/$USERNAME/.ssh
echo "$PERSONAL_PUB" > /home/$USERNAME/.ssh/authorized_keys
echo "$COOLIFY_PUB" >> /home/$USERNAME/.ssh/authorized_keys
chown -R $USERNAME:$USERNAME /home/$USERNAME/.ssh
chmod 700 /home/$USERNAME/.ssh
chmod 600 /home/$USERNAME/.ssh/authorized_keys
# 2. SSH HARDENING (Clean & Safe)
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# Remove any existing Port or AllowUsers lines to prevent duplicates
sed -i "/^Port /d" /etc/ssh/sshd_config
sed -i "/^AllowUsers /d" /etc/ssh/sshd_config
sed -i "/^PermitRootLogin /d" /etc/ssh/sshd_config
echo "Port $NEW_SSH_PORT" >> /etc/ssh/sshd_config
echo "AllowUsers $USERNAME" >> /etc/ssh/sshd_config
echo "PermitRootLogin no" >> /etc/ssh/sshd_config
echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
# 3. FIREWALL (Including Cloudflare)
apt-get update && apt-get install -y ufw curl
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow $NEW_SSH_PORT/tcp
ufw allow 22/tcp # EMERGENCY BACKUP PORT - Close this manually later!
ufw allow from 172.17.0.0/16 to any # Allow Docker Bridge
echo "Adding Cloudflare IPs..."
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do ufw allow from "$ip" to any port 80,443 proto tcp; done
for ip in $(curl -s https://www.cloudflare.com/ips-v6); do ufw allow from "$ip" to any port 80,443 proto tcp; done
ufw --force enable
# 4. INSTALL COOLIFY
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
# 5. RESTART SSH
sshd -t
systemctl restart ssh
# -------- 6. FIX PERMISSIONS & KEY INJECTION --------
echo "π§ Applying final permission fixes..."
# 1. Add your user to the root group FIRST
# This ensures that once we set '750' (Group access), you are already in that group.
usermod -aG root "$USERNAME"
# 2. Set the internal Coolify user (9999) as the owner
# and 'root' as the group for the entire directory.
chown -R 9999:root /data/coolify
# 3. Create/Verify the SSH directory
mkdir -p /data/coolify/ssh/keys
# 4. Inject the Automation Key
echo "$COOLIFY_PRIV" > /data/coolify/ssh/keys/id.root@host.docker.internal
# 5. Apply the 750 Permission (Owner: Full, Group: Enter/Read, Others: None)
# This allows you (as part of the root group) to 'cd' into these folders.
chmod 750 /data/coolify
chmod 750 /data/coolify/ssh
chmod 750 /data/coolify/proxy
# 6. Critical Security: Keep the private key locked to the owner only
chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal
chown 9999:9999 /data/coolify/ssh/keys/id.root@host.docker.internal
echo "β
Permissions updated. Group 'root' now has access to /data/coolify."
# 7. DATABASE PATCH (WAITING FOR DB)
echo "β³ Patching Coolify DB (User: $USERNAME, Port: $NEW_SSH_PORT)..."
for i in {1..60}; do
DB=$(docker ps --filter name=coolify-db --format "{{.Names}}")
if [ -n "$DB" ] && docker exec $DB pg_isready -U coolify >/dev/null 2>&1; then
docker exec $DB psql -U coolify -d coolify -c "UPDATE servers SET \"user\"='$USERNAME', port=$NEW_SSH_PORT WHERE name='localhost';"
echo "β
DB Patched."
break
fi
[ $i -eq 60 ] && echo "β DB Patch timed out. You must update the server settings in Coolify UI."
sleep 5
done
EOF
echo "-------------------------------------------------------"
echo "β
SETUP FINISHED"
echo "1. Attempt login: ssh -p $NEW_SSH_PORT $USERNAME@$REMOTE_IP -i $LOCAL_KEY_DIR/id_ed25519_personal"
echo "2. If successful, run 'sudo ufw delete allow 22/tcp' on the server."
echo "-------------------------------------------------------"
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
@vlad1mirJ on Hetzner Ubuntu 24.04, if you add ssh key, root user don't have a password, so we don't need that extra config I think