KoolBox Documentation

Install KoolBox

A complete, step-by-step install of the KoolBox private AI control panel on a Linux server you control — including a fully offline install for an isolated network.

Before you start

Verified on Ubuntu 24.04 and RHEL 9.6, releases 1.3.0 to 1.3.2.

The commands on this page were run on a clean Ubuntu 24.04 LTS server and a clean RHEL 9.6 server, across releases 1.3.0, 1.3.1 and 1.3.2, and the blocks marked as output are what those servers actually printed — including where the two distributions behave differently, and where the product has a defect you have to work around. Debian, Rocky Linux, AlmaLinux and Fedora are supported by the packages but were not part of that run.

What was not exercised, and says so where it appears: a real certificate (needs public DNS), Method C (needs a separate database server), a genuinely disconnected host — both servers kept their SSH session, so resolving operating-system dependencies from local media is untested — a reboot to watch the disabled-at-boot defect bite, ARM, and on the licensing side offline activation and renewal, where online activation was carried out for real but the other two were only verified as far as the interface.

No prior experience with the product is assumed. Every command below is shown in full, with what you should expect to see and what to do if it does not happen.

You will need three things:

  • A Linux server you control, with a user who can run sudo.
  • A way to reach that server's terminal — SSH, or a console in your cloud provider's web interface.
  • An activation code from Koolify, or — for an isolated network — an approved channel to exchange two small files with Koolify. Activation is covered on its own page, and a new installation gets a 3-day grace period, so you can install first.
No JavaScript tooling required. You do not need to install Node.js, npm, a web server, or any JavaScript tooling. The package brings its own runtime.

Check your server is suitable

Supported operating systems

Scroll to see all columns →

Operating systems and architectures supported by the KoolBox packages.
FamilyVersionsArchitecture
Ubuntu22.04 LTS, 24.04 LTS, 26.04 LTSx86_64
Debian12, 13x86_64
RHEL / Rocky Linux / AlmaLinux9, 10x86_64
FedoraCurrent supported releasesx86_64

Not on the list? The installer stops and tells you, rather than installing something that cannot work. It names the family it detected, so you can see it guessed right.

No Red Hat subscription is needed on the RHEL family: on the verification run, every dependency — PostgreSQL, LibreOffice and Java 17 included — came from the image's own baseos and appstream repositories.

To check what you have, run:

cat /etc/os-release
uname -m

uname -m must print x86_64 (some tools call it amd64).

ARM servers: do not start. The published packages are x86_64 only. aarch64 is a build target but nothing is published for it: the ARM repositories carry only the architecture-independent koolbox-full meta-package and no console package at all. So an install on an ARM server registers the repository, looks healthy, and then finds nothing to install. If uname -m reports aarch64, talk to Koolify before you begin rather than working through this page.

Hardware

  • Memory: 4 GB minimum, 8 GB recommended. This matters more than it sounds. Installing and upgrading unpacks several hundred megabytes, and on a 1 GB machine the operating system can terminate the package manager partway through, which leaves a half-installed package you have to repair by hand.
  • Disk: 20 GB.
  • PostgreSQL 13 or newer. Either on the same server (the installer can set this up for you) or reachable over the network — Amazon RDS, Azure Database for PostgreSQL, or a cluster you already run. RHEL 9 ships PostgreSQL 13 exactly — the minimum, with no margin, which is why releases before 1.3.0 had trouble there. Ubuntu 24.04 ships 16.

Check memory and disk with:

free -m          # look at the "total" column under Mem
df -h /          # look at the "Avail" column

Decide which package you need

There are two. Pick one now; you can change later, but it is easier to choose correctly.

Scroll to see all columns →

The two KoolBox packages and when to choose each.
PackageWhat it installsChoose this when
koolboxThe console onlyPostgreSQL already runs somewhere else — RDS, Azure, an existing cluster, or a separate database server
koolbox-fullThe console plus a local PostgreSQL server and LibreOfficeEverything on one server. It creates and initializes the database for you
If you are unsure. Choose koolbox-full. It is the single-server option and needs no database work from you.

Network

Scroll to see all columns →

Network access required to reach, install and activate KoolBox.
WhatDirectionDetail
Reaching the consoleInboundTCP port 3000 by default (changeable), or 80/443 if you put a reverse proxy in front — see Domain and HTTPS.
Downloading the softwareOutbound HTTPSpackages.koolify.ai
Activating a license onlineOutbound HTTPSThe Koolify activation service
For an isolated or air-gapped network. None of the outbound connections are required. Method D covers installing with no internet access, and offline activation covers licensing the same way. Once set up that way, the product makes no outbound calls at all.

Install

Choose one of the four methods below. Method A is right for almost everyone.

Method A — the one-line installer (recommended)

Log in to your server and run one of these. Everything on one server, including the database:

curl -fsSL https://packages.koolify.ai/install.sh | sudo sh -s -- --full

Console only, because your database is elsewhere:

curl -fsSL https://packages.koolify.ai/install.sh | sudo sh
On RHEL, Rocky Linux, AlmaLinux and Fedora, download the script first.

Download it, then run it. The piped form works, but dnf's key-import prompt reads the same standard input that the shell is reading the script from, which produces a misleading error and swallows the rest of the script's own output.

curl -fsSL https://packages.koolify.ai/install.sh -o install.sh
sudo sh install.sh --full

Piped on RHEL, the key-import prompt consumes the script and you see this instead:

Piped install on RHEL 9.6
Importing GPG key 0x403BE624:
 Fingerprint: 9F1B A391 E54B 66E9 6F25 E3E1 17BB 084F 403B E624
Is this ok [y/N]: Error: Failed to download metadata for repo 'koolbox': repomd.xml GPG signature verification error: Bad GPG signature

The install still proceeds correctly — dnf install -y imports the key itself a moment later — so that Bad GPG signature line is expected and harmless. What you lose is the script's closing Installation finished. Check the service with: sudo koolboxctl status, which never prints. Running the same script from a file prints it.

What this does. The script detects your distribution, installs Koolify's package-signing key so your system can verify what it downloads, registers the Koolify package repository, and then hands over to your normal package manager. It contains no application code. After it has run once, updates are ordinary apt or dnf operations.

Useful options, added after -s -- in the piped form, or straight after the script name in the two-step form. --full and --version work together:

