Ritzy One • Administrator / Vendor Guide — Internal
Run several unrelated companies — customers, or prospects on a demo server — on one Ritzy One install, each signing in at its own address such as acme.demo.ritzy.net.au. Each address shows and accepts only its own company, so nobody sees the names of the others.
A Ritzy One install keeps all its companies in one PostgreSQL database. Row-Level Security already keeps each company's data apart. But by default the sign-in page lists every company on the install to anyone who opens it, so two prospects sharing a demo server would see each other's company names.
With LOGIN_COMPANY_SCOPE=host the address in the browser decides the company:
It applies to the web app and all the phone apps (they share the same sign-in), the tenant portal's contract-number sign-in, and the public /api/auth/diag connection check. Tenant portal invitation links are not affected: the link itself names the account.
The company is the first part of the host name, matched to the company code without regard to case. LOGIN_HOST_MAP overrides that for any address you list.
| Address opened | Company | Why |
|---|---|---|
acme.demo.ritzy.net.au | ACME | First part of the name |
psk.ritzy.lan:8081 (a phone app) | PSK | The port does not matter |
demo2.demo.ritzy.net.au | DEMO_2 | Listed in LOGIN_HOST_MAP |
192.168.1.214 | none — or as mapped | “192” is not a company code |
www.demo.ritzy.net.au | none | No company called WWW |
DEMO_2 can never be matched from the first part of a name, so map it: LOGIN_HOST_MAP=demo2.demo.ritzy.net.au=DEMO_2. When choosing codes for a shared install, letters and digits only keep things simple.| Situation | Use |
|---|---|
| A demo server: several prospects, each looking at a sample or imported company for a few days | This setting — one install, one subdomain each |
| A pilot: a prospect working on their real data for weeks | A separate stack per customer (docker/new-stack.sh, see the Docker Installation guide). It has its own database, backups and upgrade timing. |
| A customer with several companies of their own | Leave the setting off. Their staff should see all their companies at one address. |
In the DBA Console, add each company to the install:
DB_<code>.FDB).Write down each company's code. It becomes that company's subdomain:
| Company code | Address |
|---|---|
PSK | psk.demo.ritzy.net.au |
ACME | acme.demo.ritzy.net.au |
Give each prospect their own user in their own company. Do not give them DBA rights (see section 11).
At your DNS provider, add one wildcard record. It covers every company, now and later, with no further DNS work:
*.demo.ritzy.net.au A <the server's public IP>
If the domain is on Cloudflare, follow section 5a instead: there's a choice to make there that affects the phone apps and HTTPS.
No DNS is needed. On each PC you test from, open Notepad as Administrator, edit C:\Windows\System32\drivers\etc\hosts and add a line per company:
192.168.1.214 psk.ritzy.lan 192.168.1.214 acme.ritzy.lan
Use the server's own address in place of 192.168.1.214.
In the Cloudflare dashboard, open the domain (ritzy.net.au) and go to DNS → Records. First decide how the subdomains go through Cloudflare. That is the Proxy status switch on the record:
| Option A — DNS only (grey cloud) ✅ recommended for a demo server | Option B — Proxied (orange cloud) | |
|---|---|---|
| What Cloudflare does | Only answers the name with the server's IP. Visitors connect to the server directly. | Visitors connect to Cloudflare, which forwards to the server. It hides the server's IP and absorbs attacks. |
| Phone apps on ports 8081–8096 | Work as they are | Do not work. Cloudflare only forwards a few ports (HTTP 80, 8080, 8880, 2052, 2082, 2086, 2095; HTTPS 443, 2053, 2083, 2087, 2096, 8443). The phone apps would need moving to those ports. |
| Certificate | The server gets its own (Caddy in docker/trial-proxy does it automatically, or win-acme) | Cloudflare's free certificate covers *.ritzy.net.au but not *.demo.ritzy.net.au (two levels deep). Use one-level names such as acme.ritzy.net.au, or buy Cloudflare's Advanced Certificate Manager. |
A, Name *.demo, IPv4 address = the server's public IP, Proxy status DNS only (grey cloud), TTL Auto. Save.nslookup acme.demo.ritzy.net.au answers with the server's IP.A, Name * for acme.ritzy.net.au-style names (or *.demo if you have Advanced Certificate Manager), IPv4 address = the server's public IP, Proxy status Proxied (orange cloud). Records you already have (www, docs, lic2…) keep working: a named record always wins over the wildcard.*.ritzy.net.au and ritzy.net.au (add *.demo.ritzy.net.au if you use that), 15 years. Save the certificate and private key as two files on the server, e.g. C:\nginx\conf\ssl\ritzy-origin.pem and ritzy-origin.key, and point nginx at them:
listen 443 ssl; ssl_certificate C:/nginx/conf/ssl/ritzy-origin.pem; ssl_certificate_key C:/nginx/conf/ssl/ritzy-origin.key;This certificate is only trusted by Cloudflare, which is all it needs to be: visitors see Cloudflare's certificate.
/api/ → Bypass cache, so no API answer is ever served from Cloudflare's cache./api/.nginx must hand the address the visitor typed to the API. The standard Ritzy One config already does this in every location /api/ block. Check it is there:
location /api/ {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host; # required: the API reads the company from this
...
}
Then add the subdomains to the server_name line of each server { } block in C:\nginx\conf\nginx.conf, the web app on port 80 and each phone app on its own port:
server_name 192.168.1.214 *.demo.ritzy.net.au *.ritzy.lan;
Take a copy of nginx.conf first. Then test the config and restart nginx in PowerShell, as Administrator:
cd C:\nginx .\nginx.exe -t Restart-Service nginx
nginx -s reload is refused (Access is denied). Use Restart-Service nginx.The phone apps keep their ports, e.g. acme.demo.ritzy.net.au:8081. CORS_ORIGIN does not need changing: each page talks to the API through its own address.
The setting goes in the API's settings file, C:\RitzyOne\packages\api\.env, not a .env in the install folder's root. On installs from November 2026 there is a commented-out section for it at the end of the file.
LOGIN_COMPANY_SCOPE=host
# Any address that is not a company code, comma-separated (address=CODE):
LOGIN_HOST_MAP=192.168.1.214=PSK,localhost=PSK,demo2.demo.ritzy.net.au=DEMO_2
Then restart the API in PowerShell, as Administrator:
Restart-Service ritzy-api
http://192.168.1.214) or by localhost, map those to a company in LOGIN_HOST_MAP, or they will be locked out.| Setting | Values | Default |
|---|---|---|
LOGIN_COMPANY_SCOPE | all — every company at every addresshost — each address only its own company | all |
LOGIN_HOST_MAP | Comma-separated address=CODE pairs. The address is the host name only: no http://, no port. Spaces and case do not matter. | empty |
Before prospects sign in over the internet, put the addresses on HTTPS. Passwords should not travel in plain text, and the phone apps can only be installed from an HTTPS address.
docker/trial-proxy (Caddy) gets and renews a certificate for each subdomain by itself. It needs ports 80 and 443 free and the wildcard DNS record from step 2.*.demo.ritzy.net.au (a wildcard needs DNS validation at your DNS provider), then add listen 443 ssl; with the certificate files to each server block.http://acme.demo.ritzy.net.au. The company box shows only ACME, already selected. Sign in.http://psk.demo.ritzy.net.au. It shows only PSK.http://www.demo.ritzy.net.au. The company list is empty./api/auth/companies on each address, e.g. http://acme.demo.ritzy.net.au/api/auth/companies. It returns one company, or [].http://acme.demo.ritzy.net.au:8081.A Docker stack reads its settings from the stack's .env (beside its docker-compose.yml). Add the same two lines there and restart the stack:
LOGIN_COMPANY_SCOPE=host LOGIN_HOST_MAP=demo2.demo.ritzy.net.au=DEMO_2
docker compose up -d
Caddy in docker/trial-proxy passes the address on by default. Point every prospect's subdomain at the one shared demo stack's port.
| What you see | Likely cause | Fix |
|---|---|---|
| Sign-in page has an empty company list | The address names no company | Check the first part of the address matches a company code, or add it to LOGIN_HOST_MAP. Restart ritzy-api. |
| Every company is still listed | Setting not read | Check it is in packages\api\.env, not commented out, spelled host; restart ritzy-api. |
| Every address shows the same company, or none | nginx is not passing the address on | Add proxy_set_header Host $host; to each location /api/; restart nginx. |
| Address does not open at all | DNS or hosts file | Check the wildcard record, or the hosts file line on that PC; ping acme.ritzy.lan should answer from the server. |
| Correct password but Invalid user ID or password | Signing in to a company that address doesn't belong to | Use that company's own address. |
| Web app works but phone apps don't, through Cloudflare | Proxied record; Cloudflare doesn't forward ports 8081–8096 | Use a DNS-only record, or move the apps to Cloudflare's ports (5a). |
Browser certificate error on acme.demo.ritzy.net.au through Cloudflare | Cloudflare's free certificate doesn't cover two-level names | Use DNS only, one-level names (acme.ritzy.net.au), or Advanced Certificate Manager. |
| 526 Invalid SSL certificate / 525 SSL handshake failed from Cloudflare | Full (strict) is on but the server has no valid certificate on 443 | Install the Cloudflare Origin Certificate in nginx (5a, option B). |
| Staff locked out after turning it on | They open the server by IP or localhost | Map those addresses in LOGIN_HOST_MAP. |
Put a # back in front of LOGIN_COMPANY_SCOPE=host (or set it to all) and run Restart-Service ritzy-api. Every address lists every company again. Nothing is stored in the database, so turning it on and off is safe at any time.