Manual Deployment
Deploy Hexabot directly with Node.js, PostgreSQL, NGINX, and HTTPS.
Choose this method when you want to run Hexabot directly on the host with Node.js.
Use this path when you want full control over the runtime and do not want Docker in production.
For shared deployment concepts, see How can I deploy my Hexabot project?.
Prerequisites
You need:
A Linux server with SSH access
Node.js
>= 24.17.0One package manager:
npm,pnpm,yarn, orbunPostgreSQL
NGINX
Certbot
A public domain that points to your server
Install the base packages first:
sudo apt-get update
sudo apt-get install -y nginx certbot python3-certbot-nginx postgresql postgresql-contrib
sudo systemctl enable --now nginx postgresqlInstall Node.js before you run any Hexabot command.
If you install Node.js from NodeSource APT packages, the binaries are usually under /usr/bin.
If you install Node.js with nvm, use the absolute node path later in the systemd service.
Helpful setup guides:
Project setup
Install the CLI globally:
Create the dedicated service user before you clone the project.
This guide uses a ubuntu system user and the home directory /home/ubuntu .
Clone the project directly into its final path and install dependencies as hexabot:
If the repository is private, use a deploy key, a machine user, or HTTPS with a token.
If .env is missing, create it once:
hexabot env init copies .env.example to .env.
Use it only when .env does not exist yet.
If the project already contains a .env file generated by the Hexabot CLI, keep that file and do not rerun the command.
Use --force only when you intentionally want to replace the existing .env from .env.example.
It does not merge values.
Run these checks before you continue:
If you keep the project in your home directory instead, replace User, WorkingDirectory, and EnvironmentFile later with your own values.
Environment configuration
Generate strong secrets before you edit .env:
Edit /opt/hexabot/my-project/.env and use production values like these:
NODE_ENV
production
Use production for ongoing public deployment.
PORT
3000
Default Hexabot app port.
APP_DOMAIN
mychatbot.ai
Public domain name.
SSL_EMAIL
admin@mychatbot.ai
Email used by Certbot.
API_ORIGIN
https://mychatbot.ai/api
Public API base URL.
FRONTEND_BASE_URL
https://mychatbot.ai
Public app URL.
FRONTEND_ORIGIN
https://mychatbot.ai
Allowed browser origin.
HTTPS_ENABLED
true
Enable public HTTPS-aware behavior.
AUDIT_LOG_SERVICE_ENVIRONMENT
production
Use production on the public server.
DB_TYPE
postgres
Recommended production database.
DB_HOST
127.0.0.1
PostgreSQL host.
DB_PORT
5432
PostgreSQL port.
DB_USERNAME
hexabot
PostgreSQL user.
DB_PASSWORD
change-me
PostgreSQL password.
DB_NAME
hexabot
PostgreSQL database name.
DB_URL
postgresql://hexabot:change-me@127.0.0.1:5432/hexabot
Optional connection string.
DB_SCHEMA
public
Default PostgreSQL schema.
DB_SYNCHRONIZE
false
Keep this false for ongoing production.
DB_AUTO_MIGRATE
true
Enable on one primary API node.
API_IS_PRIMARY_NODE
true
Set true on the node that runs automatic migrations.
SESSION_SECRET
...
Use a long random value.
JWT_SECRET
...
Use a long random value.
PASSWORD_RESET_SECRET
...
Use a long random value.
CONFIRM_ACCOUNT_SECRET
...
Use a long random value.
SIGNED_URL_SECRET
...
Use a long random value.
UPLOAD_DIR
/var/lib/hexabot/uploads
Writable upload directory.
UPLOAD_MAX_SIZE_IN_BYTES
52428800
50 MB. Match the NGINX body size.
Keep DB_HOST=127.0.0.1.
Do not use DB_HOST=localhost for this setup.
localhost may connect through a Unix socket.
PostgreSQL may then use peer authentication.
That often causes confusing login failures for the Hexabot database user.
127.0.0.1 forces a TCP connection and works with password authentication.
The default .env template may use UPLOAD_DIR=/uploads.
That path is meant for Docker mounts.
For a native server deployment, use a writable host path such as /var/lib/hexabot/uploads.
Create it before the first upload:
Optional SMTP variables:
EMAIL_SMTP_ENABLED
true
Enable transactional email.
EMAIL_SMTP_HOST
smtp.example.com
SMTP host.
EMAIL_SMTP_PORT
587
Common STARTTLS port.
EMAIL_SMTP_SECURE
false
Use true for implicit TLS on 465.
EMAIL_SMTP_USER
smtp-user
SMTP username or API key user.
EMAIL_SMTP_PASS
smtp-password
SMTP password or API key.
EMAIL_SMTP_FROM
Hexabot <noreply@mychatbot.ai>
Sender address.
Hexabot reads SMTP settings at startup.
Restart the app after any SMTP change.
PostgreSQL setup
PostgreSQL is the recommended database for production.
A typical PostgreSQL section in .env looks like this:
If PostgreSQL runs on the same server, create the Hexabot database and user:
Use the same database name, user, and password in .env.
You can also point Hexabot at a managed PostgreSQL service.
Native Ubuntu installs use the distro PostgreSQL version.
If you move data from a Docker PostgreSQL instance, use pg_dump and pg_restore.
Do not copy the raw data directory between versions.
Initial database bootstrap
Fresh PostgreSQL deployments need one private bootstrap before public traffic.
The first bootstrap may require development-style settings.
This is expected.
Database synchronization and seeding are disabled in production mode to reduce the risk of accidental data loss.
For the first bootstrap only, temporarily update .env with values like these:
If your project uses admin seed names, you can also set them here:
Then start the app in dev mode once:
Wait until the app finishes booting and the database is initialized.
Then verify that the admin user exists:
The output should include the seeded admin email.
Stop the bootstrap process after the admin user is created.
If you started it in the current shell, press Ctrl+C.
Switch back to production settings
After the first bootstrap, update .env again:
Keep DB_AUTO_MIGRATE=true and API_IS_PRIMARY_NODE=true on a single-server deployment.
Do not leave SEED_ADMIN_PASSWORD in plaintext in a production .env file after the first admin user is created.
Clear or remove the seed variables as soon as bootstrap is finished.
If you already have a trusted initialized database, import that database instead of bootstrapping a fresh one.
Production build
This guide uses the compiled production bundle.
Build it from the deployed project directory before the first start:
This command creates the dist/ directory.
node dist/main requires dist/ to exist.
Some projects also expose npm run start:prod.
That command also requires dist/ to exist.
Run the build command:
before the first production start
after every code update
before you restart the
systemdservice
A typical update flow looks like this:
If Node.js comes from nvm, use the absolute npm path instead.
systemd process management
For production, keep Hexabot under systemd.
systemd restarts the app on failure and starts it again after a reboot.
Create the service file:
Add this configuration:
The [Install] section is required for systemctl enable hexabot.
That is what makes the service start automatically on reboot.
Enable and start the service:
Follow the service logs with:
Restart the service after each code update and rebuild:
NGINX reverse proxy
Create a dedicated site config:
And copy/paste the following nginx config:
This forwards both the admin UI and /api routes to the same Hexabot app.
Hexabot uses Socket.IO for real-time communication.
Keep the WebSocket upgrade headers in the location / block.
Do not add a rewrite rule for /api.
If you increase UPLOAD_MAX_SIZE_IN_BYTES, raise client_max_body_size to match.
Keep port 3000 reachable only from the local server.
Do not expose it publicly when NGINX is the reverse proxy.
Enable the site and remove the default site:
Validate and reload NGINX:
HTTPS setup
Request the certificate:
If you also use www, add it as another -d value.
Then verify renewal:
Finally, you can try and access https://mychatbot.ai in your browser.
Firewall setup
Allow only SSH, HTTP, and HTTPS from the public internet:
Do not open the Hexabot Node.js port, usually 3000, when NGINX is in front of the app.
Final verification checklist
Run these final checks:
Then confirm this checklist:
the production build completed successfully
.envusesNODE_ENV=production.envusesDB_SYNCHRONIZE=false.envusesAUDIT_LOG_SERVICE_ENVIRONMENT=productionSEED_ADMIN_PASSWORDhas been removed or clearedDB_HOST=127.0.0.1, notlocalhostUPLOAD_DIRpoints to a writable native server paththe
systemdservice usesnode dist/main, notnpm run startthe
systemdservice has an[Install]sectionsystemctl enable hexabothas been runNGINX includes WebSocket upgrade headers
the HTTPS certificate was issued successfully
UFW allows only SSH, HTTP, and HTTPS
port
3000is not publicly exposedyou can sign in to
https://mychatbot.ai
Troubleshooting
node dist/main fails with missing files
node dist/main fails with missing filesRun the build from the deployed project directory:
The production bundle must exist in dist/.
npm run build fails with a permissions error
npm run build fails with a permissions errorDo not build as your login user inside /opt/hexabot/my-project.
That directory is owned by hexabot.
Build it as hexabot instead:
PostgreSQL login fails even though the password is correct
Check DB_HOST.
Use 127.0.0.1, not localhost.
The systemd service fails, but the app starts in your shell
systemd service fails, but the app starts in your shellThe service often uses the wrong Node.js path.
Run which node and update ExecStart to that absolute path.
The browser loads the UI, but API requests fail
Do not rewrite /api.
Hexabot already serves the API under /api from the same app.
The first admin account was not created
The first admin is seeded only outside production mode.
Run the first private bootstrap with NODE_ENV=development, DB_SYNCHRONIZE=true, and SEED_ADMIN_* values set.
Then switch back to NODE_ENV=production and DB_SYNCHRONIZE=false.
Uploads fail through NGINX
Check both permissions and size limits.
The upload directory must be writable by the hexabot user.
Keep both limits aligned.
DNS or HTTPS does not work
Check DNS first:
Then recheck NGINX and Certbot:
Related pages
Last updated
Was this helpful?