Scroll to see all columns →

Command-line options accepted by the KoolBox install script.
OptionEffect
--fullInstall koolbox-full (adds the local PostgreSQL server)
--version 1.3.1Install a specific release instead of the newest available
--repo-onlyRegister the repository but install nothing yet
On Debian and Ubuntu, --version cannot reach an older release.

The commands above are fine: a plain one-line install with no --version installed the newest release cleanly on Ubuntu 24.04, exit 0. This is narrower than that — asking --version for anything but the newest release fails outright, and the error it prints is not true: the version it calls unavailable is published and installable.

The script pins only the package you named, so apt is left trying to satisfy koolbox-full's exact-version dependency with the newest koolbox, and gives up:

install.sh --full --version 1.3.0 on Ubuntu 24.04, with 1.3.1 published
The following packages have unmet dependencies:
 koolbox-full : Depends: koolbox (= 1.3.0-1) but 1.3.1-1 is to be installed
                Recommends: libreoffice-core but it is not going to be installed
                Recommends: libreoffice-writer but it is not going to be installed
E: Unable to correct problems, you have held broken packages.
ERROR: Version 1.3.0 is not available. List versions with: apt-cache madison koolbox-full

The working form on Debian and Ubuntu is two steps: register the repository, then name both packages at the version you want. The -1 package revision is part of the version string and is required — koolbox=1.3.0 is not a valid version:

sudo sh -c "$(curl -fsSL https://packages.koolify.ai/install.sh)" -- --full --repo-only
sudo apt-get install -y koolbox=1.3.0-1 koolbox-full=1.3.0-1

That completed in 1 m 12 s on a clean server. On the RHEL family the one-line form with --version does work, because dnf resolves the pinned dependency rather than giving up:

curl -fsSL https://packages.koolify.ai/install.sh -o install.sh
sudo sh install.sh --full --version 1.3.0
After --repo-only on the RHEL family, prime the cache. --repo-only exits 0 and signs off with Repository configured. Install with: sudo dnf install koolbox, but it leaves the repository unusable: rpm --import populates rpm's keyring, not dnf's, so every later dnf call against the repository — including as root — fails with repomd.xml GPG signature verification error: Bad GPG signature. Run sudo dnf -y makecache --repo koolbox once and it behaves. The full install path is unaffected, because dnf install -y imports the key itself.

What you should see. Part-way through the run — about 110 lines before the end on Ubuntu, at line 1209 of a 1319-line install — the console package's own post-install step prints a boxed banner with everything you need:

Ubuntu 24.04, release 1.3.0 — printed part-way through the install, not at the end
Setting up koolbox (1.3.0-1) ...
Installing KoolBox 1.3.0
✓ Generated JWT signing secret.
✓ Generated database password for role 'koolbox'.
✓ Created PostgreSQL role 'koolbox'.
✓ Created database 'koolbox'.
✓ Ensured PostgreSQL extensions (unaccent, pg_trgm).
✓ Enabled scram-sha-256 password authentication for 'koolbox' on koolbox (loopback only).
✓ Verified database connectivity as 'koolbox'.
✓ Configuration complete.
Starting koolbox.service to apply schema changes...
Waiting for the service to become ready (up to 120s)...
✓ Service is ready (version 1.3.0, schema 1.3.0).
✓ Database schema is at version 1.3.0.

────────────────────────────────────────────────────────────────────────────
KoolBox Private AI Control Panel 1.3.0 is running.

  Console        http://203.0.113.10:3000
  Sign in        admin@local.koolbox / ChangeMe123!   ← change immediately

Next steps
  1. Sign in and set a new administrator password.
  2. Activate your license: Settings → License & Activation.
     A 3-day grace period starts now; online and offline (air-gapped)
     activation are both supported.
  3. Register your private AI nodes and deploy models.

Administration
  koolboxctl status              health, versions, database, backups
  koolboxctl backup              on-demand database backup
  systemctl restart koolbox      apply configuration changes
  journalctl -u koolbox -f       live logs

Optional: install LibreOffice to enable PDF/ODT document generation.
────────────────────────────────────────────────────────────────────────────
Do not judge the install by the last lines on your screen. After that banner, the package manager carries on with LibreOffice, X11, Perl and needrestart, so it has scrolled well out of sight by the time the run ends. The installer's actual final words carry no URL and no credentials, which reads like a failure and is not one:
The real end of the run, on Ubuntu 24.04
Installation finished. Check the service with:
  sudo koolboxctl status

Future upgrades use the system package manager:
  sudo apt update && sudo apt install --only-upgrade koolbox koolbox-full

Scroll back for the banner, or just run sudo koolboxctl status, which tells you the same things and is covered under Confirm the installation worked. Three details in that banner are worth knowing before you follow it:

  • The Console URL may not be reachable. It is whatever hostname -I returns, which on a cloud server is the private address — see First sign-in.
  • Settings → License & Activation does not exist. The License page is at Administration → License. That section holds exactly six items — Groups / Users, Import / Export, License, Security, Settings and Updates — and nothing called “License & Activation” anywhere. The banner's wording is a product bug, still present on 1.3.2, not a different build.
  • Ignore the LibreOffice line on a --full install. It is not optional and it is not missing: koolbox-full recommends libreoffice-core and libreoffice-writer, so apt is installing them later in the same run — around 120 MB of LibreOffice and X11. What PDF output actually needs is one restart.

On the RHEL family the shape is the same but the wording differs slightly, and the run ends with Complete! followed by the same two closing blocks. A clean RHEL 9.6 server was up and healthy in 49 to 52 seconds, twice.

Do not grep the install log for failures. A completely healthy RHEL install prints ✗ PostgreSQL is installed on this host but is not accepting connections. and a four-command recovery block part-way through, because the console package's setup step runs before koolbox-full has initialized the cluster. Searching the log for ✗ will therefore “find a failure” on a server that is working perfectly. Judge the install by sudo koolboxctl status, which is the point of the next section but one.

If you installed koolbox on its own, without --full, the run instead prints a boxed KoolBox is installed but not yet started banner. That is the expected message when PostgreSQL is not on this server, and Method C takes it from there. On a --full install you may see that same box go past mid-run, printed before the database has been set up — in that case it is stale and you should not follow it.

