> For the complete documentation index, see [llms.txt](https://docs.hexabot.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hexabot.ai/faq/how-can-i-deploy-my-hexabot-project/manual-deployment.md).

# Manual Deployment

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?](/faq/how-can-i-deploy-my-hexabot-project.md).

### Prerequisites

You need:

* A Linux server with SSH access
* Node.js `>= 24.17.0`
* One package manager: `npm`, `pnpm`, `yarn`, or `bun`
* PostgreSQL
* NGINX
* Certbot
* A public domain that points to your server

Install the base packages first:

```bash
sudo apt-get update
sudo apt-get install -y nginx certbot python3-certbot-nginx postgresql postgresql-contrib
sudo systemctl enable --now nginx postgresql
```

Install 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:

* [Setup Node.js with NVM](/developer-guide/setup-node.js-with-nvm.md)
* [CLI Command Reference](/developer-guide/cli-command-reference.md)
* [SMTP Configuration](/developer-guide/smtp-configuration.md)

### Project setup

Install the CLI globally:

```bash
npm install -g @hexabot-ai/cli
```

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`:

```bash
git clone git@github.com:YOUR_ORG/my-project.git /home/ubuntu/my-project
cd /home/ubuntu/my-project
npm install
```

If the repository is private, use a deploy key, a machine user, or HTTPS with a token.

If `.env` is missing, create it once:

```bash
hexabot env init
```

`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:

```bash
hexabot check
hexabot config show
```

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`:

```bash
openssl rand -hex 32
```

Edit `/opt/hexabot/my-project/.env` and use production values like these:

| Variable                        | Example value                                           | Notes                                                  |
| ------------------------------- | ------------------------------------------------------- | ------------------------------------------------------ |
| `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.                    |

{% hint style="warning" %}
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.
{% endhint %}

{% hint style="warning" %}
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:

```bash
sudo mkdir -p /var/lib/hexabot/uploads
sudo chown ubuntu:ubuntu /var/lib/hexabot/uploads
```

{% endhint %}

Optional SMTP variables:

| Variable             | Example value                    | Notes                                 |
| -------------------- | -------------------------------- | ------------------------------------- |
| `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.                       |

{% hint style="info" %}
Hexabot reads SMTP settings at startup.

Restart the app after any SMTP change.
{% endhint %}

### PostgreSQL setup

PostgreSQL is the recommended database for production.

A typical PostgreSQL section in `.env` looks like this:

```env
DB_TYPE=postgres
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USERNAME=hexabot
DB_PASSWORD=change-me
DB_NAME=hexabot
DB_SCHEMA=public
# Optional:
# DB_URL=postgresql://hexabot:change-me@127.0.0.1:5432/hexabot
```

If PostgreSQL runs on the same server, create the Hexabot database and user:

```bash
sudo -u postgres psql
```

```sql
CREATE USER hexabot WITH PASSWORD 'change-me';
CREATE DATABASE hexabot OWNER hexabot;
\q
```

Use the same database name, user, and password in `.env`.

You can also point Hexabot at a managed PostgreSQL service.

{% hint style="info" %}
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.
{% endhint %}

### 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:

```env
NODE_ENV=development
DB_SYNCHRONIZE=true
DB_AUTO_MIGRATE=true
API_IS_PRIMARY_NODE=true
SEED_ADMIN_EMAIL=admin@example.com
SEED_ADMIN_PASSWORD=change-me-now
```

If your project uses admin seed names, you can also set them here:

```env
SEED_ADMIN_FIRST_NAME=Admin
SEED_ADMIN_LAST_NAME=User
```

Then start the app in dev mode once:

```bash
hexabot dev
```

Wait until the app finishes booting and the database is initialized.

Then verify that the admin user exists:

```bash
sudo -u postgres psql -d hexabot -c 'SELECT email, "firstName" FROM users;'
```

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:

```env
NODE_ENV=production
DB_SYNCHRONIZE=false
AUDIT_LOG_SERVICE_ENVIRONMENT=production
HTTPS_ENABLED=true
SEED_ADMIN_EMAIL=
SEED_ADMIN_PASSWORD=
```

Keep `DB_AUTO_MIGRATE=true` and `API_IS_PRIMARY_NODE=true` on a single-server deployment.

{% hint style="warning" %}
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.
{% endhint %}

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:

```bash
npm run build
```

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 `systemd` service

A typical update flow looks like this:

```bash
npm install
npm run build
sudo systemctl restart hexabot
```

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:

```bash
sudo nano /etc/systemd/system/hexabot.service
```

Add this configuration:

{% code title="/etc/systemd/system/hexabot.service" %}

```ini
[Unit]
Description=Hexabot
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/my-project
EnvironmentFile=/home/ubuntu/my-project/.env
ExecStart=/home/ubuntu/.nvm/versions/node/v24.17.0/bin/node dist/main
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
```

{% endcode %}

The `[Install]` section is required for `systemctl enable hexabot`.

That is what makes the service start automatically on reboot.

Enable and start the service:

```bash
sudo systemctl daemon-reload
sudo systemctl enable hexabot
sudo systemctl start hexabot
sudo systemctl status hexabot
```

Follow the service logs with:

```bash
journalctl -u hexabot -f
```

Restart the service after each code update and rebuild:

```bash
sudo systemctl restart hexabot
```

### NGINX reverse proxy

Create a dedicated site config:

```bash
sudo nano /etc/nginx/sites-available/hexabot
```

And copy/paste the following nginx config:

{% code title="/etc/nginx/sites-available/hexabot" %}

```nginx
server {
    listen 80;
    server_name mychatbot.ai;
    client_max_body_size 50M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        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;
    }
}
```

{% endcode %}

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:

```bash
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf /etc/nginx/sites-available/hexabot /etc/nginx/sites-enabled/hexabot
```

Validate and reload NGINX:

```bash
sudo nginx -t
sudo systemctl reload nginx
```

### HTTPS setup

Request the certificate:

```bash
sudo certbot --nginx -d mychatbot.ai --non-interactive --agree-tos -m your@email.com
```

If you also use `www`, add it as another `-d` value.

Then verify renewal:

```bash
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
```

Finally, you can try and access `https://mychatbot.ai`  in your browser.

