Production Deployment Guide
Same-origin HTTPS deploy for CognitixERP — publish API + WebApp, put nginx (or IIS) in front,
then optionally automate updates with GitHub Actions.
Section 4 walks through the packaged Linux VPS recipe in
Source/CognitixERP/deploy/hetzner/.
1. Big picture
Use one website for everything (same-origin):
https://yourcompany.com/ → Blazor WebApp
https://yourcompany.com/api/... → Cognitix.API
https://yourcompany.com/uploads/ → logos & files
https://yourcompany.com/chat-hub → SignalR (CRM)
| On the server | Where |
|---|---|
| API files | /var/www/cognitix-erp/api |
| WebApp files | /var/www/cognitix-erp/webapp |
| API process | systemd cognitix-api on 127.0.0.1:5100 (local only) |
| Public HTTPS | nginx (or IIS) in front |
Replace every placeholder in this guide with your own values:
| Placeholder | Meaning |
|---|---|
YOUR_SERVER_IP | Public IP of your VPS |
YOUR_SSH_USER | Linux user used for deploy (often deploy) |
yourcompany.com | Your public HTTPS hostname |
/var/www/cognitix-erp | Install directory on the server (change if you prefer) |
You will configure:
- VPS — folders, database, systemd, nginx, SSL
- GitHub (optional) — secrets + Actions workflow
- Setup Wizard — first admin + license after the first successful publish
lib/Cognitix.Licensing/.
When you publish, use -p:BuyerBuild=true.
Sample files live in Source/CognitixERP/deploy/hetzner/
(nginx, systemd, PostgreSQL init, production appsettings, deploy key script).
Commands below are the same walkthrough as deploy/hetzner/README.md.
2. URLs you must set
Replace yourcompany.com with your real domain. Keep all values matching — including JWT Issuer/Audience.
CORS AllowedOrigins must be the same HTTPS origin as the WebApp.
| Place | Value |
|---|---|
| Setup Wizard — Frontend URL | https://yourcompany.com |
| Setup Wizard — API URL | https://yourcompany.com (no /api) |
WebApp appsettings.json | "ApiBaseUrl": "https://yourcompany.com/" |
| API Jwt Issuer / Audience | https://yourcompany.com |
| API Cors AllowedOrigins | https://yourcompany.com |
| API ApiSettings:BaseUrl | https://yourcompany.com |
| API AppSettings:WebAppUrl | https://yourcompany.com |
/api at the end of API URL or ApiBaseUrl.
The WebApp already calls paths like api/.... Adding /api breaks logos and uploads
(URLs become /api/uploads/... and get blocked).
3. Manual deploy (publish on your PC)
From Source/CognitixERP. Use this when you are not using GitHub Actions yet (or for a one-off publish).
3.1 Publish API (Linux VPS)
dotnet publish src\Cognitix.API\Cognitix.API.csproj ^
-c Release -r linux-x64 --self-contained true ^
-p:BuyerBuild=true ^
-o C:\publish\Cognitix.API
3.2 Publish WebApp
dotnet workload install wasm-tools
dotnet publish apps\Cognitix.WebApp\Cognitix.WebApp.csproj ^
-c Release -p:BuyerBuild=true ^
-o C:\publish\Cognitix.WebApp
Before or after publish, set WebApp config:
{
"ApiBaseUrl": "https://yourcompany.com/"
}
Copy API output to the server api/ folder. Copy WebApp wwwroot contents to webapp/.
3.3 Server secrets file
On the server only, create api/appsettings.Production.json
(use sample deploy/hetzner/appsettings.Production.server.example.json). Set:
- Database connection string
- JWT key (32+ chars) or env
COGNITIX_JWT_KEY - CORS + Jwt Issuer/Audience + BaseUrl/WebAppUrl (table in section 2)
Licensing:SkipInDevelopment=false+ CognitiveCircle ApiKey / HmacSecret
4. Linux VPS with nginx (recommended)
This is the packaged Hetzner-style recipe. It works on any Ubuntu/Debian VPS (Hetzner, DigitalOcean, AWS Lightsail, a local VM).
Sample files are in Source/CognitixERP/deploy/hetzner/.
Target layout:
/var/www/cognitix-erp/
|-- api/ # Cognitix.API (systemd: cognitix-api)
`-- webapp/ # Blazor WASM (served by nginx)
Nginx routing:
/→ WebApp/api/→http://127.0.0.1:5100(API listens locally only)/uploads/→ API static uploads (logos, favicons, files)/api/uploads/→ same files (legacy URL compatibility)/chat-hub→ SignalR
API internal URL: http://127.0.0.1:5100 · Public site: https://yourcompany.com
4.1 SSH into the server
ssh YOUR_SSH_USER@YOUR_SERVER_IP
Create a non-root deploy user with sudo if you do not already have one. The sample systemd unit uses User=deploy — change it if your SSH user is different.
4.2 Create folders
sudo mkdir -p /var/www/cognitix-erp/{api,webapp}
sudo mkdir -p /etc/cognitix-erp
sudo chown -R YOUR_SSH_USER:www-data /var/www/cognitix-erp
sudo chmod -R u+rwX,g+rwX /var/www/cognitix-erp
4.3 Database
CognitixERP supports SQL Server, PostgreSQL, or MySQL — one engine per install. The Hetzner samples assume PostgreSQL on the same VPS. Create the database before first Setup Wizard, or let the wizard create it if the login has permission.
PostgreSQL (on the server):
sudo -u postgres psql <<'SQL'
CREATE USER cognitix WITH PASSWORD 'CHANGE_ME_DB_PASSWORD';
CREATE DATABASE cognitix_erp OWNER cognitix;
GRANT ALL PRIVILEGES ON DATABASE cognitix_erp TO cognitix;
\c cognitix_erp
GRANT ALL ON SCHEMA public TO cognitix;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO cognitix;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO cognitix;
SQL
Use the same password later in appsettings.Production.json.
Or copy the sample script from your PC:
scp deploy/hetzner/postgresql-init.sql YOUR_SSH_USER@YOUR_SERVER_IP:/tmp/
ssh YOUR_SSH_USER@YOUR_SERVER_IP "sudo -u postgres psql -f /tmp/postgresql-init.sql"
Edit the script first if you changed database or user names.
4.4 Production settings (server only)
From your PC (repo root Source/CognitixERP):
scp deploy/hetzner/appsettings.Production.server.example.json YOUR_SSH_USER@YOUR_SERVER_IP:/tmp/appsettings.Production.json
On the server:
ssh YOUR_SSH_USER@YOUR_SERVER_IP
sudo mv /tmp/appsettings.Production.json /var/www/cognitix-erp/api/appsettings.Production.json
sudo nano /var/www/cognitix-erp/api/appsettings.Production.json
Set at least:
ConnectionStrings:DefaultConnection— DB host / user / passwordJwt:Key— random secret, 32+ charactersCors:AllowedOrigins—https://yourcompany.comAllowedHosts— e.g.yourcompany.com;localhost;127.0.0.1(CI health check usesHost: localhost)ApiSettings:BaseUrl—https://yourcompany.com(no trailing/api)AppSettings:WebAppUrl—https://yourcompany.comLicensing:ApiKeyandLicensing:HmacSecret— from your license provider
Permissions:
sudo chown YOUR_SSH_USER:www-data /var/www/cognitix-erp/api/appsettings.Production.json
sudo chmod 640 /var/www/cognitix-erp/api/appsettings.Production.json
Optional overrides (systemd reads this file):
sudo tee /etc/cognitix-erp/api.env >/dev/null <<'EOF'
# ConnectionStrings__DefaultConnection=...
# Licensing__ApiKey=...
# Licensing__HmacSecret=...
EOF
sudo chmod 600 /etc/cognitix-erp/api.env
appsettings.Production.json.
Keep it only on the server — the sample workflow excludes it.
4.5 systemd unit
scp deploy/hetzner/cognitix-api.service YOUR_SSH_USER@YOUR_SERVER_IP:/tmp/
ssh YOUR_SSH_USER@YOUR_SERVER_IP
sudo mv /tmp/cognitix-api.service /etc/systemd/system/cognitix-api.service
sudo systemctl daemon-reload
sudo systemctl enable cognitix-api
Edit User= and paths in the unit if your SSH user or install path differs.
The sample starts Cognitix.API --urls http://127.0.0.1:5100 with
ASPNETCORE_ENVIRONMENT=Production.
systemctl start may fail until the first successful publish — that is expected.4.6 DNS + nginx + SSL
DNS — point your hostname at the VPS:
| Type | Name | Value |
|---|---|---|
| A | your host / @ / subdomain | YOUR_SERVER_IP |
nginx
- Copy
deploy/hetzner/nginx-site.conf.example. - Replace every hostname inside with
yourcompany.com. - Ensure these locations exist:
/api/,/uploads/,/api/uploads/,/,/chat-hub.
scp deploy/hetzner/nginx-site.conf.example YOUR_SSH_USER@YOUR_SERVER_IP:/tmp/yourcompany.com.conf
ssh YOUR_SSH_USER@YOUR_SERVER_IP
sudo mv /tmp/yourcompany.com.conf /etc/nginx/sites-available/yourcompany.com
sudo ln -sf /etc/nginx/sites-available/yourcompany.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
TLS
sudo certbot --nginx -d yourcompany.com
sudo nginx -t && sudo systemctl reload nginx
/uploads/ and /api/uploads/ proxy blocks
(they are already in the sample nginx file). See Troubleshooting.
4.7 Passwordless restart for CI
Needed only if GitHub Actions will restart the API after each deploy.
sudo visudo -f /etc/sudoers.d/cognitix-erp-deploy
One line (use your SSH user):
YOUR_SSH_USER ALL=(ALL) NOPASSWD: /bin/systemctl restart cognitix-api
Test:
sudo -n systemctl restart cognitix-api
5. GitHub Actions CI/CD
Optional, but best for updates. Matches deploy/hetzner/README.md Part B.
5.1 What you need
- Your code in a GitHub repo (buyer package root =
Source/CognitixERP) - Workflow file:
.github/workflows/deploy.yml(sample:deploy/hetzner/github-workflows/deploy.yml) - Four GitHub Secrets (no secrets inside the YAML file)
- Licensing DLLs committed:
lib/Cognitix.Licensing/*.dll - GitHub Environment named
production
5.2 Create deploy SSH key (on your PC)
bash deploy/hetzner/generate-deploy-key.sh
Creates:
deploy/hetzner/deploy-keys/cognitix_erp_deploy— private (GitHub Secret only)deploy/hetzner/deploy-keys/cognitix_erp_deploy.pub— public (serverauthorized_keys)
Install the public key on the server:
# Windows PowerShell
type deploy\hetzner\deploy-keys\cognitix_erp_deploy.pub | ssh YOUR_SSH_USER@YOUR_SERVER_IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# Linux / macOS
cat deploy/hetzner/deploy-keys/cognitix_erp_deploy.pub | ssh YOUR_SSH_USER@YOUR_SERVER_IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Test:
ssh -i deploy/hetzner/deploy-keys/cognitix_erp_deploy YOUR_SSH_USER@YOUR_SERVER_IP
5.3 GitHub Secrets
GitHub → Settings → Secrets and variables → Actions → New repository secret
| Secret name | Value |
|---|---|
DEPLOY_HOST | YOUR_SERVER_IP |
DEPLOY_USER | YOUR_SSH_USER |
DEPLOY_PATH | /var/www/cognitix-erp |
DEPLOY_SSH_KEY | Full private key text (-----BEGIN OPENSSH PRIVATE KEY----- …) |
Copy private key (PowerShell):
Get-Content .\deploy\hetzner\deploy-keys\cognitix_erp_deploy -Raw | Set-Clipboard
5.4 GitHub Environment
Settings → Environments → New environment → name: production
(The workflow uses environment: production.)
5.5 Commit Licensing DLLs + run deploy
This buyer package builds Licensing from DLLs. Commit them or CI fails with missing Cognitix.Licensing.* / CS0234.
git add -f lib/Cognitix.Licensing/*.dll
git add .github/workflows/deploy.yml
git commit -m "Include Licensing DLLs for CI builds."
git push
Then: GitHub → Actions → Deploy → Run workflow (or push to the branch the workflow watches).
cognitix-api active → health HTTP 200.
5.6 What CI does / does not do
- Builds API + WebApp and copies files to the server
- Restarts
cognitix-api(needs the sudoers rule in section 4.7) - Does not overwrite
appsettings.Production.json(keep secrets on the server) - Does not delete
wwwroot/uploads(logos stay)
6. Setup Wizard after first deploy
Open https://yourcompany.com/setup after the first successful publish.
6.1 Public URLs (same origin)
| Item | URL |
|---|---|
| Frontend | https://yourcompany.com |
| Setup Wizard | https://yourcompany.com/setup |
| Login | https://yourcompany.com/users/login |
| API root (for wizard) | https://yourcompany.com |
| Health | https://yourcompany.com/api/system/health |
https://yourcompany.com).
Do not append /api.
6.2 Step 1 — Database and URLs
| Field | Typical value |
|---|---|
| Frontend URL | https://yourcompany.com |
| API URL | https://yourcompany.com (no /api) |
| Database Engine | PostgreSQL (or SQL Server / MySQL) |
| DB Server | 127.0.0.1 (if DB is on the same VPS) |
| Port | 5432 (PostgreSQL) · 1433 (SQL Server) · 3306 (MySQL) |
| Database Name | your DB name (e.g. cognitix_erp) |
| DB Username / Password | values from section 4.3 |
Schema provisioning applies all 18 module DbContexts and usually takes several minutes — keep the tab open.
6.3 Step 2 — Administrator
Create the first admin account. No default password ships with the product. Step 1 must have created a headquarters branch first.
6.4 Step 3 — License / Trial
Activate your Envato purchase code or start the 14-day trial.
Production needs internet plus CognitiveCircle keys in server appsettings.Production.json first
(Licensing:ApiKey, Licensing:HmacSecret, SkipInDevelopment=false).
6.5 WebApp ApiBaseUrl
{
"ApiBaseUrl": "https://yourcompany.com/"
}
Confirm webapp/appsettings.json (or wwwroot/appsettings.json after publish) matches after deploy. Copy the snippet from the wizard if it offers a download.
https://yourcompany.com/users/login.
7. Go-live checks
curl -i https://yourcompany.com/api/system/health
On the server:
ssh YOUR_SSH_USER@YOUR_SERVER_IP
sudo systemctl status cognitix-api
curl -i -H "Host: localhost" http://127.0.0.1:5100/api/system/health
- Site loads over HTTPS
- Login works with your admin account
- Company logo loads (needs nginx
/uploads/) - License status is Trial or Active (not DevelopmentSkipped)
Quick checklist
- VPS folders, database,
appsettings.Production.json, systemd, nginx, SSL, sudoers - Deploy key → public on server, private in
DEPLOY_SSH_KEY - Secrets:
DEPLOY_HOST,DEPLOY_USER,DEPLOY_PATH,DEPLOY_SSH_KEY - Commit Licensing DLLs + ensure
.github/workflows/deploy.yml - Run the deploy workflow (or copy a manual publish)
- Open
/setup— Frontend URL and API URL =https://yourcompany.com(no/api) - Create admin → activate license →
/users/login
8. Troubleshooting
| Symptom | What to check |
|---|---|
| Cannot SSH | IP, user, firewall, authorized_keys |
| GitHub deploy SSH fails | DEPLOY_SSH_KEY is the full private key |
| Missing Licensing assemblies | Commit lib/Cognitix.Licensing/*.dll |
| Health check HTTP 400 while API is up | AllowedHosts must allow localhost (CI sends Host: localhost) |
cognitix-api failed | journalctl -u cognitix-api -n 80 --no-pager |
| DB errors | Connection string / password / engine matches Setup Wizard |
| CORS errors | Cors:AllowedOrigins includes https://yourcompany.com |
| 502 Bad Gateway | Is the API running? Is nginx proxy_pass http://127.0.0.1:5100? |
| Logo / uploads 401 or blank | Add /uploads/ and /api/uploads/ nginx locations (below) |
| CI cannot restart service | sudoers rule in section 4.7 |
| Site does not open | DNS A record → YOUR_SERVER_IP |
nginx upload locations
Inside your site config, after location /api/:
location /uploads/ {
proxy_pass http://127.0.0.1:5100;
proxy_http_version 1.1;
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_set_header Connection keep-alive;
expires 7d;
add_header Cache-Control "public";
}
location /api/uploads/ {
proxy_pass http://127.0.0.1:5100/uploads/;
proxy_http_version 1.1;
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 Connection keep-alive;
expires 7d;
add_header Cache-Control "public";
}
sudo nginx -t && sudo systemctl reload nginx
Optional PostgreSQL cleanup if old URLs contain /api/uploads/:
UPDATE branching."CompanySetups"
SET "CompanyLogoUrl" = REPLACE("CompanyLogoUrl", '/api/uploads/', '/uploads/'),
"CompanyFaviconUrl" = REPLACE("CompanyFaviconUrl", '/api/uploads/', '/uploads/')
WHERE "CompanyLogoUrl" LIKE '%/api/uploads/%'
OR "CompanyFaviconUrl" LIKE '%/api/uploads/%';
More product-wide issues: Troubleshooting FAQ.
9. Windows IIS (short)
- Install .NET 10 Hosting Bundle + URL Rewrite
- One HTTPS site for
yourcompany.com - Serve WebApp static files; reverse-proxy
/api/,/uploads/,/chat-hubto the API (same idea as nginx) - Set
ASPNETCORE_ENVIRONMENT=ProductionandCOGNITIX_JWT_KEY - Use the same URL table from section 2
For most buyers, Linux + nginx + optional GitHub Actions is simpler.
10. Please Contact us
- Sign up at https://cognitivebd.com
- Create a Support Ticket.
- Within 48hr we will reply.
- WhatsApp: +18632279760
Or email support@cognitivebd.com with your purchase code, version, and screenshots/logs.
Production Deployment