Installing 1.2.8 or earlier on RHEL 9?. Those releases had a defect that left the console unable to reach its own database on the RHEL family, with no clear failure at the end of the run. It is fixed from 1.3.0, so a current install is not affected — the next section is there for anyone still on an older release.

Method B — register the repository by hand

Use this if your policy forbids piping a script to a shell. The result really is identical to Method A — on Ubuntu the two produce a byte-for-byte identical /etc/apt/sources.list.d/koolbox.sources.

Ubuntu and Debian:

sudo apt update
sudo apt install -y curl gnupg ca-certificates

# Install Koolify's signing key
curl -fsSL https://packages.koolify.ai/koolbox.asc \
  | sudo gpg --dearmor -o /usr/share/keyrings/koolbox-archive-keyring.gpg
sudo chmod 0644 /usr/share/keyrings/koolbox-archive-keyring.gpg

# Register the repository
sudo tee /etc/apt/sources.list.d/koolbox.sources >/dev/null <<'EOF'
Types: deb
URIs: https://packages.koolify.ai/apt
Suites: stable
Components: main
Architectures: amd64
Signed-By: /usr/share/keyrings/koolbox-archive-keyring.gpg
EOF

sudo apt update
sudo apt install -y koolbox-full       # or: koolbox

Leave Architectures: amd64 as it is. Changing it to arm64 does not give you an ARM install — that index carries no console package — see ARM servers.

RHEL, Rocky Linux, AlmaLinux and Fedora:

sudo curl -fsSL -o /etc/yum.repos.d/koolbox.repo \
  https://packages.koolify.ai/koolbox.repo
sudo rpm --import https://packages.koolify.ai/koolbox.asc
sudo dnf install -y koolbox-full       # or: koolbox

These are only for a first install. If you have already run Method A, both steps are redundant and harmless: /etc/yum.repos.d/koolbox.repo already exists and re-downloading it only adds a comment header — the [koolbox] stanza is byte-identical — and rpm --import on an already-imported key prints nothing and exits 0.

The last line is not a no-op on an existing install. On a server that already runs an older release, sudo dnf install koolbox-full does not stop and tell you it is already installed — it silently upgrades you to the newest version in the repository, service restart and schema migration included. If that is not what you want, name the version you have, or use --repo-only in Method A and install nothing.

Method C — using a database on another server

If you installed koolbox and PostgreSQL is elsewhere, the service is deliberately left stopped until you tell it where the database is. Nothing is broken; it is waiting for you.

  1. On your database server, create the database, a user, and the two extensions the product needs for keyword search:

    CREATE USER koolbox WITH PASSWORD 'choose-a-strong-password';
    CREATE DATABASE koolbox OWNER koolbox;
    \c koolbox
    CREATE EXTENSION IF NOT EXISTS unaccent;
    CREATE EXTENSION IF NOT EXISTS pg_trgm;
  2. On the KoolBox server, open the configuration file:

    sudo nano /etc/koolbox/koolbox.env

    Set these five values, then save (Ctrl+O, Enter) and exit (Ctrl+X):

    DB_HOST=db.example.internal
    DB_PORT=5432
    DB_NAME=koolbox
    DB_USER=koolbox
    DB_PASSWORD=choose-a-strong-password

    If your password contains a # or a $, wrap the whole value in double quotes.

  3. Verify the connection and start up:

    sudo koolboxctl setup      # checks the connection, enables the extensions
    sudo koolboxctl migrate    # creates the tables and starts the service

A host name containing a dot turns on TLS to the database automatically, which is what managed services such as RDS require.

This is the one install method that was not exercised on the verification servers, which both ran their database locally. The commands are the same ones koolbox-full runs for you; what we could not measure is a managed provider's own behavior.

Method D — installing with no internet access

The packages have no network dependencies beyond ordinary operating-system packages, so they install fine on an isolated server. The work is in the download, and the two distributions behave so differently here that they are worth reading separately.

Read the version out of the manifest, do not hardcode a filename. Koolify publishes a signed release manifest at https://packages.koolify.ai/updates/latest.json, with its detached signature alongside at latest.json.sig. It names the current version and lists every artifact with a size and a SHA-256. Take the version from there and fetch the matching files: a manifest and a package from different releases will fail verification, and the published set moves faster than you would expect — two releases shipped during a single afternoon of testing.

Debian and Ubuntu. Download on any connected machine of the same distribution — no sudo needed, it writes into the current directory:

apt-get download koolbox koolbox-full
Expected output
Get:1 https://packages.koolify.ai/apt stable/main amd64 koolbox amd64 1.3.2-1 [79.3 MB]
Get:2 https://packages.koolify.ai/apt stable/main amd64 koolbox-full all 1.3.2-1 [4186 B]
Fetched 79.3 MB in 1s (95.1 MB/s)

That is two files and nothing else: apt-get download does no dependency resolution at all. If your isolated server does not already have PostgreSQL and LibreOffice, they have to come from your own distribution media or local mirror. We could not verify that last part — both verification servers kept an SSH session, so their operating-system repositories stayed reachable, and resolving OS dependencies on a genuinely disconnected host is the one step on this page nobody has measured.

RHEL family. dnf can resolve the whole tree for you, which is a real advantage — and it needs root, and the bundle it produces needs one correction before it will install:

sudo dnf download --resolve --destdir ./koolbox-offline \
  koolbox-1.3.0 koolbox-full-1.3.0            # 146 files, 341 MB, about 6 seconds
Pin the version, or you will bundle the wrong one. Run on a host that already has KoolBox, dnf download --resolve koolbox koolbox-full silently fetches the newest published release rather than the one you are running — observed on a 1.3.0 host, which produced 1.3.2 files with no warning. An operator building a bundle “of what we run” gets something else. Name the version on both packages, as above.
The bundle dnf builds does not install as-is.

--resolve pulls postgresql twice, from two different RHEL streams, and the local install then refuses the entire set with cannot install both postgresql-13.23-6.el9_8.x86_64 … and postgresql-13.22-1.el9_6.x86_64. Delete the newer one. Deleting the older one does not work, because postgresql-contrib-13.22 pins postgresql = 13.22-1.el9_6 exactly:

