Skip to content

Instantly share code, notes, and snippets.

@w3cj
Last active December 28, 2025 14:23
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
@swrrvr
Copy link

swrrvr commented Jul 14, 2024

My solution is to edit line 22 in the clout-init.yml file to systemctl restart ssh || systemctl restart sshd in an attempt to target both SSH service names.

I also left out one small (huge) detail. On line 21 of cloud-init.yml, I also happened to have changed "PasswordAuthentication no" to "PasswordAuthentication yes".

Thus, conveniently disabling PasswordAuthentication, enabling access to the server (my mistake).

One could simply pipe echo "PasswordAuthentication no" >> /etc/ssh/sshd_config back into the session once connected, then wait for the system to reboot and then ssh back in. (unless you are plan adding a new server through Coolify I'd highly recommend doing that well after your new server is configured.)

I'm also testing @adrnd method to ssh into the session using ssh -o "IdentitiesOnly=yes" -i ~/.ssh/id_ed25519 root@serveripaddress, will follow up here.

@joemaffei
Copy link

The server kept asking me for my password. @adrnd's solution worked for me.

@legout
Copy link

legout commented Aug 2, 2024

If I restart the server, I get root@[xxx.xxxx.xxx.xxx]: Permission denied (publickey).

Did you set the permissions right?

chmod -R 644 ~/.ssh/your_key.pub
chmod -R 600 ~/.ssh/authorized_keys

@Jensssen
Copy link

An alternative to @adrnd solution would be to create an ssh config entry like the following:

Host my_awesome_server
 HostName xxx.xxx.xxx.xxx
 User <YOUR_USER_NAME_SPECIFIED_IN_CLOUD_CONFIG>
 Port 22
 IdentityFile ~/.ssh/<PRIVATE_KEY_FILE>

And then run ssh my_awesome_server. This should enforce a user login on the given IP to use your public/private key for authentication. Make sure to also follow @swrrvr suggestion to add systemctl restart ssh || systemctl restart sshd to the cloud config. in line 22

@dziamid
Copy link

dziamid commented Sep 19, 2024

Here's an updated version for ubuntu 24.04: https://gist.github.com/dziamid/0de2761e0ecc4b3e68e2461c60f82930

@runejac
Copy link

runejac commented Oct 9, 2024

I am struggling with getting a new public key stored in known_hosts locally when trying to run ssh root@{ip-adress} rather than the public key I set in cloud.init script. Using @dziamid new file for Ubuntu 24.04, also added the systemctl restart ssh || systemctl restart sshd, does anyone know why this happens?

@ConspiredMinds
Copy link

Here's an updated version for ubuntu 24.04: https://gist.github.com/dziamid/0de2761e0ecc4b3e68e2461c60f82930

+1

Thanks!

@dwatek
Copy link

dwatek commented Nov 21, 2024

@dziamid I still can't get in to log in to the username I create with the password 'changeme' it says Permission denied, please try again. Is this config sure to be correct?

@SebastianArce
Copy link

SebastianArce commented Dec 1, 2024

Hey @dwatek , I'm facing the same issue. Did you have any luck?

@dwatek
Copy link

dwatek commented Dec 2, 2024

@SebastianArce remember to also add #cloud-config at top of file. I thought it was just a comment, but without it the whole config didn't work

@SebastianArce
Copy link

@dwatek yes, that was it. Thanks!

@shanneykar
Copy link

Can someone explain Disable needrestart prompts ? Should we enable again at the end?
Can we use -apt full-upgrade -y as - apt upgrade -y --allow-downgrades --allow-remove-essential --allow-change-held-packages seems risky?

@dsfaccini
Copy link

Can someone explain Disable needrestart prompts ? Should we enable again at the end? Can we use -apt full-upgrade -y as - apt upgrade -y --allow-downgrades --allow-remove-essential --allow-change-held-packages seems risky?

disbaling needrestart is perfect for the cloud-init automation, we reboot at the end anyway, so there’s no need to re-enable it here
prompts would only matter for later manual updates, and you can remove this file post-setup if you want them back

as for the apt upgrade yeah, unless cj was fixing some specific issue requiring downgrades or removals, I agree apt full-upgrade -y is a safer bet

@arthureberledev
Copy link

i kept getting the error root@[xxx.xxxx.xxx.xxx]: Permission denied (publickey) error in the newest version till i found out that coolify somehow added a key (something like <key>/x coolify) to the authorized_keys key file but didnt write it to a new line, so it was appended after the already existing key. Moving the key to a new line fixed the issue for me

@zfbx
Copy link

zfbx commented Apr 7, 2025

@SebastianArce remember to also add #cloud-config at top of file. I thought it was just a comment, but without it the whole config didn't work

Thank you x_x that is so dumb I've been struggling for a while because of that simple thing

@florianmartens
Copy link

florianmartens commented Apr 25, 2025

Hm, not of the provided fixes worked for me :(

Here's a version that worked for me (it has some bigger changes compared to the original):

#cloud-config
users:
  - name: yourname
    ssh_authorized_keys:
      - "<SSH_KEY>"
    sudo: ALL=(ALL:ALL) ALL
    groups: sudo
    shell: /bin/bash
chpasswd:
  expire: true
  users:
    - name: yourname
      password: changeme
      type: text
write_files:
  - path: /etc/ssh/sshd_config.d/99-custom.conf
    content: |
      PermitRootLogin without-password
      PubkeyAuthentication yes
      PasswordAuthentication no
runcmd:
  - 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

@vlad1mirJ
Copy link

I have also encountered issues with running cloud init on Hetzner Ubuntu 24.04 ARM VPS. Unfortunately, sshd was not aliased so I had to use systemclt restart ssh instead.

Here is a config that ended up working for me

#cloud-config
users:
  - name: <username>
    ssh_authorized_keys:
      - <pub_ssh_key>
    sudo: ALL=(ALL:ALL) ALL
    groups: sudo
    shell: /bin/bash
chpasswd:
  expire: true
  users:
    - name: <username>
      password: changeme
      type: text
package_update: true
package_upgrade: true
runcmd:
  - sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin without-password/' /etc/ssh/sshd_config
  - sed -i -e '/^\(#\|\)PubkeyAuthentication/s/^.*$/PubkeyAuthentication yes/' /etc/ssh/sshd_config
  - sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
  - systemctl restart ssh
  - echo "\$nrconf{kernelhints} = -1;" > /etc/needrestart/conf.d/99disable-prompt.conf
power_state:
  delay: 1
  timeout: 60
  mode: reboot
  message: Rebooting after cloud init

@zeropaper
Copy link

@vlad1mirJ your solution worked like a charm for me (Ubuntu 24.04). Thanks!

@stumbata
Copy link

@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

@ahsan-abrar
Copy link

ahsan-abrar commented Dec 27, 2025

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 "-------------------------------------------------------"

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