Multitenancy on Development environment

Hello, I’m pretty new to web development and Frappe, and I’m trying to grasp the concept of multi-tenancy better. I want to clarify if multi-tenancy is even possible in development mode.

I tried setting up 2 sites on a single bench (all still in development mode) although I’m using nginx outside of Bench to publish the site. I tried to setup a port-based multitenancy to run both sites by tinkering with the Procfile as shown below.

//other settings
web: bench serve --port 8000
web2: bench serve --port 8001
//other settings

When I ran bench start, it does run both ports and I’m able to access both domains. However, the problem is, both domains will point to the site listed in the currentsite.txt file, so the result is I have 2 domains pointing to 1 site because currentsite.txt only accepts 1 site (as far as I have tested).

What I wanted is to have 2 sites running simultaneously, and the 2 domains I have will each point to a different site (domain1 point to siteA, while domain2 point to siteB).

I have tried many solutions to work around this as I read that multi-tenancy expects sites to be ran on production mode, and neither bench serve nor bench start have an option to run a specific site.

So, what I want to clarify is:

  1. Is multi-tenancy only works for sites in production mode as it relies on bench’s nginx and supervisor?
  2. Is the only solution to this is to setup a new bench instance if I want to run both sites simultaneously on development mode?

I’ll appreciate it if anyone have any knowledge about this that you can share with me as I still have many holes in my understanding of Frappe that I’m trying to fill :blush:

You should create two sites with this command and the appriate options (e.g. your ports – or maybe not ports, after all, since you can differentiate the sites via your domain names):

so this will will create two site-config.json, one for each site, which you can then fine-tune if necessary.

Name the sites according to the domain they shall serve.

Hi @rp21:


bench use site1.local
bench serve --port 8001

Open other terminal and

bench use site2.local
bench serve --port 8002

This works but … what about 2 different benches?
Hope this helps.

Thank you, this solution works and I figured out some of the limitations of running 2 sites on the same bench when trying out your solution (the important one is I can’t seem to keep both sites running by sending the bench serve processes to the background)

I have now used 2 different benches to run 2 sites simultaneously as you suggested and it’s able to keep running when the processes is sent to the background

It could work with DNS based multi-tenancy. I’ve been doing it for a few years now. You do need nginx or other equivalent web servers for this though.

Just create *.localhost name servers in nginx config. Like erpnext1.localhost and erpnext2.localhost. With these, you don’t need to add etc host file mappings. *.localhost always resolve to You have to pass correct X-Frappe-Site-Name header so that frappe properly resolve to specific site. Here a sample config from my development setup.

upstream frappe-v14-frappe {
	server fail_timeout=0;
upstream frappe-v14-socketio-server {
	server fail_timeout=0;
server {
	listen 80;
	server_name erpnext14.localhost;

	root /home/frappe/frappe-v14/sites;

	proxy_buffer_size 128k;
	proxy_buffers 4 256k;
	proxy_busy_buffers_size 256k;

	add_header X-Frame-Options "SAMEORIGIN";
	add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
	add_header X-Content-Type-Options nosniff;
	add_header X-XSS-Protection "1; mode=block";
	add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin";

	location /assets {
		try_files $uri =404;

	location ~ ^/protected/(.*) {
		try_files /erpnext14.localhost/$1 =404;

	location / {
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header X-Frappe-Site-Name erpnext14.localhost;
		proxy_set_header Origin $scheme://$http_host;
		proxy_set_header Host $host;

		proxy_pass http://frappe-v14-socketio-server;

	location / {

 		rewrite ^(.+)/$ $1 permanent;
  		rewrite ^(.+)/index\.html$ $1 permanent;
  		rewrite ^(.+)\.html$ $1 permanent;

		location ~* ^/files/.*.(htm|html|svg|xml) {
			add_header Content-disposition "attachment";
			try_files /erpnext14.localhost/public/$uri @webserver;

		try_files /erpnext14.localhost/public/$uri @webserver;

	location @webserver {
		proxy_http_version 1.1;
		proxy_set_header X-Forwarded-For $remote_addr;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_set_header X-Frappe-Site-Name erpnext14.localhost;
		proxy_set_header Host $host;
		proxy_set_header X-Use-X-Accel-Redirect True;
		proxy_read_timeout 120;
		proxy_redirect off;

		proxy_pass  http://frappe-v14-frappe;

	# error pages
	error_page 502 /502.html;
	location /502.html {
		root /home/nayminlwin/.pyenv/versions/3.11.5/lib/python3.11/site-packages/bench/config/templates;

	access_log  /var/log/nginx/access.log;
	error_log  /var/log/nginx/error.log;

	# optimizations
	sendfile on;
	keepalive_timeout 15;
	client_max_body_size 50m;
	client_body_buffer_size 16K;
	client_header_buffer_size 1k;

	gzip on;
	gzip_http_version 1.1;
	gzip_comp_level 5;
	gzip_min_length 256;
	gzip_proxied any;
	gzip_vary on;
		# text/html is always compressed by HttpGzipModule