# dnf resolves two versions of postgresql from two streams and the local
# install then refuses the whole set. Keep the one that matches
# postgresql-contrib and delete the other:
ls postgresql-*.rpm
rm -f postgresql-13.23-6.el9_8.x86_64.rpm      # keep 13.22, it matches -contrib

146 files is about 341 MB to carry across, and roughly 1.1 GB once installed — the two numbers sit next to each other in dnf's own transaction summary and are easy to mix up:

Expected output
Transaction Summary
==============================================================================
Install  145 Packages

Total download size: 339 M
Installed size: 1.1 G
dnf download must run as root — and fails twice if it does not. Without sudo it first fails on the KoolBox repository with a completely misleading repomd.xml GPG signature verification error: Bad GPG signature, because the signing key is cached under root-owned /var/cache/dnf and an unprivileged dnf cannot import it. Get past that and your distribution's own repositories fail too, with Curl error (58) … could not load PEM client certificate … Permission denied, because that client certificate is root-only. The two failures are sequential, not alternatives, and neither is a signature problem — both are permissions.

Then copy the files across by whatever transfer method your organization approves, and install from them locally. Debian and Ubuntu:

sudo apt-get install -y ./koolbox_*.deb ./koolbox-full_*.deb

This is silent — no warnings at all, 16 to 18 seconds, verified with the Koolify host blocked outright. What does warn is the step before it, if you refresh your package lists on a host that cannot reach the repository:

sudo apt-get update on an air-gapped host
W: Failed to fetch https://packages.koolify.ai/apt/dists/stable/InRelease  Could not connect to packages.koolify.ai:443 ...
W: Some index files failed to download. They have been ignored, or old ones used instead.

That warning is expected on an isolated server and has no effect on a local install. RHEL family — here the --disablerepo is not optional:

sudo dnf install --disablerepo=koolbox ./koolbox-*.rpm ./koolbox-full-*.rpm

If the KoolBox repository is registered — and it is, on any server installed from it — dnf tries to refresh its metadata before it looks at your local files, and aborts there. On an isolated network that refresh cannot succeed, so disabling that one repository for that one command is what makes the install work.

Name both packages.

Name koolbox and koolbox-full in every command, as the steps above do. It is what the installer itself prints, and on an isolated server it is the difference between working and not: koolbox-full declares Depends: koolbox (= <its own exact version>), so with no repository to fetch the console from, naming only the meta-package leaves a dependency nothing can satisfy and both package managers stop.

If you deliberately want the console alone, with your database elsewhere, name only koolbox — that is Method C, not an accident.

Naming only the meta-package, with no repository available
# Debian / Ubuntu, exit 100
The following packages have unmet dependencies:
 koolbox-full : Depends: koolbox (= 1.2.8-1) but 1.2.7-1 is to be installed
E: Unable to correct problems, you have held broken packages.

# RHEL family
Error:
 Problem: conflicting requests
  - nothing provides koolbox = 1.2.8-1 needed by koolbox-full-1.2.8-1.noarch from @commandline

With the repository still reachable, that same single-package command is not refused — the package manager quietly downloads the missing half over the network, which on a server you believe to be isolated is the opposite of what you want. That is the other reason to carry both files and name both.

Then license the installation with offline activation, and keep it current with offline updates, which from 1.3.0 are a single verified command.

Removing and reinstalling

sudo dnf remove koolbox koolbox-full on the RHEL family removes 138 packages and frees about 1.1 GB on 1.3.0 and later, and it leaves PostgreSQL, its data directory, the koolbox role and the koolbox database alone. Measured: no postgresql line appears anywhere in the transaction, and afterwards all four PostgreSQL packages are still installed with the service active and enabled. On Debian and Ubuntu, apt remove never proposed touching the database engine.

On 1.2.8 and earlier, removal takes PostgreSQL with it.

The same command on a pristine 1.2.8 host removed 145 packages, among them postgresql, postgresql-contrib, postgresql-private-libs and all 23 MB of postgresql-server — because dnf treated the engine as an unused dependency. The data directory survived, but the engine that reads it did not, and local RPMs could then no longer satisfy their own dependencies.

Fixed from 1.3.0: the koolbox-full scriptlet marks the engine as user-installed, so dnf stops considering it removable, and upgrading an old host is enough to earn the protection — on a pristine 1.2.8 server, moving to 1.3.0 dropped the removal from 145 packages to 138. If you are still on an older release, read the removal list before confirming it, or upgrade first.

What a removal leaves behind, on both distributions:

Scroll to see all columns →

What a package removal keeps and what it takes away.
PathAfter dnf remove / apt remove
/etc/koolbox/koolbox.envKept, untouched
/var/lib/koolbox/Kept — backups, staged packages and the recorded version
/var/lib/pgsql/dataKept (the RHEL data directory; Debian keeps its own under /var/lib/postgresql)
/opt/koolboxEmptied apart from about 28 KB of image cache — 256 MB before, 28 KB after
/usr/bin/koolboxctlRemoved
/usr/lib/systemd/system/koolbox.serviceRemoved

To remove everything, including the data, that is an explicit decision:

sudo rm -rf /opt/koolbox /etc/koolbox /var/lib/koolbox
sudo rm -rf /var/lib/pgsql        # only if you want the database gone as well

A remove followed by a reinstall preserves everything that was kept, byte for byte: the same configuration file, the same installation identifier, the same admin user, identical row counts and the existing backups. Neither koolboxctl setup nor koolboxctl migrate is needed — the koolbox-full package does both itself.

A reinstall leaves the service disabled at boot — still true on 1.3.2. After a remove and reinstall the service is running but the unit is not re-enabled: koolboxctl status reports State : active (disabled at boot) and systemctl is-enabled koolbox prints disabled, so the next reboot comes up with no console and nothing warns you in between. Run sudo systemctl enable koolbox. Fresh installs and ordinary in-place upgrades are unaffected; only a removal does this. We confirmed the state on 1.3.2 but did not reboot a server to watch it fail.

One more thing worth knowing: installing only koolbox, without koolbox-full, leaves the service stopped on purpose — Service was not running — left stopped. Start it with: systemctl start koolbox.service.

How long this takes

Scroll to see all columns →

