Server requirements
What a server must have before you run the Bicrypto installer — Ubuntu 24.04 LTS or newer, the exact Node.js range, MySQL, Redis, optional ScyllaDB, open ports and root access.
Bicrypto is not a PHP application you unzip into a web root. A running install is
three long-lived Node.js processes supervised by PM2 (backend, frontend,
cron), talking to MySQL and Redis, behind a reverse proxy that terminates TLS.
Every requirement below follows from that shape.
The installer (bash installer.sh) checks only three things — RAM, free disk and
internet — and warns rather than stops on all three. Everything else on this page
is an unstated prerequisite that fails later, during the build or at first boot.
The short version
- A Linux VPS or dedicated server you control, with SSH and root
- Ubuntu 24.04 LTS or newer — or another distro shipping glibc 2.38+ (Debian 13+, RHEL/Rocky/Alma 10+, Fedora 39+). Ubuntu 22.04 will not run.
- x86-64 or ARM64, glibc — not Alpine/musl
- Node.js 22, 24 or 26 — no other major version will boot
- pnpm (the repo pins
pnpm@10.28.0); npm and yarn will not resolve the workspace - 4 GB RAM minimum, 8 GB if you want the frontend build to finish reliably
- 10 GB free disk on the partition holding the app directory
- 2+ CPU cores
- A reachable MySQL or MariaDB server, plus the
mysqlclient on the app box - Redis, reachable and answering
PING - ScyllaDB — only if you intend to run the Ecosystem or Futures addons
- nginx or Apache in front, with a real TLS certificate
-
initial.sqlpresent in the directory you run the installer from - Outbound HTTPS to
updates.mashdiv.comfor licensing
Node.js — the version is not negotiable
Supported majors are 22, 24 and 26. Nothing else works, and nothing at install time tells you. The build succeeds, PM2 reports the apps as started, and the frontend then answers every request with a connection error to the API.
Both package.json files declare the range:
"engines": {
"node": "22 || 24 || 26"
}The constraint comes from one dependency. uWebSockets.js — the HTTP and
WebSocket server the backend runs on — has no build step. It loads a prebuilt
binary named for your Node ABI:
require('./uws_' + platform + '_' + arch + '_' + process.versions.modules + '.node')The pinned version (v20.69.0) ships ABI 127, 137 and 147 only, which is exactly
Node 22, 24 and 26. On Node 20 (ABI 115) there is no file to load and the
process dies with Cannot find module './uws_linux_x64_115.node' four frames
deep inside the request handler — a message that reads like a corrupt install,
not a wrong runtime.
Those same binaries carry a second, independent constraint that Node cannot satisfy: they are linked against glibc 2.38. That is what sets the operating system floor at Ubuntu 24.04 — see Operating system below. Installing the right Node major on a 22.04 box does not help.
To make that survivable, backend/preflight.ts runs before anything else, prints
the running and required versions, and exits with code 78 (EX_CONFIG).
The backend and cron apps set stop_exit_codes: [78], so PM2 stops the app with the
message still on screen instead of crash-looping sixteen times and scrolling it
away.
The installer targets Node 26 specifically — it adds the NodeSource setup_26.x
repository, falls back to nvm, and verifies the resulting major version
rather than merely that node exists.
Two traps when you change Node by hand:
- PM2 keeps the Node it was started with.
node -vcan read 26 while the daemon still runs 20. Re-point it withpm2 kill && npm install -g pm2. - Native modules are compiled against an ABI. After a major change, run
pnpm rebuild -rbefore starting.
Preflight also resolves dotenv, module-alias, ioredis, sequelize,
mysql2, bullmq and uWebSockets.js up front and exits 78 listing all the
missing ones at once, rather than surfacing them one restart at a time.
Operating system
Linux only, and Ubuntu 24.04 LTS is the oldest release that runs the platform. Build on 24.04 unless you have a reason not to — it is what we install on, what the managed-hosting images use, and what the installer is tested against.
The pinned uWebSockets.js prebuilt binaries are linked against glibc 2.38.
Ubuntu 22.04 ships glibc 2.35, so the binary cannot be loaded at all and the
backend dies at require time:
Error: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found
(required by .../node_modules/uWebSockets.js/uws_linux_x64_147.node)There is no way around it on that box. uWebSockets.js has no build step to
fall back to, glibc is not something you upgrade under a running distro, and the
requirement is in every Linux binary in the package — x86-64 and ARM64, all
three Node ABIs. The fix is a newer OS.
Nothing warns you first. backend/preflight.ts checks the Node major and that
uWebSockets.js resolves — it never loads the binary — so an Ubuntu 22.04 box
passes every check the platform makes, installs cleanly, builds the frontend,
and then crash-loops on the first start.
Check the box before you install anything:
ldd --version | head -1 # glibc must be 2.38 or higher
cat /etc/os-release # Ubuntu 24.04 or newer| Platform | glibc | Works |
|---|---|---|
| Ubuntu 24.04 LTS and newer | 2.39+ | Yes — the reference platform |
| Debian 13 (trixie) and newer | 2.41 | Yes |
| RHEL / Rocky / Alma 10 | 2.39 | Yes |
| Fedora 39 and newer | 2.38+ | Yes |
| Ubuntu 22.04 LTS and older | 2.35 and older | No — too old for the uWebSockets.js binary |
| Debian 12 (bookworm) and older | 2.36 and older | No |
| RHEL / Rocky / Alma 9, Amazon Linux 2023 | 2.34 | No |
| RHEL 8 / CentOS 7 | 2.28 / 2.17 | No |
| Linux x86-64 and ARM64 (glibc 2.38+) | — | Yes — prebuilt binaries ship for both |
| Alpine or any musl distro | — | No — no uWebSockets.js binary exists for musl |
| Windows / macOS | — | Development only. There is no installer, and pnpm start expects PM2 plus a Linux service layer. |
The installer does not enforce any of this. It reads /etc/os-release, exits if
the file is absent, then picks a package manager in this order — apt, then
dnf, then yum — and never looks at the version. On Ubuntu 22.04 it will run
to completion and report success. Checking the release is your job.
If you are already on 22.04, a do-release-upgrade to 24.04 works, but treat it
as a migration: snapshot the box, and expect to pnpm rebuild -r afterwards so
every other native module is rebuilt against the new libc.
CPU, RAM and disk
RAM. The installer warns below 4096 MB and offers to continue anyway.
Treat 4 GB as the floor for running the platform, not for building it: the
frontend build runs Node with a 7,780 MB heap ceiling
(NODE_OPTIONS=--max-old-space-size=7780). On a 4 GB box with no swap,
pnpm build:frontend can be killed by the OOM reaper part-way through, leaving
frontend/.next incomplete and the frontend serving 500s. Give the box 8 GB, or
add swap before you build.
Disk. The installer requires 10 GB free, measured with df -BG . — that
is the partition holding the app directory, not /var and not the MySQL data
directory. Below 10 GB it offers to continue. What consumes it: three
node_modules trees (root, backend, frontend), the local pnpm store the
installer creates at ./.pnpm-store, the Next.js build output, the MySQL data
directory, and frontend/public/uploads which grows with every KYC document and
dispute attachment. SSD, not spinning disk — order matching is latency-sensitive.
CPU. Nothing checks core count, so nothing stops you installing on a single
core. Two is the practical floor: you are running three Node processes plus
MySQL plus Redis on the same box. If you later switch to the threaded backend
entry point, it spawns min(NEXT_PUBLIC_BACKEND_THREADS, os.cpus().length)
workers, so cores directly cap concurrency.
CPU age. On x86-64 without the AVX2 flag, the prebuilt sharp image binaries
do not apply. The installer detects this from /proc/cpuinfo and works down a
chain — build sharp from source, then sharp@0.32.6, then canvas, then
disable Next.js image optimisation entirely. It usually recovers, but the build
takes far longer and the last rung costs you optimised images. Any CPU from the
last decade has AVX2; very old or heavily restricted VPS profiles may not.
Package manager
pnpm. The repo is a pnpm workspace (frontend and backend are packages) and
pins the version:
"packageManager": "pnpm@10.28.0"npm and yarn will not resolve the workspace links. Note the installer runs
npm install -g pnpm@latest, so the version it lands may be newer than the pin —
if a dependency resolves oddly, install the pinned version explicitly.
The installer runs pnpm install --store-dir ./.pnpm-store --frozen-lockfile,
falling back to a plain install and then --shamefully-hoist. It builds the
frontend and runs the seeders; it does not build the backend, because
backend/dist ships pre-built and is never deleted.
MySQL or MariaDB
No mysql-server or mariadb-server package appears anywhere in the installer.
It only prompts for credentials and then uses them. Worse, the connection check
is written as local x=$(mysql …) followed by if [[ $? -eq 0 ]], and $?
there is the exit status of local, which is always 0. The check reports success
no matter what, and the entire error-handling menu behind it is unreachable.
Prove the credentials yourself first, from the app server:
mysql -h DB_HOST -P 3306 -u DB_USER -p -e "SELECT 1"What must be true before you start:
- A MySQL or MariaDB server, reachable from the app box on
DB_PORT(default3306). Local or remote both work. - The
mysqlclient binary on the app server, even for a remote database — the installer shells out to it to create the schema and importinitial.sql. - A user that can
CREATE DATABASE, and create, drop and alter tables in it. The installer creates the database asutf8mb4/utf8mb4_unicode_ciif it does not exist. - Elevated privileges are helpful but optional. Before importing, the installer
raises
max_allowed_packet,innodb_log_file_sizeand clearssql_modeviaSET GLOBAL, which needsSUPER/SYSTEM_VARIABLES_ADMIN. Those calls are swallowed on failure, so a restricted user simply skips them — and then a large statement ininitial.sqlcan fail against a lowmax_allowed_packet. - At least 1 GB free in the MySQL data directory; the installer checks and stops below that.
initial.sql must be in the directory you run the installer from. If it is
missing the installer exits immediately. If the database already contains tables,
you are asked whether to drop them all and reimport — answering yes destroys the
existing data.
Credentials go into .env, which the installer creates from .env.example if it
is absent:
DB_NAME="v4"
DB_USER="root"
DB_PASSWORD=""
DB_HOST="localhost"
DB_PORT="3306"Redis
Required. Not optional, not a cache you can skip.
If Redis is unreachable the backend prints the endpoint it tried, which environment variables chose it, and how to install Redis — then exits 78. PM2 stops the app rather than restarting into the same permanent fault. There is no in-memory fallback; an earlier release had one and it was removed, because a per-process store cannot coordinate anything: two processes each believed they held the same lock, and a settings change never reached the others.
Redis holds sessions, CSRF tokens, rate-limit counters, distributed locks, the BullMQ cron queues, and the settings invalidation channel. Losing it mid-flight takes the platform down.
REDIS_HOST="127.0.0.1"
REDIS_PORT="6379"
REDIS_PASSWORD=""Verify before installing:
redis-cli -h 127.0.0.1 -p 6379 ping # expects: PONGThe installer does install and start Redis on a supported distro, verifies it
with redis-cli ping, and — after three failed attempts — offers to continue
without it. Do not take that option. The install will finish and the backend will
refuse to boot.
ScyllaDB — Ecosystem and Futures only
Skip this entirely unless you are running the Ecosystem (native trading) or Futures addons. Everything else works without it.
Where it is used, it is the order book and market data store: orders, candles,
order book levels, trades, open orders by market and stop orders live in the
SCYLLA_KEYSPACE keyspace, with a second keyspace for futures.
Two things to plan for:
- The installer never touches ScyllaDB. There is not a single mention of it
in
installer.sh. Install it, create nothing (the client creates its own keyspaces and tables on first connection), and make port9042reachable. - None of its settings are in
.env.example. You add them by hand.
| Variable | Default |
|---|---|
SCYLLA_CONNECT_POINTS |
127.0.0.1:9042 |
SCYLLA_DATACENTER |
datacenter1 |
SCYLLA_KEYSPACE |
trading |
SCYLLA_FUTURES_KEYSPACE |
futures |
SCYLLA_USERNAME / SCYLLA_PASSWORD |
unset |
SCYLLA_ENABLED |
true |
If Scylla is disabled or unreachable, ecosystem trading endpoints return
503 rather than failing quietly — the rest of the platform stays up. Setting
SCYLLA_ENABLED="false" makes that explicit and stops the connection retries.
Note also that the built-in database backup covers MySQL only. ScyllaDB has no backup path in the product; if you run it, you own its backups.
Ports
Only three ports should be reachable from the internet. Everything else is loopback or private-network traffic.
| Port | Process | Exposure |
|---|---|---|
| 22 | SSH | Public (restrict by source if you can) |
| 80 | Reverse proxy — ACME challenges and the HTTPS redirect | Public |
| 443 | Reverse proxy — all real traffic, HTTP and WebSocket | Public |
| 3000 | Next.js frontend | Loopback only |
| 4000 | Backend API and WebSockets | Loopback only |
| 4001 | Cron worker | Loopback only — nothing may connect to it |
| 3306 | MySQL | Loopback or private network |
| 6379 | Redis | Loopback or private network |
| 9042 | ScyllaDB (if used) | Loopback or private network |
Two details that catch people out.
The installer opens port 3000 in the firewall. Its ufw and firewalld
rules allow ssh, http, https and 3000. That is there so you can see the
site before a proxy exists. Close it once nginx or Apache is in front, or the
frontend is served on plain HTTP alongside your HTTPS site — and nobody will be
able to log in on that URL anyway (see below).
Port 4000 is never opened, and must stay that way. In production the backend
calls listen() with no host argument, so it binds every interface. Nothing in
the installer firewalls it. If your provider has no default-deny firewall, the
API is publicly reachable on 4000 the moment it starts. The cron worker on 4001
is worse — it exists purely to avoid a port clash and is not meant to serve
anyone.
Outbound, the box needs HTTPS to updates.mashdiv.com for license validation.
The heartbeat runs hourly and there is a 72-hour grace period when the host is
unreachable, so a blocked egress rule takes three days to become visible.
TLS is mandatory, not a hardening step
When NODE_ENV=production, the backend sets accessToken and sessionId with
Secure and SameSite=None. Browsers discard Secure cookies over HTTP, and
SameSite=None requires Secure. Login will appear to succeed and the next
request will be anonymous.
There is no ACME or certbot automation anywhere in the repo — the installer only prints a reminder. Have a certificate ready, or plan to issue one immediately after install.
The backend itself speaks plain HTTP; TLS terminates at the proxy. Note that in
production Next.js does not proxy /api to the backend — those rewrites are
development-only — so without a location /api block in your proxy config the
entire API is unreachable.
Root and SSH access
The installer refuses to run as anything but root, and exits immediately:
sudo bash installer.shThat is not defensiveness. It installs system packages, adds the NodeSource
repository, enables and starts services with systemctl, writes to
/var/log/bicrypto-installer.log, configures ufw or firewalld, creates a
dedicated bicrypto system user when the app directory is owned by root,
installs /usr/local/bin/bicrypto-start, and registers PM2 with pm2 startup.
You also need ongoing shell access. Starting, stopping and updating the platform
are command-line operations (pnpm start, pnpm stop, pnpm updator) — there
is no control panel button for them.
Why shared hosting will not work
Not "will be slow" — will not run at all. Each of these on its own is fatal:
- No root. The installer exits before it does anything.
- No long-running processes. cPanel-style hosting kills background processes. Bicrypto needs three of them alive continuously, plus PM2 supervising them.
- No PM2 and no systemd.
pnpm start,pnpm stopand the whole update chain are PM2 operations. - No native modules.
uWebSockets.js,sharp,argon2,bcryptand the crypto libraries all load compiled binaries. - No Redis. Shared hosts do not offer it, and the backend will not boot without it.
- You cannot choose the Node major. Shared "Node.js app" panels give you a fixed list, and if 22, 24 or 26 is not on it there is nothing to do.
- No control over ports or the reverse proxy. You need to bind 3000 and 4000
locally and put your own
location /apiblock in front of them.
A small VPS with root beats any shared plan here. If you want the platform installed for you, managed hosting is the alternative to running the installer yourself.
Before you run the installer
Run these on the target box. Every one of them is something the installer will not reliably tell you about.
# OS must be Ubuntu 24.04+ (or another distro with glibc 2.38+)
cat /etc/os-release | head -2
ldd --version | head -1
# Node major must be 22, 24 or 26
node -v
# pnpm present
pnpm -v
# Database reachable with the credentials you are about to type in
mysql -h 127.0.0.1 -P 3306 -u YOUR_DB_USER -p -e "SELECT VERSION()"
# Redis answering
redis-cli -h 127.0.0.1 -p 6379 ping
# Free space on the partition holding the app directory (need 10 GB)
df -h .
# RAM (need 4 GB; 8 GB to build comfortably)
free -m
# The schema file the installer requires in this directory
ls -l initial.sql