### Firewall setup

Allow only SSH, HTTP, and HTTPS from the public internet:

```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
```

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:

```bash
curl http://127.0.0.1:3000/api/health
sudo systemctl status hexabot
sudo systemctl status nginx
sudo systemctl status postgresql
sudo nginx -t
dig mychatbot.ai
```

Then confirm this checklist:

* the production build completed successfully
* `.env` uses `NODE_ENV=production`
* `.env` uses `DB_SYNCHRONIZE=false`
* `.env` uses `AUDIT_LOG_SERVICE_ENVIRONMENT=production`
* `SEED_ADMIN_PASSWORD` has been removed or cleared
* `DB_HOST=127.0.0.1`, not `localhost`
* `UPLOAD_DIR` points to a writable native server path
* the `systemd` service uses `node dist/main`, not `npm run start`
* the `systemd` service has an `[Install]` section
* `systemctl enable hexabot` has been run
* NGINX includes WebSocket upgrade headers
* the HTTPS certificate was issued successfully
* UFW allows only SSH, HTTP, and HTTPS
* port `3000` is not publicly exposed
* you can sign in to `https://mychatbot.ai`

### Troubleshooting

#### `node dist/main` fails with missing files

Run the build from the deployed project directory:

```bash
cd /opt/hexabot/my-project
sudo -u hexabot -- npm run build
```

The production bundle must exist in `dist/`.

#### `npm run build` fails with a permissions error

Do not build as your login user inside `/opt/hexabot/my-project`.

That directory is owned by `hexabot`.

Build it as `hexabot` instead:

```bash
cd /opt/hexabot/my-project
sudo -u hexabot -- npm run build
```

#### 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

The 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:

```bash
dig mychatbot.ai
```

Then recheck NGINX and Certbot:

```bash
sudo nginx -t
sudo systemctl status nginx
```

### Related pages

* [How can I deploy my Hexabot project?](/faq/how-can-i-deploy-my-hexabot-project.md)
* [Docker Deployment](/faq/how-can-i-deploy-my-hexabot-project/docker-deployment.md)
* [Setup Node.js with NVM](/developer-guide/setup-node.js-with-nvm.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hexabot.ai/faq/how-can-i-deploy-my-hexabot-project/manual-deployment.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
