# Troubleshooting

Find what you see, then fix it.

Start with the containers and their logs, in the install folder. A part that says `Restarting` or `(unhealthy)` is where to look; its log starts with the reason.

```bash
docker compose ps
docker compose logs -f api worker
```

- [“another installation already runs as the compose project …”](https://docs.gorunner.io/self-host/troubleshooting#another-installation): Setup
- [“could not pull the images”, or “unauthorized”](https://docs.gorunner.io/self-host/troubleshooting#could-not-pull): Setup
- [“1 problem. GoRunner was not started.”, or “Nothing was started, and .env was not changed.”](https://docs.gorunner.io/self-host/troubleshooting#the-check-found-a-problem): Setup
- [“… does not resolve yet”, or “… does not answer over https yet”](https://docs.gorunner.io/self-host/troubleshooting#the-address-does-not-open-or-has-no-certificate): Setup
- [“Could not get v2026.10.30’s bundle …”](https://docs.gorunner.io/self-host/troubleshooting#update-cant-get-the-bundle): Update
- [“1 problem. Nothing was changed: … is still running.”](https://docs.gorunner.io/self-host/troubleshooting#update-check-problem): Update
- [The address doesn’t open, or has no certificate](https://docs.gorunner.io/self-host/troubleshooting#the-address-does-not-open-or-has-no-certificate): The address
- [A test install: “fetch failed”, CERTIFICATE_VERIFY_FAILED, or the embed doesn’t load](https://docs.gorunner.io/self-host/troubleshooting#a-test-install-certificate): The address
- [“That setup code isn’t right.”, or a lost code](https://docs.gorunner.io/self-host/troubleshooting#setup-code): Signing in
- [The sign-in email doesn’t arrive](https://docs.gorunner.io/self-host/troubleshooting#sign-in-email): Signing in
- [“That email and password don’t match.”, “Too many wrong passwords.”, “Too many sign-in attempts from this network.”](https://docs.gorunner.io/self-host/troubleshooting#password): Signing in
- [“The database CA file … is not a PEM certificate” and similar](https://docs.gorunner.io/self-host/troubleshooting#database-ca-file): Starting
- [A container keeps restarting: REFUSING TO START: …](https://docs.gorunner.io/self-host/troubleshooting#refusing-to-start): Starting
- [“Paused.”](https://docs.gorunner.io/self-host/troubleshooting#paused): The license
- [A license is refused, or “The configured license was refused”](https://docs.gorunner.io/self-host/troubleshooting#the-license-is-refused): The license
- [“Platform needs a two-factor sign-in”](https://docs.gorunner.io/self-host/troubleshooting#the-platform-console-does-not-open): Platform
- [The only platform admin lost two-factor](https://docs.gorunner.io/self-host/troubleshooting#the-only-admin-lost-two-factor): Platform
- [No platform admin can sign in](https://docs.gorunner.io/self-host/troubleshooting#no-platform-admin-can-sign-in): Platform

## “another installation already runs as the compose project …”

**Why**

This server already runs a GoRunner under that name, from the folder the message names. Two would share their containers and data, so the setup changes nothing.

**Fix**

Give the second one its own name and ports:

```bash
./setup.sh --project gorunner-2 --https-port 8443 --http-port 8080
```

## “could not pull the images”, or “unauthorized”

**Why**

The server can’t reach the registry, isn’t signed in to it, or the version doesn’t exist. GoRunner’s images are private.

**Fix**

1. Sign in again with your GitHub account’s user name and its read-only token: `docker login ghcr.io -u <user name>`. If the token ended, make a new one. See [Sign in to the registry](https://docs.gorunner.io/self-host/install-docker#sign-in-to-the-registry).
2. Check the version’s spelling, with its `v`.
3. For a mirror, sign in to it and give the setup its address: `./setup.sh --registry registry.example.com/ralve-org`.

## “1 problem. GoRunner was not started.”

**Why**

The preflight check found something GoRunner can’t work with. The `✗` lines above it say what, and what to change. On a re-run, `.env` is put back: “Nothing was started, and .env was not changed.”

**Fix**

1. Fix what the line says, and run `./setup.sh` again. [The preflight check](https://docs.gorunner.io/self-host/preflight-check) lists what it tests.
2. Sure it doesn’t matter? Run the command after “Sure it’s fine?”: yours, with `--skip-check`.

“Going on would put data GoRunner already holds at risk” is never skipped: put the right encryption key back, or switch file storage on again.

## “Could not get v2026.10.30’s bundle …”

**Why**

The version is misspelt, or the server isn’t signed in to the registry. Nothing was changed.

**Fix**

1. Check the version, with its `v`.
2. Sign in again: `docker login ghcr.io -u <user name>`. If your token ended, make a new one on GitHub.

## “1 problem. Nothing was changed: … is still running.”

**Why**

The new version’s check found something to fix first. The old version keeps running.

**Fix**

1. Fix what the lines above it say.
2. Run the same `./deploy.sh` again.

“building index … waited 1m0s for older transactions to end” means a long transaction, often a `pg_dump`, held up the migrations. Let it end, then run it again. [Going back](https://docs.gorunner.io/self-host/update#go-back) has its own rules.

## The address doesn’t open, or has no certificate

**Why**

With automatic HTTPS, Caddy gets the certificate only once the domain points at the server and ports 80 and 443 reach it. Behind your own proxy, the proxy may not reach GoRunner.

**Fix**

1. Check the domain’s A (and AAAA) record has the server’s public address, and the firewall opens 80 and 443.
2. Read why: `docker compose logs -f caddy`. Caddy tries again by itself.
3. Behind your own proxy, ask GoRunner directly on the server. `{"status":"ok"` means the problem is in the proxy.

```bash
curl -sS http://127.0.0.1:8080/api/ready
```

The setup warns “… does not resolve yet” and goes on: Caddy keeps trying. It stops at “… does not answer over https yet”. Once DNS is right, run `./setup.sh` again and answer **y** to “Check that everything is running with these answers?”.

Let’s Encrypt issues at most five certificates for one name a week. Never run `docker compose down -v`: it deletes Caddy’s.

## A test install: “fetch failed” or `CERTIFICATE_VERIFY_FAILED`

**Why**

A test install on a laptop ([`--tls internal`](https://docs.gorunner.io/self-host/install-docker#testing-on-a-laptop-first)) uses Caddy’s own certificate. Browsers warn about it, and programs refuse it: your app’s server reports `fetch failed` (Node) or `CERTIFICATE_VERIFY_FAILED` (Python).

**Fix**

Save Caddy’s own authority in the install folder, and point your programs at it:

```bash
docker compose exec -T caddy cat \
  /data/caddy/pki/authorities/local/root.crt > gorunner-test-ca.crt
curl --cacert gorunner-test-ca.crt https://gorunner.localhost:8443/api/ready
NODE_EXTRA_CA_CERTS=/path/to/gorunner-test-ca.crt node node.mjs
SSL_CERT_FILE=/path/to/gorunner-test-ca.crt python3 python.py
```

In the browser, accept the warning for this address before you try Embed, or the embed fails with nothing but a failed load in the console. For an Embed test, allow `http://localhost:3000` under **Allowed websites**, and leave the key’s address list empty: the installation sees your app’s calls from Docker’s own network.

## “That setup code isn’t right.”

**Why**

The code is 8 letters and digits, such as `K7QM-4RZP`, and only the server shows it. After 10 wrong tries from one network, the page asks you to wait 15 minutes.

**Fix**

1. Print it again on the server:

**Docker**

```bash
docker compose exec api gorunner setup-code
```

**Kubernetes**

```bash
kubectl --namespace gorunner exec deploy/gorunner-api -- gorunner setup-code
```

“This installation already has an admin” means the account exists: sign in. With `GORUNNER_ADMIN_EMAIL` set, only that address can be the first admin.

## The sign-in email doesn’t arrive

**Why**

It went to spam, the mail service refused it, or the installation is paused, when only platform admins get one.

**Fix**

1. Look in the spam folder, and wait a minute before asking again: one address may ask once a minute and five times an hour, one network 20 times an hour. Only the newest email’s link and code work.
2. Read what the mail service answered: `docker compose logs api`.
3. Check the sender’s domain is verified with Resend, or has SPF and DKIM on your SMTP server.
4. Run `docker compose run --rm check`: it signs in to the mail server without sending.

Port 465 expects TLS from the first byte; elsewhere set `SMTP_SECURE=tls` for that. Mixed up, it looks like a timeout. A mail server with a private certificate needs it in `certs/`. Some clouds block mail ports: use another port, or Resend. Change the answers with `./setup.sh`.

## “That email and password don’t match.”

**Why**

The password is wrong, there is no account with that address, or the person has no password yet. All three look the same on purpose.

**Fix**

1. An owner or admin of their organization makes them a reset link. See [Someone forgot their password](https://docs.gorunner.io/self-host/sign-in-without-email#someone-forgot-their-password).
2. An admin who forgot their own runs `gorunner reset-password` on the server.

“Too many wrong passwords.” lifts after 15 minutes, or with a reset link. “Too many sign-in attempts from this network. Try again later.” counts wrong passwords from one network; the right password clears it. “This installation has no free seat” means every seat of the license is taken.

## “Paused.”

**Why**

The license ended after its grace days, or no valid license is loaded. Nothing runs, and only platform admins can sign in.

**Fix**

1. Next to **Platform admin?**, press **Sign in**.
2. Load a license on the page that follows. See [Start it again](https://docs.gorunner.io/self-host/license-ends#start-it-again).

## A license is refused

**Why**

The reason shows under the field.

**Fix**

| It says | Do this |
| --- | --- |
| “That isn’t a license. Check that the whole text was pasted.” | Copy the whole file again, first character to last. |
| “This license wasn’t issued for this product, or it was changed after it was issued.” | Copy it again from the original. Still refused? Ask GoRunner. |
| “This license has already ended.” | Ask GoRunner for a renewal. If it should be valid, check the server’s clock. |
| “This license names an edition this version doesn’t know.” | [Update](https://docs.gorunner.io/self-host/update) to the version GoRunner gives you. A free license needs v2026.10.3 or newer. |
| “This build doesn’t accept free licenses. Use a GoRunner self-host image, v2026.10.3 or newer.” | Install from GoRunner’s self-host image. |
| “The configured license was refused, so this installation runs without a license.” | The `LICENSE_KEY` in `.env` isn’t valid. Load a valid license on the License card, which takes its place, or fix `LICENSE_KEY`. |
| “This installation has 2 organizations, 2 workspaces and 5 people …” | It is bigger than the free license. Remove what is over, or keep a paid one. [The free license](https://docs.gorunner.io/self-host/free-license#at-the-limits) |

## “The database CA file … is not a PEM certificate” and similar

**Why**

GoRunner couldn’t use the certificate authority file for your database. The line names the file.

**Fix**

| It says | Do this |
| --- | --- |
| “… could not be read”, or “POSTGRES_CA_FILE … cannot be read” | Put the file in `certs/` and give its path inside the container, such as `/certs/rds.pem`. |
| “… is not a PEM certificate” | Convert it: `openssl x509 -inform der -in ca.der -out ca.pem`. |
| “… holds a PEM certificate that could not be parsed” | Download the CA again, as your database provider publishes it. |
| “… holds no certificate, only …” | Give the CA’s certificate, a `-----BEGIN CERTIFICATE-----` block, not a key. |

## A container keeps restarting: `REFUSING TO START`

**Why**

GoRunner runs the quick part of the check at every start, and stops when something is missing or wrong. The sentence after `REFUSING TO START:` says what.

**Fix**

```bash
docker compose logs api | grep "REFUSING TO START"
docker compose logs caddy postgres redis
```

Fix it in `.env` and apply it with `./deploy.sh "$(cat VERSION)"`.

## “Platform needs a two-factor sign-in”

**Why**

The console opens only for a session signed in with two-factor. Either your account has none yet, or you turned it on during this session.

**Fix**

1. Turn it on. See [Turn on two-factor](https://docs.gorunner.io/self-host/first-sign-in#turn-on-two-factor).
2. Press **Sign out and sign in again**, and type the code.

No **Platform** in the menu means your account isn’t a platform admin. Every address showing the license page means the installation is paused.

## The only platform admin lost two-factor

**Why**

Another platform admin would open the person in **Platform › People** and choose **Reset two-factor…**. There is no other admin.

**Fix**

Someone with the server runs:

**Docker**

```bash
docker compose exec api gorunner reset-two-factor dana@example.com
```

**Kubernetes**

```bash
kubectl --namespace gorunner exec deploy/gorunner-api -- \
  gorunner reset-two-factor dana@example.com
```

It works for a platform admin only. It turns their two-factor off, deletes their recovery codes, ends their sessions and records it in the audit log. When it works, it says “Two-factor is off for dana@example.com, and every session they had has ended.” They then turn two-factor on again.

## No platform admin can sign in

**Why**

Nobody can open the Platform console.

**Fix**

The server’s operator has a console of their own at `/admin`, the **Instance console**. It is closed until you set a secret.

1. Make a secret with `openssl rand -hex 32` and set it as `PLATFORM_ADMIN_SECRET`: in `.env`, then `./deploy.sh "$(cat VERSION)"`; on Kubernetes, in your Secret.
2. Open `https://automations.example.com/admin`, type it as **Console secret** and press **Unlock**.

There you can **Activate license** or **Revoke license…**. The same secret, in an `X-Platform-Admin` header, makes an existing account a platform admin:

```bash
curl -X POST https://automations.example.com/api/instance/platform-admins \
  -H "X-Platform-Admin: $SECRET" -H "Content-Type: application/json" \
  -d '{"email":"dana@example.com"}'
```

Remove the secret again afterwards.

## On Kubernetes

| You see | Do this |
| --- | --- |
| `helm install` stops with a sentence such as “encryptionKey is required…” | A value is missing or wrong; the sentence names it. |
| The migrations Job fails | Read its check: `kubectl logs job/<its name> -c check`. |
| Pods stay in `ImagePullBackOff` | The cluster can’t download the image: check it reaches `ghcr.io`, the `ghcr` Secret and `imagePullSecrets`. |
| Pods stay in `Init:0/1` | They wait for the migrations. Read the Job’s log; `kubectl get jobs` lists it. |
| App pods run but aren’t ready | GoRunner can’t reach the database or Redis: `kubectl logs deploy/gorunner-api`. |
| The address answers 404 or 502 | The Ingress has no address yet, or DNS doesn’t point at it: `kubectl get ingress`. |
| 502 or 504 while the pods are ready | The network policy doesn’t admit your controller: set `networkPolicy.apiFrom`. |
| The live run view stays still | The ingress buffers. [The ingress](https://docs.gorunner.io/self-host/install-kubernetes#the-ingress) |

## Something else

- **Everyone had to sign in again**: Redis lost its data. No run was lost, but one in progress that could change something outside failed with **Progress unknown**. [What Redis holds](https://docs.gorunner.io/self-host/own-stores#what-redis-holds)
- **A step failed with “Outcome unknown”**: Its worker stopped without warning, such as out of memory, or Redis was down over 2 minutes. Another worker carried on from what was finished, within about 10 seconds, or about 3 minutes when the server was gone. A step that changes something outside isn’t sent again. A run whose worker stops 3 times is stopped for good. [A run failed](https://docs.gorunner.io/build/a-run-failed#outcome-unknown)
- **“This address can’t be reached from workflows: …”**: [Outbound access](https://docs.gorunner.io/self-host/outbound-access#when-a-step-is-refused)
- **The audit log shows your proxy’s address for everyone**: [The address ranges to trust](https://docs.gorunner.io/self-host/own-proxy#the-address-ranges-to-trust)
- **Steps can’t read or save their data**: Read `docker compose logs seaweedfs seaweedfs-s3`. The relay follows a restarted file store within a minute.
- **The address is gone after the server restarted**: Run `docker compose up -d`. It never loses data; `down -v` does.
- **The License card says **Embed: not in this license****: Your license has no Embed. Ask GoRunner for one that does.

## Still stuck?

Send GoRunner the output of `docker compose ps`, the last lines of the log, and the `VERSION` file. Never send `.env`: it holds your passwords.

Source: https://docs.gorunner.io/self-host/troubleshooting