Measured durations on 2-vCPU cloud servers running Ubuntu 24.04 and RHEL 9.6, releases 1.3.0 to 1.3.2.
OperationUbuntu 24.04RHEL 9.6
--full install on a clean server, including PostgreSQL and LibreOffice1 m 12 s49–52 s
Package-manager upgrade, one release to another15 s23 s
koolboxctl update, guided, including backup and restart22 s—
koolboxctl rollback (8 runs)20–21 s—
koolboxctl update --offline17–19 s—
Applying an update from the console~23 s online, ~25 s offline—
Downloading the packages for an offline install1 s (2 files, 79.3 MB)6 s (146 files, 341 MB)
Installing from local package files16–18 s44 s
Removing the product—7 s (138 packages)
koolboxctl update --check, online then blocked0.5 s / 0.03 s—
Backup of a small database< 1 s< 1 s
Service restart until it reports ready12–15 s~5 s
setsebool -P httpd_can_network_connect 1—2 s

Round figures: one to two minutes for a single-server --full install and well under a minute for an upgrade. Treat those as an indication rather than a guarantee: roughly 80 MB of console has to arrive first, and both of these servers pulled it at 36 to 95 MB/s from a content edge in their own region, so a slower link moves every figure in the table. Where a cell reads —, that operation was exercised on the other distribution only.

The product's own estimate for the console outage during an update — “ roughly 30 to 90 seconds” — is deliberately conservative. Every apply measured here finished in 17 to 25 seconds.

RHEL 9 on 1.2.8 and earlier — the database password mismatch

Fixed from 1.3.0 — skip this section on a current release.

Releases up to 1.2.8 left a koolbox-full install on RHEL 9 and its rebuilds with a console that could never authenticate to its own database. It is fixed, properly rather than by rewording: the installer now raises the cluster's password hashing before it creates the role, so the two halves cannot disagree. Two clean 1.3.0 installs on stock RHEL 9.6 came up healthy in 49 and 52 seconds with no manual steps.

This section is for anyone still on 1.2.8 or earlier — how to recognize it, and how to get out of it.

Two lines in the install output are new in 1.3.x, and the first one is the fix:

RHEL 9.6, release 1.3.0
✓ Cluster password_encryption set to scram-sha-256 (was md5).
✓ Enabled scram-sha-256 password authentication for 'koolbox' on koolbox (loopback only).

PostgreSQL is still 13.22 and RHEL's own postgresql.conf still defaults to md5; the override is written to postgresql.auto.conf, which is where you can see it:

Expected output
$ sudo -u postgres psql -Atc "SHOW password_encryption"
scram-sha-256

$ sudo cat /var/lib/pgsql/data/postgresql.auto.conf
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
password_encryption = 'scram-sha-256'

$ sudo grep -n koolbox /var/lib/pgsql/data/pg_hba.conf
1:# koolbox: password authentication for the control panel (managed by koolboxctl)
2:host    koolbox    koolbox    127.0.0.1/32    scram-sha-256
3:host    koolbox    koolbox    ::1/128         scram-sha-256
4:# koolbox: end of the koolboxctl-managed block

How to tell you are affected

On 1.2.8 and earlier, dnf exits 0 and both packages install, so nothing announces itself as a failure. The only sign is a warning roughly 500 lines before the end of a 560-line run, and the run still signs off cheerfully:

Release 1.2.8 on RHEL 9.6, reproduced
✓ Enabled password authentication for 'koolbox' on koolbox (loopback only).
✗ The database is still not reachable with the configured credentials.
  Verify manually:  PGPASSWORD=… psql -h 127.0.0.1 -U koolbox -d koolbox -c 'SELECT 1'
  Then finish with: sudo koolboxctl migrate
WARNING: local PostgreSQL setup did not complete.
         Then finish with: sudo koolboxctl setup && sudo koolboxctl migrate

The service never starts, so the console does not answer at all — connection refused, not an error page:

Expected output
$ sudo koolboxctl status
  Application version : 1.2.8
Service
  systemd unit        : koolbox.service
  State               : failed (enabled at boot)
Database
  Connectivity        : unreachable
Health
  No response from http://127.0.0.1:3000/api/health

$ curl -i http://127.0.0.1:3000/api/health
curl: (7) Failed to connect to 127.0.0.1 port 3000: Connection refused

The cause is that RHEL 9 ships PostgreSQL 13, whose default password_encryption is md5, while those releases wrote scram-sha-256 into pg_hba.conf. The role, the database and both extensions were created correctly; only the two ends disagreed about the hash, so authentication could never succeed. Debian and Ubuntu ship PostgreSQL 15 or 16, where scram-sha-256 is already the default, which is why this was confined to the RHEL family.

The way out: upgrade

Upgrading repairs it on its own. The 1.3.x package raises the cluster setting, re-hashes the role's password to match the file it already has, verifies the connection and starts the service — no manual SQL, nothing to type in. Reproduced twice from a genuine 1.2.8 failure, in 22 seconds:

sudo dnf upgrade koolbox koolbox-full     # this alone usually fixes it
sudo koolboxctl status                    # confirm: reachable / active

# only if the service is still down:
sudo koolboxctl setup && sudo koolboxctl migrate
Upgrading a broken 1.2.8 host to 1.3.0
Upgrading KoolBox unknown → 1.3.0
Added new setting: UPDATE_MANIFEST_URL
✓ Merged 1 new setting(s) into /etc/koolbox/koolbox.env.
Service was not running — left stopped. Start it with: systemctl start koolbox.service

  Running scriptlet: koolbox-full-1.3.0-1.noarch                            2/4
✓ Cluster password_encryption set to scram-sha-256 (was md5).
✓ Re-hashed the password of role 'koolbox' under scram-sha-256.
Database 'koolbox' already exists — left untouched.
✓ Ensured PostgreSQL extensions (unaccent, pg_trgm).
pg_hba.conf already grants scram-sha-256 password access to 'koolbox'.
✓ Verified database connectivity as 'koolbox'.
✓ Configuration complete.
Starting koolbox.service to apply schema changes...
✓ Service is ready (version 1.3.0, schema 1.3.0).
If you run setup, always follow it with migrate. On 1.3.x, koolboxctl setup does repair the mismatch in place and exits 0 — but it leaves the service failed and says nothing about it, so you are left with a working database behind a dead console. Its own output ends ✓ Configuration complete. while systemctl is-active koolbox still reports failed. sudo koolboxctl migrate is what starts it.
Expected output
$ sudo koolboxctl setup
✓ Cluster password_encryption set to scram-sha-256 (was md5).
✓ Re-hashed the password of role 'koolbox' under scram-sha-256.
Database 'koolbox' already exists — left untouched.
✓ Ensured PostgreSQL extensions (unaccent, pg_trgm).
pg_hba.conf already grants scram-sha-256 password access to 'koolbox'.
✓ Verified database connectivity as 'koolbox'.
✓ Configuration complete.

