This guide covers setting up and managing multi-tenant Frappe deployments where multiple sites run on different domains from a single bench instance.
Understanding Multitenancy
Bench supports two modes of operation:
Port-based (Default)
- Each site runs on a different port
- Example:
site1.localhost:8001, site2.localhost:8002
- Suitable for development
- No DNS configuration required
DNS-based (Multitenancy)
- Each site runs on its own domain
- Example:
company1.com, company2.com
- Required for production SSL
- Requires DNS configuration
- All sites run on ports 80/443
DNS-based multitenancy is required for SSL certificates and production deployments.
Enabling DNS Multitenancy
bench config dns_multitenant on
This updates sites/common_site_config.json:
{
"dns_multitenant": true
}
Regenerate NGINX configuration
This creates NGINX configuration that routes requests based on domain name rather than port.
sudo bench setup reload-nginx
sudo systemctl reload nginx
Creating Multi-Tenant Sites
Point your domain(s) to your server:
# A record
company1.com A 203.0.113.10
company2.com A 203.0.113.10
# Or CNAME for subdomains
app.company1.com CNAME server.example.com
dig company1.com +short
nslookup company2.com
Create sites with domain names
bench new-site company1.com
bench new-site company2.com
Use the actual domain name as the site name. Bench will use this for routing.
bench --site company1.com install-app erpnext
bench --site company2.com install-app erpnext
Regenerate NGINX configuration
bench setup nginx
sudo bench setup reload-nginx
Managing Custom Domains
Add additional domains to existing sites:
bench setup add-domain custom.example.com --site company1.com
This adds the domain to sites/company1.com/site_config.json:
{
"domains": [
"custom.example.com"
]
}
bench setup add-domain secure.example.com --site company1.com \
--ssl-certificate /path/to/cert.pem \
--ssl-certificate-key /path/to/key.pem
bench setup remove-domain custom.example.com --site company1.com
bench setup sync-domains --site company1.com --domain domain1.com --domain domain2.com
Site Configuration for Multitenancy
Site config structure
Each site’s configuration in sites/[sitename]/site_config.json:
{
"db_name": "company1_db",
"db_password": "secretpassword",
"domains": [
"www.company1.com",
"app.company1.com",
{
"domain": "secure.company1.com",
"ssl_certificate": "/path/to/cert.pem",
"ssl_certificate_key": "/path/to/key.pem"
}
],
"host_name": "https://company1.com"
}
Common site config
sites/common_site_config.json for all sites:
{
"dns_multitenant": true,
"serve_default_site": true,
"restart_supervisor_on_update": true,
"webserver_port": 80,
"socketio_port": 9000
}
Wildcard SSL for Multitenancy
Use wildcard certificates to cover all subdomains:
Setup wildcard certificate
Configuration is added to common_site_config.json
{
"wildcard": {
"domain": "*.example.com",
"ssl_certificate": "/etc/letsencrypt/live/example.com/fullchain.pem",
"ssl_certificate_key": "/etc/letsencrypt/live/example.com/privkey.pem"
}
}
Create sites under wildcard domain
bench new-site app1.example.com
bench new-site app2.example.com
bench new-site app3.example.com
All sites automatically use the wildcard certificate.
NGINX Configuration Details
DNS multitenant NGINX structure
With DNS multitenancy, NGINX uses server name routing:
# Generated in config/nginx.conf
upstream frappe-bench-frappe {
server 127.0.0.1:8000 fail_timeout=0;
}
upstream frappe-bench-socketio {
server 127.0.0.1:9000 fail_timeout=0;
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name company1.com company2.com;
return 301 https://$host$request_uri;
}
# HTTPS server blocks
server {
listen 443 ssl http2;
server_name company1.com;
ssl_certificate /etc/letsencrypt/live/company1.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/company1.com/privkey.pem;
# Proxy to Frappe
location / {
proxy_pass http://frappe-bench-frappe;
}
}
Domain mapping
NGINX maps domains to site directories using X-Frappe-Site-Name header:
map $host $site_name_abc123 {
~^company1\.com$ company1.com;
~^company2\.com$ company2.com;
~^custom\.example\.com$ company1.com;
}
Managing Multiple Sites
List all sites
Or:
Run command on all sites
bench --site all migrate
bench --site all clear-cache
bench --site all backup
Site-specific commands
bench --site company1.com console
bench --site company2.com backup --with-files
bench --site company1.com migrate
Set default site
Then run commands without --site flag:
bench migrate
bench backup
Backup and Restore in Multi-Tenant
Backup individual sites
bench --site company1.com backup --with-files
bench --site company2.com backup --with-files
Backup all sites
Automated backups
Set up cron for all sites:
This creates backups for all sites in the bench.
Restore specific site
bench --site company1.com restore /path/to/backup.sql.gz
Worker configuration
Configure workers based on site load in sites/common_site_config.json:
{
"gunicorn_workers": 8,
"background_workers": 4
}
Rule of thumb: gunicorn_workers = (CPU cores * 2) + 1
Database connections
Each site has its own database. Monitor connection pool:
bench --site company1.com mariadb
SHOW PROCESSLIST;
SHOW STATUS LIKE 'Threads_connected';
Redis isolation
All sites share Redis instances by default. For isolation:
- Set up separate Redis instances per site (advanced)
- Configure in site_config.json:
{
"redis_cache": "redis://localhost:13001",
"redis_queue": "redis://localhost:11001"
}
NGINX caching
Enable caching for static assets in NGINX configuration (advanced).
Troubleshooting Multitenancy
Site not resolving
Issue: Domain doesn’t route to correct site
Solutions:
-
Verify DNS:
-
Check NGINX configuration:
sudo nginx -t
cat config/nginx.conf | grep server_name
-
Regenerate NGINX config:
bench setup nginx
sudo bench setup reload-nginx
-
Check site exists:
Wrong site loading
Issue: Different site loads than expected
Solution:
Check domain mapping in site_config.json:
cat sites/company1.com/site_config.json
Verify domains list and regenerate:
SSL certificate issues
Issue: SSL works for one domain but not others
Solution:
Verify each site has SSL configured:
sudo certbot certificates
Setup SSL for missing sites:
sudo bench setup lets-encrypt company2.com
Port conflicts
Issue: After enabling multitenancy, sites still use ports
Solution:
-
Ensure dns_multitenant is on:
cat sites/common_site_config.json | grep dns_multitenant
-
Remove port assignments from site configs:
# Edit sites/[sitename]/site_config.json
# Remove "nginx_port" key
-
Regenerate:
bench setup nginx
sudo bench setup reload-nginx
Default site issues
Issue: Wrong site loads when accessing by IP
Solution:
Configure default site in common_site_config.json:
{
"default_site": "company1.com",
"serve_default_site": true
}
Migration to Multitenancy
Migrating from port-based to DNS-based:
bench config dns_multitenant on
Update site names (if needed)
If sites are named like site1.local, you may need to rename:
Backup site
Create new site with domain name
Restore backup to new site
Delete old site
Add A/CNAME records for each site domain.
bench setup nginx
sudo bench setup reload-nginx
sudo bench setup lets-encrypt company1.com
sudo bench setup lets-encrypt company2.com
Verify all sites are accessible via their domains.
Best Practices
Name sites with their production domain:
bench new-site company.com # Good
bench new-site mysite # Avoid in production
Plan your domain structure
Main app: app.company.com
Admin panel: admin.company.com
API: api.company.com
Use wildcard certificates
Database connections
Memory for caching
Background workers
Scale infrastructure accordingly.
Maintain documentation of:
Site name → Domain mapping
SSL certificate assignments
Custom domain configurations
Automate backups for all sites:
Test multitenancy setup in staging first.
Multi-Tenant Architecture Example
SaaS scenario
Bench: production-bench
├── Site: client1.saas.com (ERPNext)
├── Site: client2.saas.com (ERPNext)
├── Site: client3.saas.com (ERPNext + Custom App)
└── Wildcard SSL: *.saas.com
Configuration:
- DNS multitenancy: enabled
- Wildcard SSL: *.saas.com
- Automated backups: daily
- Workers: 8 gunicorn, 4 background
Multi-company scenario
Bench: corporate-bench
├── Site: company-a.com (ERPNext + HRMS)
├── Site: company-b.com (ERPNext + HRMS)
├── Site: internal.company-a.com (Custom portal)
└── Individual SSL per domain
Configuration:
- DNS multitenancy: enabled
- Individual Let's Encrypt SSL
- Separate databases
- Shared apps, isolated data
Next Steps