Skip to content

Installing on a server

This page is meant to be followed end to end, with nothing left to guess. It installs Kewos ERP on a Linux server, as a permanent service, behind your own reverse proxy.

What the server needs

Operating systemDebian 12 / 13, Ubuntu 22.04 / 24.04, or equivalent, 64-bit.
CPUFour cores to begin with.
Memory8 GB. Plan for 16 GB beyond twenty concurrent users.
Disk20 GB for the product, plus room for your attachments and your backups.
DatabasePostgreSQL 16 or 17.
QueueRedis 6 or later.
NetworkA domain name and a proxy terminating TLS.

System packages

Fenêtre de terminal
sudo apt update
sudo apt install -y postgresql redis-server ca-certificates curl

Rendering PDF documents relies on a few native libraries:

Fenêtre de terminal
sudo apt install -y libpango-1.0-0 libpangoft2-1.0-0 libcairo2 libgdk-pixbuf-2.0-0 \
libffi8 shared-mime-info fonts-dejavu-core

Without them the application starts and printing a document fails at the moment somebody needs it. Install them now.

1. The service account and the layout

The application never runs as root.

Fenêtre de terminal
sudo useradd --system --home-dir /opt/kewos --shell /usr/sbin/nologin kewos
sudo mkdir -p /opt/kewos /etc/kewos /var/lib/kewos/storage /var/log/kewos
sudo chown -R kewos:kewos /opt/kewos /var/lib/kewos /var/log/kewos
sudo chmod 750 /etc/kewos
PathContents
/opt/kewos/currentThe release in service (a symlink into /opt/kewos/releases/).
/opt/kewos/releases/Installed releases. Keep two or three so you can go back.
/etc/kewos/kewos.envThe configuration. Holds secrets: root:kewos, mode 640.
/etc/kewos/license.tokenYour licence token.
/var/lib/kewos/storageAttachments and generated documents. Back this up.
/var/log/kewosLogs.

2. The database

The role needs CREATEDB: the application creates its own database and, in advanced multi-company setups, those of the extra entities.

Fenêtre de terminal
sudo -u postgres psql <<'SQL'
CREATE ROLE kewos LOGIN PASSWORD 'a-long-random-password' CREATEDB;
CREATE DATABASE kewos OWNER kewos;
SQL

Check the connection before going further:

Fenêtre de terminal
PGPASSWORD='a-long-random-password' psql -h 127.0.0.1 -U kewos -d kewos -c 'select 1'

3. Fetch your release

Fenêtre de terminal
cd /tmp
curl -u '<your-identifier>' -O https://download.kewos.io/erp/kewos-erp-<version>.tar.gz
curl -u '<your-identifier>' -O https://download.kewos.io/erp/kewos-erp-<version>.tar.gz.sha256

Check the digest before deploying. A package that does not match its own digest is not a Kewos package.

Fenêtre de terminal
sha256sum -c kewos-erp-<version>.tar.gz.sha256

Then install it into its own release directory:

Fenêtre de terminal
sudo mkdir -p /opt/kewos/releases/<version>
sudo tar -xzf kewos-erp-<version>.tar.gz -C /opt/kewos/releases/<version> --strip-components=1
sudo chown -R kewos:kewos /opt/kewos/releases/<version>
sudo ln -sfn /opt/kewos/releases/<version> /opt/kewos/current

The symlink is not decoration: it is what makes upgrading and rolling back immediate, and it never leaves the application half-copied.

4. Configuration

Create /etc/kewos/kewos.env. It is the only file you have to write.

Fenêtre de terminal
sudo install -o root -g kewos -m 640 /dev/null /etc/kewos/kewos.env
sudo editor /etc/kewos/kewos.env

Generating the secrets

Three values must be random and yours. Generate them once:

Fenêtre de terminal
echo "KEWOS_SECRETS_MASTER_KEY=$(openssl rand -base64 32)"
echo "KEWOS_ATTACHMENT_SIGNING_SECRET=$(openssl rand -hex 32)"
echo "KEWOS_PORTAL_SIGNING_SECRET=$(openssl rand -hex 32)"

The file, in full