$ systemctl is-active koolbox
failed

$ sudo koolboxctl migrate
Starting koolbox.service to apply schema changes...
✓ Service is ready (version 1.3.0, schema 1.3.0).
On 1.2.8 itself, setup cannot fix it. Run against the affected release, koolboxctl setup reports PostgreSQL role 'koolbox' already exists — password left unchanged. and exits 2 — it refused by design to re-key an existing role, which is exactly what needed to change. setup --auto does the same. That is why the remedy is to upgrade rather than to re-run setup. If you genuinely cannot move off 1.2.8, the cluster setting and the role password have to be changed by hand before setup and migrate will work:
# last resort, only if you cannot upgrade off 1.2.8
sudo -u postgres psql -c "ALTER SYSTEM SET password_encryption = 'scram-sha-256';"
sudo systemctl restart postgresql
DBPASS=$(sudo sed -n 's/^DB_PASSWORD=//p' /etc/koolbox/koolbox.env | tr -d '"')
sudo -u postgres psql -c "ALTER ROLE koolbox WITH PASSWORD '$DBPASS';"
sudo koolboxctl setup && sudo koolboxctl migrate

Confirm the installation worked

sudo koolboxctl status

This prints the version, whether the service is running, the database it is using, a health summary and your most recent backups. You want to see the service active and the database reachable:

Expected output
KoolBox Private AI Control Panel
  Application version : 1.3.2
  Install directory   : /opt/koolbox
  Configuration       : /etc/koolbox/koolbox.env
  Console URL         : http://127.0.0.1:3000

Service
  systemd unit        : koolbox.service
  State               : active (enabled at boot)

Database
  Host                : localhost:5432
  Database / user     : koolbox / koolbox
  Connectivity        : reachable

Health
  Endpoint            : http://127.0.0.1:3000/api/health
  Reported status     : ok
  Schema version      : 1.3.2
  Uptime (s)          : 15

Backups
  koolbox-1.3.0-20261017-102604.sql.gz  (20K)

Logs: journalctl -u koolbox.service -f

State tells you two separate things: whether the service is running now, and whether it will come back after a reboot. If it says active (disabled at boot), run sudo systemctl enable koolbox — see Removing and reinstalling.

Two more quick checks:

sudo koolboxctl version                        # version numbers only
curl http://127.0.0.1:3000/api/health          # a small JSON health report
Expected output
koolbox 1.3.2
bundled node v22.23.3
database schema 1.3.2

version is the one command that needs no sudo. The health report looks like this, and the "status":"ok" is the part that matters:

Expected output
{"status":"ok","version":"1.3.2","schemaVersion":"1.3.2","database":"connected","uptimeSeconds":15}

It needs no authentication, which is deliberate so that monitoring can use it; the rest of the API does require a session.

On a server that has never had a backup taken, the Backups block reads none — expected, not a fault. A package-manager install or upgrade does not take one for you; the update tooling does.

This is the check — not the install log. koolboxctl status is the one thing worth trusting. The install log legitimately contains alarming ✗ lines on a healthy server, on both distributions, because package setup steps run in an order that briefly looks wrong.

If the service will not start, or you cannot reach the console in a browser, work through Troubleshooting.

Restart once if you want PDF output

On a koolbox-full install, the package manager starts the console while it is setting up the koolbox package — before LibreOffice has been unpacked. So a brand-new server logs this even though LibreOffice is installed a few seconds later:

Expected output
[KoolBox] LibreOffice not found — document generation will output DOCX only (no PDF conversion)
[KoolBox]   Ubuntu/Debian: sudo apt install libreoffice-core libreoffice-writer

One restart is all it needs, and the log then says so:

sudo systemctl restart koolbox
Expected output
[KoolBox] LibreOffice detected: LibreOffice 24.2.7.2 420(Build:2) — PDF document generation available

Do not install LibreOffice again in response to that first message — it is already there. If PDFs still come out as DOCX after a restart, see Documents come out as DOCX.

First sign-in

Open the console

In a browser, go to:

http://YOUR-SERVER-IP:3000

Replace YOUR-SERVER-IP with your server's address. On a server on your own network, hostname -I prints it and the first address shown is usually the right one.

On a cloud server, hostname -I is the wrong address. A cloud instance only knows its private address, so hostname -I returns something like 172.31.0.42 while the address your browser needs is the public or elastic IP shown in your provider's console. Use that one, and make sure the security group allows inbound TCP 3000 to it. The installer's own banner has the same problem, because it builds its Console line from exactly that command — it was measured on both verification servers and both printed an address no browser outside the network could reach.

Sign in with the credentials that are the same on every new installation. The form labels the first field Email, not Username:

Email     admin@local.koolbox
Password  ChangeMe123!
The KoolBox sign-in page with username and password fields
The KoolBox sign-in page on a freshly installed server.
The KoolBox dashboard after signing in, showing the left sidebar and overview panels
The dashboard you land on after signing in.

Change the password immediately

This is the one step not to postpone. Until you change it, your console is reachable with a password that is published in this guide and identical on every installation.
  1. Click your name at the bottom of the left sidebar. A centered My Account dialog opens, with Appearance, Change Password, Enable 2FA and Sign Out.
  2. Choose Change Password.
  3. Enter the new password twice — it does not ask for the current one — and confirm.
Aim for 14 characters, not 8. The dialog itself accepts 8, but the security policy every new installation ships with requires at least 14 characters, with an uppercase letter, a number and a symbol, along with a 24-hour session timeout and a lockout after 5 failed attempts. It is visible and changeable under Administration → Security, so set a password that satisfies the policy rather than one the dialog merely tolerates.
The user menu open at the bottom of the KoolBox sidebar, listing Change Password and Enable 2FA
The user menu lives at the bottom of the left sidebar.
The Change Password dialog with new password and confirmation fields
The Change Password dialog.

