Installing Bicrypto
How to install Bicrypto on a Linux server — the guided installer, the manual path, licence activation, and how to prove the install actually works.
An install is not one program. It is a Node application supervised by PM2, a MySQL database, a Redis server, and a reverse proxy terminating TLS in front of both. You need a Linux box with root SSH access. Shared hosting cannot run this — the platform needs to bind ports, install system packages and keep long-lived processes alive.
Budget 20–40 minutes of wall time. The installer prints "10-15 minutes"; the frontend build is the long pole and on a small VPS it alone can take longer than that.
What ends up running
pnpm start hands three applications to PM2, defined in production.config.js:
| App | What it is | Port |
|---|---|---|
backend |
The REST API and every WebSocket endpoint (backend/dist/index.js) |
NEXT_PUBLIC_BACKEND_PORT, default 4000 |
frontend |
The Next.js site (next start) |
3000, hardcoded |
cron |
The scheduler — same code, CRON_MODE=only |
4001, serves no traffic |
The frontend port is set inside the PM2 env block, so NEXT_PUBLIC_FRONTEND_PORT
in .env does not move it. Port 4001 exists purely so the scheduler does not
collide with the API on 4000; never point a load balancer or proxy at it.
Alongside those you need MySQL (or MariaDB) and Redis. Redis is not a cache you can skip: sessions, rate limits, distributed locks, the BullMQ scheduler and cross-process settings invalidation all live in it, and the backend exits with code 78 at boot if it cannot reach it.
Before you start
- Ubuntu 24.04 LTS or newer, with root SSH. 24.04 is the floor: the backend's
HTTP server is a prebuilt binary linked against glibc 2.38, which Ubuntu 22.04
does not have. The installer reads
/etc/os-releaseand picksapt,dnforyumin that order, but it never checks the release — on 22.04 it installs happily and the backend then crash-loops. See Server requirements. - Node.js 22, 24 or 26 — nothing else. The guided installer installs Node 26.
- A reachable MySQL or MariaDB server with a user that can create tables.
- 4 GB RAM and 10 GB free disk. Below either, the installer warns and asks whether to continue.
- A domain pointing at the box, and a TLS certificate. See HTTPS is not optional.
- Your Envato purchase code. Without activation the platform serves 403 on nearly every API route.
Nothing in installer.sh installs mysql-server or mariadb-server. It only
prompts for connection details and connects. Install and start your database
first, or the install fails at the schema import with the platform half
configured.
Getting the files
Download the release archive from your Envato downloads for item 35599184 and
unpack it into the directory that will be the application root — for example
/var/www/bicrypto. Everything runs from there; there is no separate public
directory to point a vhost at.
Two files must sit at the top of that directory:
installer.sh— the guided installer.initial.sql— the database schema. The installer exits with an error if it is not in the working directory, andpnpm seedhas nothing to seed into without it.
Later releases are applied in place from Admin → System → Updates, which downloads and extracts over the same tree. That screen requires an activated licence, so a first install always starts from the archive.
The guided installer
Run this from the application root, as root.
-
Change into the application directory. The installer uses the working directory as the application root — it never asks where the code is.
cd /var/www/bicrypto -
Run it with no arguments. Anything else is a repair mode, not an install.
bash installer.shEverything it prints is also appended to
/var/log/bicrypto-installer.log, including the parts that scroll past. When something goes wrong, that file is the record, not your terminal scrollback. -
Confirm, then answer the prompts. It asks
Continue with installation? [Y/n]and then works through twelve steps, stopping for input at the ones listed under What it asks you. Nothing is destructive until the schema import, which asks first if the database already has tables. -
Leave it alone during the build. Steps 8 of 12 (
pnpm installandpnpm build:frontend) are where the time goes. A silent death here is almost always the kernel OOM killer on a small box — the frontend build is launched with a 7780 MB heap ceiling. -
Read the summary. It prints the site URL, the database name, the Node and pnpm versions, and the seeded administrator account:
superadmin@example.com/12345678.
At the end the installer starts the platform itself and polls
http://localhost:3000 for up to a minute. If it reports the application may not
have started, the startup output is in /tmp/bicrypto-startup.log.
The final screen offers systemctl start|stop|restart|status bicrypto. The same
installer deliberately deletes /etc/systemd/system/bicrypto.service a few
seconds earlier, because that unit re-ran pnpm start every 10 seconds and
restarted the whole platform in a loop. Use pnpm start, pnpm stop and
pnpm restart from the application root. Do not recreate the unit.
What it asks you
Defaults come from the current .env. The site URL is the public address —
https://example.com, not a port. It is written to NEXT_PUBLIC_SITE_URL, which
is compiled into the browser bundle and into the allowed image hosts, so changing
it later means rebuilding the frontend.
Each defaults to the existing .env value; press Enter to keep it. An empty
password is rejected three times before it offers to exit. The port is checked to
be a number between 1 and 65535.
The connection test that runs after these prompts always reports success — the
script tests the wrong exit status. A wrong password therefore looks fine here
and surfaces later as a failed initial.sql import. If the import fails on
access denied, your credentials were wrong all along.
Only asked if the target database already contains tables. Answer y and every
table is dropped and re-imported; anything else skips the import and keeps what
is there. On a re-run over a live install, answering y destroys your data.
Each is a "continue anyway?" prompt rather than a hard stop. Continuing past the Redis failure produces an install that cannot boot the backend — Redis is a hard dependency, not a nicety.
What it changes on the server
Worth knowing before you run it on a box that does other work:
- Installs build tooling (
build-essential/gcc,python3,curl,git,unzip) through your package manager. - Installs Node 26 from NodeSource, and
pnpmglobally vianpm install -g pnpm@latest. Note the repository pinspnpm@10.28.0. - Installs and starts Redis, verifying it with
redis-cli ping. - Creates
.envfrom.env.exampleif absent, then writes your answers into it and generates four fresh 64-byte token secrets. - Imports
initial.sql, installs dependencies into./.pnpm-store, builds the frontend and runspnpm seed. - If the application directory is owned by root, creates a
bicryptosystem user, chowns the tree to it and recordsAPP_USERin.env. - Opens
ssh,http,httpsand 3000 in ufw or firewalld. It never opens 4000, which is correct — see Ports. - Enables Apache proxy modules, or restarts nginx. It writes no vhost for either.
- Runs
pm2 startupso PM2 comes back after a reboot, but never runspm2 save, so the saved process list is empty. Runpm2 saveyourself once the three apps are up.
Repair flags
bash installer.sh --help # list the flags
bash installer.sh --seed-only # import initial.sql and seed, no build
bash installer.sh --fix-permissions # re-apply ownership and file modes
bash installer.sh --fix-sharp # rebuild the image library on older CPUs
bash installer.sh --clean-build # delete build artefacts and node_modulesNone of these run the OS detection first, so they do not check that you are root
and they do not know your package manager — --fix-sharp in particular cannot
install system libraries when invoked this way.
The next full install then runs pnpm install --frozen-lockfile against a
missing lockfile and fails on its first attempt. If you use this flag, restore
the lockfile from the release archive before reinstalling.
The manual path
Use this when the box is already provisioned, when you are installing under a configuration-management tool, or when the guided installer failed somewhere you would rather do by hand. It is the same sequence, without the package installation.
-
Check the runtime. Any other major version fails at boot with
Cannot find module './uws_linux_x64_115.node'or similar.node -v # must be v22.x, v24.x or v26.x npm install -g pnpm@10.28.0 -
Create
.envfrom the template, then edit it. The shipped defaults point at a database calledv4onlocalhostasrootwith no password — all five database values need changing on a real box.cp .env.example .env -
Generate four token secrets. The values in
.env.exampleare published samples; leaving them is the same as having no secret at all. The backend refuses any token secret shorter than 32 characters.node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"Run it four times and paste the results into
APP_ACCESS_TOKEN_SECRET,APP_REFRESH_TOKEN_SECRET,APP_RESET_TOKEN_SECRETandAPP_VERIFY_TOKEN_SECRET. -
Create the database and import the schema.
pnpm seedinserts rows into tables thatinitial.sqlcreates; running it first does nothing useful.mysql -u root -p -e "CREATE DATABASE \`bicrypto\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -u root -p bicrypto < initial.sql -
Install dependencies from the application root. This is a pnpm workspace; do not run
npm installinfrontend/orbackend/.pnpm install -
Build the frontend. Finish editing
.envbefore this step — everyNEXT_PUBLIC_*value is compiled into the browser bundle here.pnpm build:frontendThere is no backend build to run.
backend/distships pre-built in the release archive. Never delete it: nothing in the install or update flow recreates it. -
Seed reference data. This writes roles, the 715 permission keys, notification templates and the first administrator.
pnpm seed -
Start the platform.
pnpm start pm2 save
The values that must be right
Everything else in .env can wait until the platform is up. These cannot.
pnpm build:frontend. Also the sole source of the production CORS allowlist.production on a live install. It is what makes session cookies Secure, and what selects the production asset paths.true ONLY for a load balancer on a different host, and understand that it then believes a forwarding header from any peer, including a caller who reaches the API port directly. Prefer TRUST_PROXY_CIDRS.Two more are worth setting before the first pnpm seed, because they decide
the first administrator account:
SUPERADMIN_EMAIL="you@example.com"
SUPERADMIN_PASSWORD="a-real-password"Set neither and the seeder creates superadmin@example.com with the published
password 12345678. The seeder records a marker in settings on its first run
and never touches the user table again, so this is a one-time choice — after
that, change the password from the profile screen.
Put a web server in front
Nothing you have done so far is reachable from the internet, and the frontend on its own cannot serve the API.
Two processes have to appear at one hostname: the frontend on localhost:3000
and the backend — including every WebSocket — on localhost:4000 under /api.
In development Next.js rewrites /api to the backend. In a production build it
does not — the rewrite rules sit below an early return in
frontend/next.config.js. If your proxy has no /api route, the site looks
fine and every request inside it fails.
Apache is the default. The installer checks for it first: if apache2 or
httpd is running it configures Apache and never looks at nginx. A Virtualmin
box is Apache out of the box, which is what most installs are.
Note what the installer does and does not do. configure_apache() enables the
proxy modules and restarts Apache — it does not write the vhost directives.
configure_nginx() does nothing at all beyond restarting the service. Either
way you add the proxy configuration yourself.
The default. Vhost directives, the modules to enable, and the Virtualmin caveat.
The alternative, if you run nginx instead. Complete server block.
Every WebSocket in the platform lives under /api and connects to the page's own
origin, so whichever server you use, handling the upgrade on that one prefix
covers all of them. The server pings each socket every 30 seconds, so the proxy
timeout must sit comfortably above that — a browser gives up permanently after
five failed reconnects, so one aggressive timeout kills live prices for that tab
until the user reloads.
HTTPS is not optional
In production the backend marks its accessToken and sessionId cookies
Secure with SameSite=None. Browsers discard Secure cookies delivered over
plain HTTP. A production install served over HTTP cannot log anyone in —
the login request succeeds, the cookie is dropped, and the next request is
anonymous. Nothing in the repository automates certificates; issue one with
certbot or your host's tooling before you go further.
Ports and the firewall
The backend binds every interface in production, not just loopback. Ports 4000
and 4001 must not be reachable from outside the box — the proxy talks to them
over 127.0.0.1. The installer opens 3000 in the firewall so you can test before
DNS is ready; close it once the proxy works.
Sign in and activate the licence
Until the licence is activated, the backend answers almost every API route with
403 License validation failed. Please activate your license. A short list stays
open precisely so you can get out of that state: /api/auth,
/api/user/profile, /api/settings, /api/admin/system/license,
/api/admin/system/extension and the geo-restriction routes. Everything else is
closed.
-
Open the site and sign in at
/loginwith the administrator account —superadmin@example.com/12345678unless you setSUPERADMIN_EMAILandSUPERADMIN_PASSWORDbefore seeding. -
Change that password immediately. It is published in this documentation, in the installer's own output, and in the seeder source.
-
Go to
/admin/system/licenseand enter your Envato purchase code. There is no menu entry for this screen; type the path, or follow the link from an exchange or extension detail page. -
Confirm the rest of the admin panel responds. If screens still return 403, activation did not take.
Activation and the hourly heartbeat call https://updates.mashdiv.com. If your
firewall blocks outbound HTTPS to that host, activation fails outright, and an
already-activated install keeps running on a 72-hour grace period before it
starts refusing requests.
Check the install is healthy
Run these from the application root on the server.
pm2 listYou want exactly three online apps — backend, frontend and cron — with a
restart count that is not climbing. A cron app that keeps restarting is usually
memory pressure; it is capped at 2 GB on purpose.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000
curl -s http://127.0.0.1:4000/api/settings | head -c 200The first should print 200. The second should print a JSON object with
settings and extensions keys — it is unauthenticated and licence-exempt,
which makes it the right readiness probe for a proxy health check too. If the
first works and the second is refused, the backend is down and the site will look
alive while every action fails.
pm2 logs backend --lines 50Exit code 78 in those logs is a configuration failure the backend will not retry: either the Node major version has no matching uWebSockets binary, or Redis is unreachable. The message says which. PM2 stops the app rather than crash-looping, so the explanation stays on screen.
Finally, load the site over its real domain in a browser, sign in, and open Admin → System → Settings. If that screen saves, the frontend, the API, the database, Redis and the licence are all working together.
If it fails
Two causes, and the log line names which. Either Node is not 22, 24 or 26 — check
with node -v, and note that PM2 keeps whatever Node it was first started with,
so pm2 kill and restart after upgrading Node — or Redis is unreachable. Test
Redis with redis-cli -h 127.0.0.1 -p 6379 ping, which must answer PONG.
The licence is not activated, or the platform cannot reach
https://updates.mashdiv.com. Sign in and open /admin/system/license; the
licence routes are exempt from the gate, so that screen works even when nothing
else does.
You are serving production over plain HTTP. Session cookies are Secure and are
being discarded by the browser. Fix TLS at the proxy — there is no setting that
turns this off.
Your database credentials were wrong. The installer's connection test cannot
detect this, so it reported success and continued. Correct DB_USER,
DB_PASSWORD, DB_HOST and DB_PORT in .env, import the schema by hand, then
run bash installer.sh --seed-only.
Almost always the kernel OOM killer. Check dmesg | tail. The frontend build is
the heaviest step in the whole install; add swap or build on a larger instance.
On a CPU without AVX2 the build can also fail inside the image library instead —
bash installer.sh --fix-sharp is the intended repair, but run it as root from
the application root.
The installer applies chmod 644 to every file in the tree, which strips the
execute bit from binaries in node_modules/.bin. Only *.sh files are restored.
Reinstalling dependencies with pnpm install puts them back.
pm2 startup was configured but pm2 save was never run, so PM2 resurrected an
empty list. Start the platform with pnpm start, then run pm2 save once.
That is what it does. pnpm stop stops the three apps and starts a maintenance
server in their place, which answers 503 with a maintenance page for pages and
503 JSON for /api/*. It exits with an error rather than claiming success if a
port is still held by something it does not manage. To close the ports entirely,
use pnpm stop:all.
Everything the installer printed, including the parts that scrolled away, is in
/var/log/bicrypto-installer.log. The startup attempt at the end writes to
/tmp/bicrypto-startup.log.
Back up before you go live
Two things on this box cannot be recovered if you lose them:
.env— it holdsENCRYPTED_ENCRYPTION_KEYandENCRYPTION_KEY_PASSPHRASEonce you set up custodial wallets. Losing them means losing every encrypted wallet key on the platform. Nothing can rebuild them.lic/— the activated licence files, which are machine-bound.
The built-in backup at Admin → System → Database dumps MySQL only. Redis,
.env, lic/ and frontend/public/uploads/ are yours to back up separately.