Twenty is an open-source CRM (a modern Salesforce/HubSpot alternative). The self-hosted stack is a Postgres database, Redis, and the Twenty server + worker containers, served through a single reverse-proxied entrypoint. It's a comparatively light stack and runs comfortably on a small VPS.
1. Prerequisites
- RamNode VPS on Ubuntu 24.04 LTS
- Minimum 4 GB RAM, 2 vCPUs, 25 GB disk for a small team; bump RAM if you'll have dozens of concurrent users
- DNS A record for the subdomain, e.g.
crm.yourdomain.com - Root/sudo SSH access
2. Base server setup
apt update && apt -y upgrade
apt -y install curl git ufw
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable3. Install Docker
curl -fsSL https://get.docker.com | sh
apt -y install docker-compose-plugin
systemctl enable --now docker4. Set up Twenty
mkdir -p /opt/twenty && cd /opt/twenty
curl -fsSL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/.env.example -o .envEdit .env:
PG_DATABASE_PASSWORD— strong unique passwordAPP_SECRET— generate withopenssl rand -base64 32SERVER_URL— set tohttps://crm.yourdomain.com(this must match the public URL exactly — Twenty uses it for auth callbacks, invite links, and file URLs)STORAGE_TYPE—localis fine for a single VPS; switch tos3and fill in the corresponding vars if you want uploads on external object storage- Mail settings (
EMAIL_FROM_ADDRESS,EMAIL_DRIVER, SMTP credentials) — required for invite emails and password resets to actually send
5. Start the stack
docker compose up -dThis starts Postgres, Redis, the server container (API + frontend), and a worker container (background jobs — emails, imports, workflow automation). Watch logs:
docker compose logs -f serverThe server listens on port 3000 by default.
6. nginx reverse proxy + TLS
apt -y install nginx certbot python3-certbot-nginx/etc/nginx/sites-available/twenty:
server {
listen 80;
server_name crm.yourdomain.com;
client_max_body_size 50M; # file/attachment uploads
location / {
proxy_pass http://127.0.0.1:3000;
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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # GraphQL subscriptions / real-time updates
}
}ln -s /etc/nginx/sites-available/twenty /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d crm.yourdomain.com7. First login
Visit https://crm.yourdomain.com and complete the workspace creation wizard — this creates the first admin user. From there, invite teammates via Settings → Members (requires working SMTP from step 4).
8. Backups
Only Postgres and, if using local storage, the uploads volume need regular backup:
docker exec twenty-db-1 pg_dump -U postgres default > /root/backups/twenty-pg-$(date +%F).sql
docker run --rm -v twenty_server-local-data:/data -v /root/backups:/backup alpine \
tar czf /backup/twenty-uploads-$(date +%F).tar.gz -C /data .Cron this nightly and copy backups off the VPS.
9. Updating
cd /opt/twenty
docker compose down
docker compose pull
docker compose up -dTwenty runs its own DB migrations automatically on server container startup — check the changelog before major version jumps, since CRM schema migrations can take a few minutes on larger datasets.
Troubleshooting notes
- Invite/reset emails never arrive → almost always an SMTP config issue in
.env; test with a known-good SMTP relay before assuming the app is broken. - Login redirects loop or fail after TLS setup → check
SERVER_URLmatches the actual public HTTPS URL exactly (scheme + host, no trailing slash mismatch). - Background jobs (imports, automations) not running → confirm the
workercontainer is up alongsideserver; they're separate containers and onlyserverbeing healthy doesn't mean jobs are processing.