Turn on two-factor authentication

Strongly recommended for every administrator.

  1. Click your name at the bottom of the sidebar again to reopen My Account.
  2. Choose Enable 2FA. This leaves the console for a dedicated full page at /auth/2fa/setup, laid out like the sign-in screen — that is expected, not a lost session.
  3. Scan the QR code with Google Authenticator, 1Password, Authy, or any other authenticator app. If you cannot scan it, use the Manual setup key shown beside it — but use its copy button rather than typing what you see, as the key is visually truncated in its box.
  4. Click I have scanned the QR code, type the 6-digit code from your app, and click Verify and activate.
  5. Save the eight recovery codes it then shows you, somewhere safe. They come in XXXX-XXXX-XXXX form with Download .txt and Copy all buttons, and are shown only once. Each one can be used a single time to get back in if you lose your authenticator. Then click Continue to dashboard.
Enrolment is one-way in the console. Once 2FA is on, the My Account dialog offers only Register New Authenticator — there is no control in the console that turns 2FA off again. Keep the recovery codes, and treat a lost authenticator with no codes as a support matter rather than something you can clear yourself. At sign-in, the challenge page offers Authenticator, Security Key or Recovery Code.
Two-factor authentication setup showing a QR code and a manual setup key
2FA setup: scan the QR code, or use the manual setup key beside it.
The list of one-time 2FA recovery codes shown after activating two-factor authentication
Recovery codes are shown once. Store them where you can reach them without the console.

The welcome walkthrough

Until someone completes or dismisses it, each new administrator account gets a seven-step introduction on sign-in: Welcome, Choose Your Operating Mode, AI Agents & Knowledge Bases, Workflows & Automation, Security & Compliance, Guides & Documentation, and Activate Your License. You can step through it or close it.

The first step of the KoolBox welcome walkthrough
The first step of the welcome walkthrough.

The only step that changes how the product behaves is Choose Your Operating Mode:

Scroll to see all columns →

The two KoolBox operating modes and what each one means.
ModeWhat it means
AssistedKai, the AI configuration assistant, is available to help you build knowledge bases, workflows and agents. Your data and all AI inference still stay on your own infrastructure — Kai only helps you configure the system.
Air-gappedFull manual control, no assistant in the interface. Choose this if your network is isolated or policy requires it. The product makes no outbound calls in this mode.
The Choose Your Operating Mode step, offering Assisted and Air-gapped
Choose Your Operating Mode — the one walkthrough step that changes behavior.

You can change this at any time afterwards under Administration → Settings → Deployment, without reinstalling anything.

Put the console on a domain with HTTPS

Optional, and recommended for anything beyond a private network. The console speaks plain HTTP on port 3000 and does not handle certificates itself, so you put a reverse proxy in front of it.

  1. Make the console listen only on the server itself:

    sudo sed -i 's/^HOSTNAME=.*/HOSTNAME=127.0.0.1/' /etc/koolbox/koolbox.env
    sudo systemctl restart koolbox
  2. Install nginx and create the site. The configuration is identical on every distribution; only the file it goes in differs. Debian and Ubuntu:

    sudo apt install -y nginx
    
    sudo tee /etc/nginx/sites-available/koolbox >/dev/null <<'EOF'
    server {
        listen 80;
        server_name console.example.com;
    
        client_max_body_size 100M;   # document uploads can be large
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_http_version 1.1;
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Upgrade           $http_upgrade;
            proxy_set_header Connection        "upgrade";
    
            proxy_buffering off;         # without this, AI replies arrive all at once
            proxy_read_timeout 600s;     # long AI tasks would otherwise time out
        }
    }
    EOF
    
    sudo ln -s /etc/nginx/sites-available/koolbox /etc/nginx/sites-enabled/
    sudo rm /etc/nginx/sites-enabled/default    # see the note below
    sudo nginx -t && sudo systemctl reload nginx
    Remove the stock default site on Debian and Ubuntu. Both distributions ship an enabled default site that claims every request not matching another server_name, so until you remove it any test by IP address — or any request whose Host header does not yet say console.example.com — gets nginx's welcome page instead of the console. That looks like a broken proxy and is not one.

    RHEL, Rocky Linux, AlmaLinux and Fedora — same block, written to conf.d, and there is no symlink step:

    sudo dnf install -y nginx
    
    sudo tee /etc/nginx/conf.d/koolbox.conf >/dev/null <<'EOF'
    server {
        listen 80;
        server_name console.example.com;
    
        client_max_body_size 100M;   # document uploads can be large
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_http_version 1.1;
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Upgrade           $http_upgrade;
            proxy_set_header Connection        "upgrade";
    
            proxy_buffering off;         # without this, AI replies arrive all at once
            proxy_read_timeout 600s;     # long AI tasks would otherwise time out
        }
    }
    EOF
    
    sudo nginx -t && sudo systemctl enable --now nginx
    sites-available does not exist on RHEL.

    Neither /etc/nginx/sites-available nor /etc/nginx/sites-enabled is present, so the Debian commands fail outright rather than quietly doing nothing: tee exits 1 and the symlink step fails with ln: failed to create symbolic link '/etc/nginx/sites-enabled/': No such file or directory. RHEL's nginx.conf includes /etc/nginx/conf.d/*.conf instead, which is why the file goes there and nothing needs enabling.

    One side effect, and it is the exact mirror image of Debian's: conf.d is included before RHEL's stock server block, so yours becomes the default server on port 80 and requests arriving with no matching Host header also reach the console. On Debian the stock site shadows yours; on RHEL yours shadows the stock one. Add your own catch-all server block first if that matters to you.

    The two settings with comments are easy to leave out and cause confusing symptoms later: without proxy_buffering off the assistant's replies appear in one lump instead of progressively, and without the longer proxy_read_timeout a long task ends in a gateway error.

  3. On RHEL, Rocky Linux, AlmaLinux and Fedora, let nginx talk to port 3000. SELinux is enforcing on a stock install and httpd_can_network_connect is off, so nginx is not permitted to open a connection to the console:

    getsebool httpd_can_network_connect          # off, on a stock RHEL install
    sudo setsebool -P httpd_can_network_connect 1
    A 502 here is SELinux, not KoolBox.

    With a correct configuration and the console healthy on 127.0.0.1:3000, the proxy still answers 502 Bad Gateway until that boolean is set. The giveaway is (13: Permission denied) in nginx's error log — a refused connection would say connection refused instead:

    /var/log/nginx/error.log
    [crit] 25357#25357: *1 connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream, client: 127.0.0.1, server: console.example.com, request: "GET /api/health HTTP/1.1", upstream: "http://127.0.0.1:3000/api/health", host: "console.example.com"

    sudo ausearch -m avc -ts recent </dev/null confirms it from the other side — redirect its input, or with no matching records it sits waiting on your terminal and looks hung:

    Expected output
    type=AVC msg=audit(1791952386.820:3211): avc:  denied  { name_connect } for  pid=25357 comm="nginx" dest=3000 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:ntop_port_t:s0 tclass=tcp_socket permissive=0

    Setting the boolean takes about three seconds and the proxy returns HTTP 200 with the real health JSON immediately afterwards. No restart is needed.

  4. Check the proxy before DNS points anywhere. Ask for the console by name, from the server itself:

    curl -I -H 'Host: console.example.com' http://127.0.0.1/

    A working proxy sends you to the sign-in page:

    Expected output
    HTTP/1.1 307 Temporary Redirect
    location: /auth/login
    X-Powered-By: Next.js

    Without the Host header — or with the Debian default site still enabled — you get the distribution's welcome page instead, which says nothing about KoolBox:

    Expected output
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Content-Type: text/html
    Content-Length: 615
  5. Get a certificate:

    sudo apt install -y certbot python3-certbot-nginx      # Debian / Ubuntu
    sudo dnf install -y certbot python3-certbot-nginx      # RHEL family
    
    sudo certbot --nginx -d console.example.com

    Certbot edits the same file, adds the HTTPS redirect, and sets up automatic renewal. This is the one step on this page that was not exercised during verification, because it needs a publicly resolvable DNS name.

  6. Open ports 80 and 443 — in your cloud provider's security group, and in the host firewall if there is one. Port 80 must stay reachable for certificate renewal even though no real traffic is served on it.

    # Debian / Ubuntu
    sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
    
    # RHEL family
    sudo firewall-cmd --permanent --add-port=80/tcp --add-port=443/tcp
    sudo firewall-cmd --reload

    For direct access on port 3000 instead, substitute 3000/tcp. On a stock cloud image there may be no host firewall at all: on the RHEL 9.6 image used for verification firewalld was not installed and iptables had an empty, all-ACCEPT ruleset, so both ports were reachable with no firewall work whatsoever and only the cloud security group mattered. Check with rpm -q firewalld or sudo ufw status before assuming a command failed.

