Hanzo
CommerceDeployment

Self-Hosted

Deploy Hanzo Commerce on bare metal or a VPS with systemd and Nginx

Deploy Hanzo Commerce directly on a Linux server without container orchestration. This guide covers binary installation, systemd service configuration, Nginx reverse proxy, SSL with Let's Encrypt, and backup strategy.

Prerequisites

  • A Linux server (Ubuntu 22.04+ or Debian 12+ recommended)
  • Root or sudo access
  • A domain name pointing to your server's IP
  • PostgreSQL 15+ (local or managed)
  • Redis 7+ (local or managed)

System Requirements

ComponentMinimumRecommended
CPU2 cores4 cores
RAM4 GB8 GB
Disk20 GB SSD100 GB SSD
OSUbuntu 22.04 / Debian 12Ubuntu 24.04

Install Dependencies

# Update system
sudo apt update && sudo apt upgrade -y

# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# Install Redis
sudo apt install -y redis-server

# Install Nginx
sudo apt install -y nginx

# Install certbot for Let's Encrypt
sudo apt install -y certbot python3-certbot-nginx

Configure PostgreSQL

sudo -u postgres psql -c "CREATE USER commerce WITH PASSWORD 'your-secure-password';"
sudo -u postgres psql -c "CREATE DATABASE commerce OWNER commerce;"

Configure Redis

Edit /etc/redis/redis.conf:

maxmemory 256mb
maxmemory-policy allkeys-lru
appendonly yes

Restart Redis:

sudo systemctl restart redis-server

Install Commerce Binary

Download the latest release:

# Create commerce user
sudo useradd -r -m -d /opt/commerce -s /bin/false commerce

# Download binary
sudo curl -L -o /usr/local/bin/commerce \
  "https://github.com/hanzoai/commerce/releases/latest/download/commerce-linux-amd64"
sudo chmod +x /usr/local/bin/commerce

# Create data directory
sudo mkdir -p /opt/commerce/data
sudo chown -R commerce:commerce /opt/commerce

Verify the installation:

commerce version

Configuration

Create the configuration file:

# Server
COMMERCE_DIR=/opt/commerce/data
COMMERCE_ENV=production
COMMERCE_PORT=8090
COMMERCE_HOST=127.0.0.1
COMMERCE_LOG_LEVEL=info

# Database
DATABASE_URL=postgresql://commerce:your-secure-password@localhost:5432/commerce?sslmode=disable

# Cache
KV_URL=redis://localhost:6379/0

# Storage (local filesystem or S3)
S3_URL=file:///opt/commerce/data/uploads

# Search (optional -- install Meilisearch separately if needed)
# SEARCH_URL=http://localhost:7700

# Hanzo Platform
HANZO_API_KEY=hk_live_...
HANZO_CLIENT_ID=your_client_id
HANZO_CLIENT_SECRET=your_client_secret

# Payments
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Set permissions:

sudo chown commerce:commerce /opt/commerce/.env
sudo chmod 600 /opt/commerce/.env

systemd Service

Create the service file:

[Unit]
Description=Hanzo Commerce API
After=network.target postgresql.service redis-server.service
Requires=postgresql.service redis-server.service

[Service]
Type=simple
User=commerce
Group=commerce
WorkingDirectory=/opt/commerce
EnvironmentFile=/opt/commerce/.env
ExecStart=/usr/local/bin/commerce serve
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/commerce/data
PrivateTmp=true

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=commerce

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable commerce
sudo systemctl start commerce

Check status:

sudo systemctl status commerce
sudo journalctl -u commerce -f

Nginx Reverse Proxy

Create the Nginx configuration:

upstream commerce_backend {
    server 127.0.0.1:8090;
    keepalive 32;
}

server {
    listen 80;
    server_name commerce.example.com;

    # Redirect to HTTPS (certbot will modify this)
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name commerce.example.com;

    # SSL (certbot will fill these in)
    # ssl_certificate /etc/letsencrypt/live/commerce.example.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/commerce.example.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Request limits
    client_max_body_size 50M;

    # Proxy to Commerce
    location / {
        proxy_pass http://commerce_backend;
        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 "";

        # Timeouts
        proxy_connect_timeout 10s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }

    # Health check (no access log)
    location /health {
        proxy_pass http://commerce_backend;
        access_log off;
    }

    # Block direct access to metrics
    location /metrics {
        deny all;
        return 404;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/commerce /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL with Let's Encrypt

sudo certbot --nginx -d commerce.example.com

Certbot will:

  • Obtain a certificate
  • Update your Nginx config with SSL paths
  • Set up automatic renewal

Verify auto-renewal:

sudo certbot renew --dry-run

Firewall

Allow only HTTP, HTTPS, and SSH:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Backup Strategy

PostgreSQL Backups

Create a daily backup script:

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/opt/commerce/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

mkdir -p "$BACKUP_DIR"

# Dump database
pg_dump -U commerce -Fc commerce > "$BACKUP_DIR/commerce_$TIMESTAMP.dump"

# Remove old backups
find "$BACKUP_DIR" -name "*.dump" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: commerce_$TIMESTAMP.dump"
sudo chmod +x /opt/commerce/backup.sh
sudo chown commerce:commerce /opt/commerce/backup.sh

Add a cron job:

sudo crontab -u commerce -e
# Daily backup at 2:00 AM
0 2 * * * /opt/commerce/backup.sh >> /opt/commerce/backups/backup.log 2>&1

Restore from Backup

pg_restore -U commerce -d commerce --clean /opt/commerce/backups/commerce_20260216_020000.dump

Updating

# Download new binary
sudo curl -L -o /usr/local/bin/commerce \
  "https://github.com/hanzoai/commerce/releases/latest/download/commerce-linux-amd64"
sudo chmod +x /usr/local/bin/commerce

# Restart the service (migrations run automatically)
sudo systemctl restart commerce

# Verify
sudo systemctl status commerce
curl -s http://localhost:8090/health | python3 -m json.tool

Monitoring

Journal Logs

# Follow logs
sudo journalctl -u commerce -f

# Last hour
sudo journalctl -u commerce --since "1 hour ago"

# Errors only
sudo journalctl -u commerce -p err

Health Check Script

#!/bin/bash
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8090/health/ready)
if [ "$STATUS" != "200" ]; then
  echo "Commerce unhealthy (HTTP $STATUS)" | systemd-cat -t commerce-healthcheck -p err
  systemctl restart commerce
fi

Add to cron for every 5 minutes:

*/5 * * * * /opt/commerce/healthcheck.sh

Next Steps

  • Set up Docker if you prefer containerized deployments
  • Deploy your storefront to Vercel
  • Review architecture for scaling beyond a single server

How is this guide?

Last updated on

On this page