Enter your Wardyn license key to open the deployment guide and download the package.
This guide takes a fresh Linux server to a running Wardyn portal on your own domain. Wardyn is self-hosted: the portal and its database live on your infrastructure, under your brand. It reaches the vendor only for a periodic license check, and no employee data ever leaves your network.
portal.company.com, and access to its DNS.WDN-). The matching security secret is provisioned automatically on first check; nothing to paste by hand.Machine-bound sign-in needs a Chromium browser (Chrome, Edge, Brave, Opera, Vivaldi and others, list in section 8) with the Wardyn sign-in agent extension, section 8. Roles set to the authenticator-app method sign in from any browser.
Run each portal on its own subdomain. In your DNS provider (or Cloudflare) add an A record:
| Type | Name | Value |
|---|---|---|
A | portal (your chosen subdomain) | your server's public IP |
Propagation takes a few minutes. Confirm it resolves before continuing; HTTPS depends on it:
ping portal.company.commust answer with your server IPRunning several portals or brands? Add one A record per subdomain and repeat the install (or add one Nginx server block + certificate per subdomain on the same host).
The delivered archive (wardyn.zip) carries its own installer. It checks the server, generates every secret on the spot and writes .env (readable by root only), issues the HTTPS certificate and sets up its renewal, sets up a nightly database backup, then starts the portal, PostgreSQL, Redis and Nginx together. At the end it prints the one-time link to the Setup Wizard.
curl -fsSL https://get.docker.com | shunzip wardyn.zip && cd wardyn && sudo ./install.shIt asks for the portal domain, an e-mail for the certificate and your licence key. The same in one line, without questions:
sudo ./install.sh --domain portal.company.com --email [email protected] --key WDN-COR-XXXXXXXX-XXXXXXXX-XXXXXXXX --yes| Option | When |
|---|---|
--domain, --email, --key | Portal domain, e-mail for the Let's Encrypt certificate, your licence key. Asked interactively if left out. With the key in place the wizard opens already filled from your licence. |
--yes | No questions; everything comes from the options. |
--trust cloudflare | The server sits behind Cloudflare and only accepts its addresses. Put the Cloudflare Origin certificate into certs/ (fullchain.pem, privkey.pem) before you run the installer. |
--trust xff --hops N | Default. The portal reads the client address from X-Forwarded-For, trusting N proxies in front of the app (1 when nothing sits in front of this server). |
--trust direct | No proxy at all: the socket address is used and forwarding headers are ignored. |
--no-tls | HTTPS ends before this server (your own proxy). No certificate is issued here. |
--behind-proxy [PORT] | Another Nginx or Caddy already owns ports 80 and 443 on this machine. The portal listens on 127.0.0.1:PORT (8080 if not given) and the installer prints the server block for your proxy. |
The client address decides how the IP rules work, so the trust mode is set explicitly and never guessed. It is written to .env and applied again on every run of the installer.
The installer prints the location of.envat the end. Save it in your password manager, apart from database backups: without itsAES_KEYthe stored credentials cannot be decrypted. Running the installer again in the same folder is safe; it never overwrites the secrets in an existing.env.
Prefer full control, or already run your own database? Install the stack directly.
sudo apt update && sudo apt -y upgradecurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -sudo apt -y install nodejs redis-server nginxsudo npm i -g pm2sudo apt -y install postgresqlsudo -u postgres psql -c "CREATE USER your_db_user WITH PASSWORD 'CHANGE_ME_DB_PASSWORD';"sudo -u postgres psql -c "CREATE DATABASE your_db_name OWNER your_db_user;"unzip wardyn.zip && cd wardyn/appcp ../.env.example .envnano .envsection 5npm ci --omit=devinstalls all dependencies (incl. Excel import/export)npm run migratecreates the schemapm2 start src/app.js --name wardynpm2 save && pm2 startupthen run the printed commandServe the frontend (the web/ folder) as static files and reverse-proxy the API to 127.0.0.1:3000 in Nginx:
server {
server_name portal.company.com;
root /opt/wardyn/web;
index login.html;
location / { try_files $uri $uri.html $uri/ /login.html; }
location /api/ {
proxy_pass http://127.0.0.1:3000;
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_http_version 1.1;
}
listen 80;
}sudo nginx -t && sudo systemctl reload nginx.env)For the manual install. The one-command installer writes this file for you.
This is what your .env should look like. Copy it, then replace every CHANGE_ME value with your own:
# Wardyn portal .env (fill every CHANGE_ME value)
# database connection: postgresql://DB_USER:PASSWORD@HOST:5432/DB_NAME
# your_db_user / your_db_name = the user and database you create in step 4.2
DATABASE_URL=postgresql://your_db_user:CHANGE_ME_DB_PASSWORD@localhost:5432/your_db_name
REDIS_URL=redis://localhost:6379
# secrets: generate once and paste the output:
# openssl rand -hex 64 -> SESSION_SECRET
# openssl rand -hex 32 -> AES_KEY and MACHINE_SALT
SESSION_SECRET=CHANGE_ME_64_HEX
AES_KEY=CHANGE_ME_32_HEX
MACHINE_SALT=CHANGE_ME_32_HEX
# one-time code that protects the Setup Wizard: openssl rand -hex 16
SETUP_TOKEN=CHANGE_ME_16_HEX
# how the client address is read (Nginx in front of the app: xff, 1 hop)
TRUST_PROXY_MODE=xff
XFF_TRUST_HOPS=1
# your portal domain (the subdomain you set up in step 2)
RP_ID=portal.company.com
RP_ORIGIN=https://portal.company.com
PORT=3000
NODE_ENV=productionOn Docker, set the hosts to the compose services instead of localhost: ...@db:5432/... and redis://redis:6379.
The database user, database name and PM2 process name shown asyour_db_user/your_db_nameare placeholders. Use any names you like; you are not required to use ours. The only rule: the DB user and DB name must be identical across theCREATEcommands,DATABASE_URLand your backups.
What each key means:
| Key | Value |
|---|---|
DATABASE_URL | your Postgres connection string; the password is the one you set in step 4.2 |
REDIS_URL | redis://localhost:6379 (Docker: redis://redis:6379) |
SESSION_SECRET | output of openssl rand -hex 64 |
AES_KEY | output of openssl rand -hex 32 (encrypts secrets at rest) |
MACHINE_SALT | output of openssl rand -hex 32 |
RP_ID | your portal host, e.g. portal.company.com |
RP_ORIGIN | https://portal.company.com, must match the served domain exactly |
PORT | 3000 |
SETUP_TOKEN | output of openssl rand -hex 16. The Setup Wizard opens only with this code; without it anyone who reaches the server first could finish setup |
TRUST_PROXY_MODE / XFF_TRUST_HOPS | xff / 1 behind the Nginx above; cloudflare only if the server accepts nothing but Cloudflare addresses; direct without a proxy |
LICENSE_KEY | optional. When set, the Setup Wizard takes the licence from here and opens already filled |
The one-time e-mail code used during setup is relayed through the vendor's mail service, so you do not need your own email/SMTP configuration.RESEND_API_KEY/EMAIL_FROMare used only as a fallback if the relay cannot be reached.
For the manual install. The one-command installer issues the certificate and renews it on its own.
Let's Encrypt (Certbot):
sudo apt -y install certbot python3-certbot-nginxsudo certbot --nginx -d portal.company.comCloudflare: enable the proxy (orange cloud) on the A record and set SSL mode to Full (strict) with an Origin certificate on the server.
Open the link the installer printed at the end: https://portal.company.com/setup/?t=<code>. The code is the one-time SETUP_TOKEN from .env; if you open /setup/ without it, the wizard asks for it. The wizard works only until setup is finished. Under every step it says where to change that choice later.
The agent is a free extension, Wardyn Agent (formerly IT Portal Agent), published in the Chrome Web Store. It turns the employee's own computer into the key: it shows a six-digit sign-in code that changes every 15 seconds, and it opens the systems the person is allowed into without showing them a password. An administrator adds the person once in the portal; after that there is nothing for them to remember.
The agent runs in any browser built on Chromium 116 or newer. If the link does not open, search the Chrome Web Store for Wardyn Agent (until the store shows the new name: IT Portal Agent).
| Browser | How to install |
|---|---|
| Google Chrome, Chromium | Open the link, click Add to Chrome. |
| Microsoft Edge | Open the link. Edge shows a bar at the top: click Allow extensions from other stores, then Allow, then Add to Chrome. If the bar never appears, turn it on in edge://extensions (bottom left). |
| Brave | Open the link, click Add to Brave (the store may still say Add to Chrome; it installs into Brave). |
| Vivaldi, Yandex Browser, Arc | Open the link, click Add to Chrome; it installs into the browser you are in. |
| Opera, Opera GX | First add Opera's own Install Chrome Extensions add-on from addons.opera.com. Then open the link and click Add to Opera. |
| Firefox, Safari, phones and tablets | Not supported. People on these sign in with the authenticator-app method if their role allows it. |
Run agent/provision.bat from the delivery archive once per machine, as administrator. It writes the machine's serial number and name for the extension in Chrome, Edge and Brave, so the binding survives a browser reinstall. It writes nothing else and installs nothing. Laptops and machines without it bind by the extension's own device id; that is normal.
With Group Policy, Intune or your own image, add the extension to ExtensionInstallForcelist (id nokhapiedndgmkkfkogjdcieohddlfem, update URL https://clients2.google.com/service/update2/crx). The policy exists for Chrome and for Edge; the browser installs it on every machine and people cannot remove it.
curl -s https://portal.company.com/api/healthexpect {"ok":true,...}Manual install, on the server itself:
curl -s localhost:3000/api/healthexpect {"ok":true,...}wardyn as online (or docker compose ps shows the services up).One-command install: the installer sets up a nightly dump at 03:30 into the backups/ folder next to the installer; dumps are kept 14 days. Run one now with sudo ./backup.sh. Before every database migration the portal also takes its own dump, and the update stops if that dump fails.
Copy the dumps and .env off the server, to different places: a dump is useless without the AES_KEY from .env, and both are lost together if they stay on the same disk.
Manual install:
pg_dump -U your_db_user your_db_name | gzip > wardyn-$(date +%F).sql.gzFrom inside the portal (one-command install). Administration → Service → Updates shows the releases offered to your licence with their notes. Nothing installs by itself: the portal keeps running the version it has until the super admin, or an administrator given the Install updates right, presses Install. The portal checks the vendor signature, backs up the database, installs, restarts and runs a health check; if the check fails it returns to the previous version and data by itself. The last five versions are kept, so you can go back from the same screen. A security release is announced at the top of the portal and in the bell.
This needs the update agent that the installer sets up (a small systemd service, python3 on the server). If the screen says the agent is missing, run the installer again in the same folder.
By hand (one-command install): unpack the new wardyn.zip over the same folder (your .env, certificate and backups are not in the archive and stay) and run the installer again with your domain and the same --trust / --hops / --behind-proxy options as the first time, because the trust mode is applied again on every run.
unzip -o wardyn.zip && cd wardyn && sudo ./install.sh --domain portal.company.com --yesManual install: replace the app files (keep your .env), then:
cd wardyn/app && npm ci --omit=dev && npm run migrate && pm2 restart wardynmanual| State | Meaning |
|---|---|
| active | Valid. The portal checks in with the vendor every 2 hours and right after setup. Every answer is signed by the vendor; the portal accepts only signed, recent answers, so the licence cannot be edited or faked locally. |
| trial | A 14-day trial key. When it ends the portal goes read-only; nothing is deleted. |
| grace | Vendor temporarily unreachable. The portal keeps working for 7 days, then goes read-only. |
| read-only | Expired, revoked, paused, or the domain does not match the licence: read-only at once, without the 7 days. People can still sign in and read; changes are blocked until it is resolved. Data stays. |
ping the domain.SETUP_TOKEN from .env.wardyn.maxen.world is allowed. Use "Check now" in Settings → License.pm2 logs wardyn (or docker compose logs app); most often a wrong DATABASE_URL or a missing secret in .env.Questions? Reply to your activation email or write to [email protected].