Downloads reference

Where everything lives

Everything is served from https://packages.koolify.ai. No account, password or token is needed to download — your entitlement is enforced by the license inside the product, not by the download. Every URL listed on this page was fetched during verification and all of them returned 200.

Scroll to see all columns →

Published KoolBox installer and signing-key files.
WhatLink
Installer scripthttps://packages.koolify.ai/install.sh
Signing key (ASCII)https://packages.koolify.ai/koolbox.asc
Signing key (binary)https://packages.koolify.ai/koolbox.gpg
Ready-made DNF repository filehttps://packages.koolify.ai/koolbox.repo
Signed release manifesthttps://packages.koolify.ai/updates/latest.json
Manifest signaturehttps://packages.koolify.ai/updates/latest.json.sig

The manifest is the authoritative statement of what the current release is. It carries the version, the release date, the oldest version that can upgrade directly to it, and every artifact with its size and SHA-256 — and its signature is what the offline update path checks before it installs anything.

Getting the latest version

The recommended way is always through your package manager, because it verifies signatures for you:

sudo apt update && sudo apt install --only-upgrade koolbox koolbox-full
sudo dnf upgrade koolbox koolbox-full

To see what the newest version is before installing it, use the commands under Check whether a newer version exists.

Downloading a specific file directly

The layout is predictable, so you can fetch a single file over HTTPS if you need to. For a version X.Y.Z:

Scroll to see all columns →

Direct download paths for each KoolBox package file.
FilePath
Console, Debian/Ubuntu/apt/pool/main/koolbox_X.Y.Z-1_amd64.deb
Meta-package, Debian/Ubuntu/apt/pool/main/koolbox-full_X.Y.Z-1_all.deb
Console, RHEL family/rpm/x86_64/koolbox-X.Y.Z-1.x86_64.rpm
Meta-package, RHEL family/rpm/x86_64/koolbox-full-X.Y.Z-1.noarch.rpm

Example:

curl -fLO https://packages.koolify.ai/apt/pool/main/koolbox_1.3.1-1_amd64.deb
Do not treat a filename as stable. Read the current version out of https://packages.koolify.ai/updates/latest.json and build the path from that. Releases land faster than documentation does — two shipped during a single afternoon of testing — and a package fetched today alongside a manifest fetched tomorrow will fail checksum verification, which is exactly what that verification is for.

Only x86_64 console packages exist. The arm64 and aarch64 paths hold the meta-package alone, which cannot be installed on its own, so there is no ARM download to find here — see ARM servers.

The repository indexes, which list every available version, are at /apt/dists/stable/main/binary-amd64/Packages and /rpm/x86_64/repodata/repomd.xml.

Verifying what you downloaded

Packages and repository indexes are signed with Koolify's key. If you installed through apt or dnf, signatures are checked on every download automatically and you need do nothing further. To check the fingerprint yourself:

curl -fsSL https://packages.koolify.ai/koolbox.asc | gpg --show-keys
Expected output
pub   rsa4096 2026-09-15 [SCEA] [expires: 2031-09-14]
      9F1BA391E54B66E96F25E3E117BB084F403BE624
uid                      Koolify Inc Package Signing <packages@koolify.ai>

The fingerprint must be exactly 9F1BA391E54B66E96F25E3E117BB084F403BE624. It is the same key the installer imports, and the same one dnf offers to import as 0x403BE624.

Want This Running on Your Infrastructure?

Talk to us about the deployment architecture that fits your environment — private cloud, on-premise, or completely air-gapped.