---
title: "Coolify setup lessons from shipping this site"
description: "Deploying dacforge.com on Coolify and Hetzner taught us four things a shell script install will not. A short field report."
canonical: https://dacforge.com/blog/coolify-setup-lessons/
date: 2026-04-17T00:00:00.000Z
tags: ["coolify", "devops", "self-hosting"]
---

We ship clients on [Coolify](https://coolify.io) because it is the least sad path to running your own PaaS in 2026. You get Heroku-style ergonomics on a server you own, Docker underneath, and no seat-based pricing surprise in month eight. The install script is a one-liner. The _production-ready_ install is not.

This site, [dacforge.com](https://dacforge.com), runs on Coolify on Hetzner. Setting it up took about a day of focused work. Here are the four gotchas that ate the most time, written up for anyone standing up their first Coolify box.

## 1. The port-80 trap

Coolify's app settings default the "ports exposes" field to `3000`. That default is right for a Node app. It is wrong for a static site served by nginx inside your container.

We deployed the first version of this site and got a polite **502 Bad Gateway** from Traefik. The container was running. The nginx inside it was answering on port 80. Traefik was trying to reach it on port 3000. Nothing was listening there.

The fix is to set the app port to `80` in Coolify's UI, matching where the in-container nginx actually listens. The UI does not surface this as a likely gotcha, so if you are deploying anything other than a Node app for the first time, check your container's listen port against the Coolify field before you wonder why TLS termination hates you.

## 2. The HTTPS→HTTP→HTTPS redirect chain

The second 502 was more interesting. Every non-trailing-slash URL on the site went through two redirects:

```
GET https://dacforge.com/work     → 301 → http://dacforge.com/work/  → 307 → https://dacforge.com/work/
```

Two hops. The first hop drops to plain HTTP for a microsecond before Traefik bounces it back. Google's crawler tolerates one redirect, grudgingly tolerates two, and occasionally stops following after the first.

The cause was nginx emitting _absolute_ redirects from inside the container. Traefik handles TLS termination upstream, so the nginx process sees the request come in as plain HTTP. When nginx issues a trailing-slash redirect, it writes the scheme it sees (`http://`) into the Location header. Traefik then has to re-redirect that to HTTPS.

The fix is a three-line addition to `nginx.conf`:

```nginx
absolute_redirect off;
server_name_in_redirect off;
port_in_redirect off;
```

With those set, nginx emits _relative_ redirects (just a path, no scheme). The browser carries forward whatever scheme the original request used, and the chain collapses to one hop. You need a custom `nginx.conf` copied into your Docker image; the default config the `nginx:alpine` base image ships with will not set these.

## 3. Cloudflare, proxy off

Cloudflare is lovely for DNS, a trap for TLS until you commit to it.

We put the domain on Cloudflare with the orange cloud _off_ on every record. That tells Cloudflare to act as a dumb resolver: it answers DNS, it does not terminate TLS. Coolify and Traefik then see the real client IP, issue real [Let's Encrypt](https://letsencrypt.org/) certificates via HTTP-01, and renew them quietly.

The alternative, proxy _on_, is not wrong but is not cheap. You have to flip Cloudflare's TLS mode to Full (strict), make sure your origin cert is trusted or your upstream has a valid Let's Encrypt cert that Cloudflare can verify, and accept that Cloudflare sits in every hot path. For a small site that is overhead for a benefit we do not currently need. If you are going to end up using Cloudflare's WAF or Workers, flip it on deliberately. Otherwise leave it off and let Let's Encrypt do its job without a proxy in the way.

## 4. The runbook is the part nobody writes

The thing we have had to rebuild from memory three times, and will never rebuild from memory again, is the runbook. A Coolify setup is not complete without:

- **Which defaults in the UI are wrong for your case.** Port 80 vs 3000 is one. Health checks are another (Coolify's default interval is aggressive; turn it down for slow-cold-starting apps).
- **How to deploy a new app from scratch.** One page, screenshots, the exact order of clicks.
- **How to roll back a bad release.** Coolify keeps previous deploys; there is a button. Know where it is _before_ you need it.
- **Where the logs actually live.** Application logs in the Coolify UI; Docker logs on the server; Traefik access logs in the Traefik container. All three have a reason to be consulted; none of them is the default place you will look.

This is the part most consultants skip and most self-installers learn the expensive way. If you are setting up Coolify for yourself, write the runbook for the you of six months from now who has forgotten how TLS renewal works.

## That is most of it

Coolify is a small, opinionated, genuinely nice PaaS. The install script does the install; the gotchas above are what it does not warn you about. If you want help landing your own setup cleanly, our [Coolify setup consultant](/services/coolify-setup-consultant/) engagement is the one-block version of this post.