Coolify is an open-source self-hosted PaaS that gives you one-click deployments, automatic SSL, and a clean dashboard — without paying Heroku or Render prices. By the end of this guide you'll have Coolify running on a Hetzner CAX11 ARM VPS (2 vCPU, 4 GB RAM, $6/month), secured behind a firewall, serving HTTPS apps from a real domain.
Prerequisites
- A Hetzner Cloud account with billing enabled
- A domain name with access to its DNS settings
- SSH key already added to your Hetzner account
- Local machine running macOS or Linux
- Basic comfort with
sshandsystemctl
1. Provision the Hetzner CAX11 Server
Log in to console.hetzner.cloud, create a new project, then click Add Server with these settings:
| Field | Value |
|---|---|
| Location | Falkenstein (FSN1) or Nuremberg (NBG1) |
| Image | Ubuntu 24.04 |
| Type | CAX11 (Arm64, 2 vCPU, 4 GB) |
| SSH key | your pre-uploaded key |
| Firewall | none for now — you'll add one in step 3 |
Note the public IPv4 address once the server is running. Hetzner provisions in under 30 seconds.
2. Connect and Update the Server
1. SSH in as root.
ssh root@<YOUR_SERVER_IP>
2. Update all packages.
apt update && apt upgrade -y
Expected output ends with a line similar to:
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
(The exact counts vary; what matters is no errors.)
3. Set the hostname.
hostnamectl set-hostname coolify
4. Create a non-root sudo user.
useradd -m -s /bin/bash deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
Coolify's installer runs fine as root, but having a deploy user lets you SSH in without root after setup.
3. Lock Down the Firewall with UFW
Coolify needs ports 22 (SSH), 80 (HTTP), and 443 (HTTPS) open. Port 8000 is Coolify's management UI — expose it only temporarily during first login, then proxy it behind Nginx. Following best VPS hardening practices, we'll configure UFW to restrict access by default.
5. Install and configure UFW.
apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 8000/tcp comment 'Coolify UI - temporary'
ufw --force enable
6. Verify the rules are active.
ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 8000/tcp ALLOW IN Anywhere
4. Install Coolify
Coolify ships a single-command installer that pulls the latest stable release, installs Docker, and starts all required containers.
7. Run the official installer.
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
The script takes 2–4 minutes on CAX11. You'll see it:
- Install Docker Engine (latest stable)
- Pull the
coolifyandcoolify-dbimages - Start the
coolifysystemd service
Expected final lines:
[INFO] Coolify is ready!
[INFO] Please visit http://<YOUR_SERVER_IP>:8000 to get started.
8. Confirm the Coolify service is running.
systemctl status coolify
● coolify.service - Coolify
Loaded: loaded (/etc/systemd/system/coolify.service; enabled; preset: enabled)
Active: active (running) since ...
5. Point Your Domain and Finish First-Run Setup
9. Create a DNS A record.
In your DNS provider's dashboard, add:
| Type | Name | Value | TTL |
|---|---|---|---|
| A | coolify |
<YOUR_SERVER_IP> |
300 |
This creates coolify.yourdomain.com. You'll use this subdomain for the management UI.
10. Open the setup wizard.
Navigate to http://<YOUR_SERVER_IP>:8000 in a browser. Complete the wizard:
- Create your admin account (email + password)
- On the Instance Settings screen, set App URL to
https://coolify.yourdomain.com - Enable Auto-Update if you want unattended upgrades (safe for this use case)
Coolify will now issue a Let's Encrypt certificate for coolify.yourdomain.com automatically via its built-in Traefik proxy.
11. Close port 8000 once HTTPS is confirmed working.
ufw delete allow 8000/tcp
ufw status numbered
After this, the UI is only reachable at https://coolify.yourdomain.com.
6. Deploy Your First App
This step deploys a sample Node.js app from a public GitHub repo to confirm the full pipeline works.
12. Add a server resource.
In the Coolify dashboard:
- Go to Servers → Add Server
- Choose Localhost (the same machine Coolify runs on)
- Click Validate Server — it should pass all checks
13. Create a new project and application.
- Projects → New Project → Create
- Inside the project, click Add New Resource → Application
- Source: Public Repository
- Repository URL:
https://github.com/render-examples/express-hello-world - Branch:
main - Build Pack: Nixpacks (auto-detected)
14. Set the domain for the app.
In the app's General settings, set Domains to:
https://hello.yourdomain.com
Add a corresponding DNS A record pointing hello.yourdomain.com to your server IP before deploying.
15. Deploy.
Click Deploy. Coolify clones the repo, builds the image with Nixpacks, starts the container, and provisions a Let's Encrypt cert. The build log streams in real time in the dashboard.
A green Running badge appears when the container is healthy.
7. Verify It Works
Check the Coolify service:
systemctl is-active coolify
active
Check running containers:
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
You should see coolify, coolify-db, coolify-redis, coolify-proxy (Traefik), and your deployed app container.
Test the HTTPS endpoint:
curl -I https://hello.yourdomain.com
HTTP/2 200
server: Caddy
content-type: text/html; charset=utf-8
(Coolify uses Traefik internally; the server header may vary by app.)
Check certificate expiry:
echo | openssl s_client -connect hello.yourdomain.com:443 -servername hello.yourdomain.com 2>/dev/null \
| openssl x509 -noout -dates
notBefore=...
notAfter=... (90 days out)
8. Troubleshooting
Coolify UI not loading on port 8000
Run docker ps and confirm the coolify container is up. If not: systemctl restart coolify. Check logs with docker logs coolify --tail 50.
Let's Encrypt certificate not issuing
Confirm the DNS A record has propagated: dig +short coolify.yourdomain.com. Port 80 must be reachable from the internet for the HTTP-01 ACME challenge — verify with ufw status.
Build fails with "nixpacks: command not found"
Nixpacks is bundled inside the Coolify container. If you see this error in a build log, the container may have started before Docker finished pulling all layers. Run systemctl restart coolify and redeploy.
App container restarts in a loop
Open the app's Logs tab in the Coolify dashboard. The most common cause on ARM (CAX11) is an x86-only base image. Switch the build pack to Dockerfile and use a FROM --platform=linux/arm64 base image, or choose an image that publishes a multi-arch manifest.
Disk fills up over time Coolify keeps old build images. Add a weekly cron to prune them:
crontab -e
Add:
0 3 * * 0 docker system prune -af --filter 'until=168h' >> /var/log/docker-prune.log 2>&1
Next Steps
- Add a Hetzner Volume ($0.0476/GB/month) and mount it at
/var/lib/dockerbefore your disk fills up — do this before you accumulate data, not after. - Enable S3-compatible backups in Coolify's instance settings using Hetzner Object Storage (or Backblaze B2) to back up your Postgres databases automatically.
- Set resource limits per app container under Advanced → Resource Limits to prevent a runaway app from starving Coolify itself on the shared 4 GB RAM.
- Upgrade to CAX21 (4 vCPU, 8 GB, ~$14/month) once you're running more than three concurrent apps — the CAX11 will start swapping under load.
You now have a self-hosted PaaS that handles builds, deploys, SSL, and reverse proxying for $6/month. Every app you add to it costs nothing extra in platform fees. For more on keeping your infrastructure secure and performant, this guide on Core Web Vitals covers optimization best practices that apply to any self-hosted deployment.