Fenêtre de terminal
# --- Required ------------------------------------------------------------
KEWOS_ENV=production
KEWOS_MASTER_DATABASE_URL=postgresql+psycopg://kewos:PASSWORD@127.0.0.1:5432/kewos
KEWOS_REDIS_URL=redis://127.0.0.1:6379/0
KEWOS_SECRETS_MASTER_KEY=...
KEWOS_LICENSE_PUBLIC_KEY=... # issued by Kewos with your licence
KEWOS_PUBLIC_BASE_URL=https://erp.your-domain.com
# --- Paths ---------------------------------------------------------------
KEWOS_MODULES_ROOT=/opt/kewos/current/modules
KEWOS_STORAGE_ROOT=/var/lib/kewos/storage
KEWOS_LOG_DIR=/var/log/kewos
# --- Network -------------------------------------------------------------
KEWOS_HOST=127.0.0.1
KEWOS_PORT=8080
# --- Link signing --------------------------------------------------------
KEWOS_ATTACHMENT_SIGNING_SECRET=...
KEWOS_PORTAL_SIGNING_SECRET=...
# --- First administrator (read on first boot only) -----------------------
KEWOS_ADMIN_LOGIN=admin
KEWOS_ADMIN_EMAIL=admin@your-domain.com
KEWOS_ADMIN_PASSWORD=...
# --- Recommended ---------------------------------------------------------
KEWOS_LOG_LEVEL=INFO
KEWOS_MAIL_FROM=no-reply@your-domain.com
KEWOS_RATE_LIMIT_PER_MINUTE=100
KEWOS_HTTP_TIMEOUT_SECONDS=20
KEWOS_DB_TIMEOUT_SECONDS=30
KEWOS_JOB_TIMEOUT_SECONDS=900

What each key does

KeyRole
KEWOS_ENVproduction on a server. Any other value enables development conveniences, including hot schema application.
KEWOS_MASTER_DATABASE_URLThe database address. The postgresql+psycopg:// prefix is required.
KEWOS_REDIS_URLThe job queue and the cache. Without it no background work runs.
KEWOS_SECRETS_MASTER_KEYEncrypts stored secrets. Irreplaceable.
KEWOS_LICENSE_PUBLIC_KEYThe key your licence token is verified against. Without it the application refuses to start in production.
KEWOS_PUBLIC_BASE_URLThe public address. Used to build absolute links in emails and PDFs; without it images do not appear in sent documents.
KEWOS_MODULES_ROOTWhere the modules live. Point at the current symlink, not a fixed release.
KEWOS_STORAGE_ROOTWhere attachments live. Back this up with the database.
KEWOS_LOG_DIREnables the rotating file log. Without it everything goes to standard output.
KEWOS_HOST / KEWOS_PORTWhat it listens on. Stay on 127.0.0.1: the proxy does the exposing.
KEWOS_ATTACHMENT_SIGNING_SECRETSigns attachment links. Changing it invalidates links already sent.
KEWOS_PORTAL_SIGNING_SECRETSigns external portal access. Same caveat.
KEWOS_ADMIN_PASSWORDThe first administrator’s password, applied on first boot.
KEWOS_MAIL_FROMDefault sender for outbound mail.
KEWOS_RATE_LIMIT_PER_MINUTERequest budget per actor per minute.
KEWOS_LOG_LEVELINFO in service, DEBUG only while diagnosing.

5. First boot

This registers the installation, creates the schema and the administrator. It is idempotent: running it again does no harm.

Fenêtre de terminal
cd /opt/kewos/current
sudo -u kewos env $(grep -v '^#' /etc/kewos/kewos.env | xargs) \
./bin/kewos-server provision

Then align the schema. A fresh installation reports drift until this is done, because the tables are created in one go without the migrations being stamped. The command is additive: it creates what is missing and never destroys.

Fenêtre de terminal
sudo -u kewos env $(grep -v '^#' /etc/kewos/kewos.env | xargs) \
./bin/kewos reconcile --confirm --modules-root /opt/kewos/current/modules
main [active]: reconciled (37 migration(s), 0 column(s))
done: 1 tenant(s), 0 failed

6. Apply your licence

The application serves nothing until this is done:

refusing to start: tenant(s) without a valid license: main=missing
Fenêtre de terminal
sudo install -o root -g kewos -m 640 your-licence.token /etc/kewos/license.token
cd /opt/kewos/current
sudo -u kewos env $(grep -v '^#' /etc/kewos/kewos.env | xargs) \
./bin/kewos license install \
--tenant main \
--database-url "$KEWOS_MASTER_DATABASE_URL" \
--token-file /etc/kewos/license.token

Check it:

status: valid
tenant: main
plan: on-prem
users: 1 / 25
companies: 1 / 3
expires: 2027-09-03T11:28:38+00:00

If your licence is machine-bound

Fenêtre de terminal
sudo -u kewos ./bin/kewos license fingerprint

Send the value to Kewos, which issues a token bound to this server.

7. The services

Two services: the application, and the worker that drains the queue. Both are required. Without the worker, emails, imports and documents wait with no error message.

/etc/systemd/system/kewos.service:

[Unit]
Description=Kewos ERP
After=network-online.target postgresql.service redis-server.service
Wants=postgresql.service redis-server.service
[Service]
Type=simple
User=kewos
Group=kewos
WorkingDirectory=/opt/kewos/current
EnvironmentFile=/etc/kewos/kewos.env
ExecStart=/opt/kewos/current/bin/kewos-server
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/kewos /var/log/kewos
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target

/etc/systemd/system/kewos-worker.service is the same unit with ExecStart=/opt/kewos/current/bin/kewos worker --threads 8 and no LimitNOFILE.

Fenêtre de terminal
sudo systemctl daemon-reload
sudo systemctl enable --now kewos kewos-worker
sudo systemctl status kewos kewos-worker --no-pager

Startup takes about ten seconds. A check run too early fails while everything is fine.

8. The proxy

Caddy

erp.your-domain.com {
encode zstd gzip
request_body {
max_size 60MB
}
reverse_proxy 127.0.0.1:8080
}

nginx

server {
listen 443 ssl http2;
server_name erp.your-domain.com;
ssl_certificate /etc/letsencrypt/live/erp.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/erp.your-domain.com/privkey.pem;
# Attachments and imports can be large: too low a value here returns 413
# before the application ever sees the request.
client_max_body_size 60m;
location / {
proxy_pass http://127.0.0.1:8080;
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_read_timeout 120s;
}
}

9. Check it

Fenêtre de terminal
curl -fsS https://erp.your-domain.com/health/live
# {"status":"alive"}
curl -fsS https://erp.your-domain.com/health/schema
# {"status":"ok","tenants_checked":1,"drifted":{}}
curl -o /dev/null -w '%{http_code}\n' https://erp.your-domain.com/api/v1/about
# 401

A 401 on the API is a good sign: it is closed.

What you seeWhat it meansWhat to do
The login screenAll good.Go live.
The service does not startMissing licence, missing public key, or unreachable database.journalctl -u kewos -n 50 --no-pager
A blank page, API respondingThe package is incomplete.Download again and check the digest.
/health/schema says driftedThe schema lags the code.Run reconcile --confirm, then restart.
Emails do not go outThe worker is not running.sudo systemctl status kewos-worker

10. Upgrading, and going back

An upgrade means installing a release beside the current one and moving the symlink.

Fenêtre de terminal
# 1. Back up FIRST.
sudo -u postgres pg_dump -Fc kewos > /var/backups/kewos-$(date +%F).dump
# 2. Install the new release next to the old one.
sudo mkdir -p /opt/kewos/releases/<new>
sudo tar -xzf kewos-erp-<new>.tar.gz -C /opt/kewos/releases/<new> --strip-components=1
sudo chown -R kewos:kewos /opt/kewos/releases/<new>
# 3. Switch, align the schema, restart.
sudo ln -sfn /opt/kewos/releases/<new> /opt/kewos/current
sudo systemctl stop kewos kewos-worker
cd /opt/kewos/current && sudo -u kewos env $(grep -v '^#' /etc/kewos/kewos.env | xargs) \
./bin/kewos reconcile --confirm --modules-root /opt/kewos/current/modules
sudo systemctl start kewos kewos-worker

Rolling back is the same move in reverse:

Fenêtre de terminal
sudo ln -sfn /opt/kewos/releases/<old> /opt/kewos/current
sudo systemctl restart kewos kewos-worker

Next

Go live: the settings to get right before you open the door.