While Docker offers a quick containerized setup for ERPNext, many production environments and developers prefer the "Bench" method. This "bare metal" approach gives you granular control over the services, easier debugging, and direct access to the configuration files.
This guide covers installing ERPNext Version 16 on Ubuntu 24.04 LTS using modern tools like uv for Python management and nvm for Node.js.
Before we begin, we need to install the core engines and build tools. We will also create a dedicated frappe user, as running ERPNext directly as root is a significant security risk.
Start by ensuring your package lists and installed packages are up to date.
sudo apt update && sudo apt upgrade -y
We need Git, Redis, MariaDB, Nginx, Supervisor, and various development headers.
sudo apt install -y git redis-server mariadb-server mariadb-client \ pkg-config libmariadb-dev gcc build-essential libssl-dev cron \ nginx supervisor python3-dev python3-setuptools python3-pip xvfb libfontconfig nano
ERPNext relies on wkhtmltopdf (with patched Qt) to generate PDFs. Since Ubuntu 24.04 has dropped support for this package and its dependencies (libssl1.1), we must manually install the library and the package.
# 1. Install libssl1.1 (Required dependency not in Noble repos) wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb # 2. Install wkhtmltopdf (Jammy Build) wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_amd64.deb sudo dpkg -i wkhtmltox_0.12.6.1-3.jammy_amd64.deb # 3. Fix any missing font/X11 dependencies sudo apt --fix-broken install -y
ERPNext requires a specific database format (Barracuda) and character set. Without this, the installation will fail.
Edit the configuration file:
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
Add the following configuration block under [mysqld]:
[mysqld] character-set-client-handshake = FALSE character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci [mysql] default-character-set = utf8mb4
Restart MariaDB to apply changes:
sudo systemctl restart mariadb
Run the security script to set a root password and remove insecure defaults.
sudo mariadb-secure-installation
Follow these responses:
We will create the frappe user, set a password, and prepare the directory. We will also add the www-data user (which Nginx runs as) to the frappe group, ensuring it can read the static assets securely without opening permissions to the whole world.
# 1. Create the user and set a password sudo useradd -m -s /bin/bash frappe sudo usermod -aG sudo frappe sudo passwd frappe # 2. Add www-data to the frappe group (For Nginx Access) sudo usermod -aG frappe www-data # 3. Create the directory and set ownership sudo mkdir -p /opt/frappe sudo chown -R frappe:frappe /opt/frappe # 4. Secure directory permissions # 750: User(rwx), Group(rx), Others(none). # This allows Nginx (in group) to read, but blocks others. sudo chmod 750 /opt/frappe # 5. Switch to the frappe user sudo su - frappe
Note: All subsequent steps should be run as the
frappeuser unless specified otherwise.
For a production environment, we recommend installing Node.js system-wide using the official NodeSource repository rather than nvm. This prevents path issues with Supervisor/Systemd.
# Download and setup the NodeSource repository curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - # Install Node.js sudo apt-get install -y nodejs # Install Yarn sudo npm install -g yarn
We use pure Python 3.14 for this Version 16 setup using uv.
# Install uv curl -LsSf https://astral.sh/uv/install.sh | sh source ~/.bashrc uv python install 3.14 --default
Install the bench command line tool using uv into a managed environment.
uv tool install frappe-bench --python python3.14
Now we initialize the Frappe Bench and install the ERPNext application.
cd /opt/frappe # 1. Initialize Bench bench init frappe-bench --frappe-branch version-16 --python python3.14 cd frappe-bench # 2. Download the ERPNext App bench get-app --branch version-16 erpnext # 3. Create a New Site # Replace 'erp.yourdomain.com' with your actual domain bench new-site erp.yourdomain.com --admin-password 'YourAdminPass' --mariadb-root-password 'YourDBRootPass' # 4. Install ERPNext on the site bench --site erp.yourdomain.com install-app erpnext # 5. Enable the Scheduler bench --site erp.yourdomain.com enable-scheduler
For a production environment, we use Nginx as a reverse proxy and Supervisor to manage the processes.
Enable DNS multitenancy so Bench uses domain names to serve sites.
bench config dns_multitenant on bench setup nginx bench setup supervisor
We need to link the generated configs to the system directories. Run these as a user with sudo access (or use sudo).
# Remove default Nginx site sudo rm /etc/nginx/sites-enabled/default # Link Nginx config sudo ln -s /opt/frappe/frappe-bench/config/nginx.conf /etc/nginx/conf.d/frappe.conf # Link Supervisor config sudo ln -s /opt/frappe/frappe-bench/config/supervisor.conf /etc/supervisor/conf.d/frappe.conf # Reload services sudo supervisorctl reread sudo supervisorctl update sudo systemctl restart nginx
Install Certbot and generate an SSL certificate for your domain.
# Install Certbot via Snap sudo snap install core && sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot # Run the Bench SSL setup command using the full path sudo /home/frappe/.local/bin/bench setup lets-encrypt erp.yourdomain.com
Because we are using a restricted directory (/opt/frappe), verify that the home directory permissions are applied correctly so Nginx (running as www-data) can read the assets.
# Ensure the group 'frappe' has execute (traverse) permissions on the home directory sudo chmod 750 /opt/frappe
Your ERPNext instance should now be accessible at https://erp.yourdomain.com.
1. Why use uv instead of standard pip?
uv is significantly faster and handles Python version management cleanly without interfering with the system Python system. It simplifies managing specific versions like Python 3.14.
2. I get a "Permission Denied" error on static files.
Ensure you ran the Phase 7 commands. Nginx runs as the www-data user, so it needs execute (traverse) permissions on the parent directories /opt and /opt/frappe to reach the static assets inside frappe-bench.
3. How do I update ERPNext? With bench, updating is straightforward:
cd /opt/frappe/frappe-bench bench update
This pulls the latest code, runs database migrations, and builds the assets.
About the Author
David Muraya is a Solutions Architect specializing in Python, FastAPI, and Cloud Infrastructure. He is passionate about building scalable, production-ready applications and sharing his knowledge with the developer community. You can connect with him on LinkedIn.
Related Blog Posts
Enjoyed this blog post? Check out these related posts!

How to Install ERPNext on Ubuntu with Docker
A Complete Guide to Deploying ERPNext with Docker Compose, Nginx, and SSL
Read More...

Add Client-Side Search to Your Reflex Blog with MiniSearch
How to Build Fast, Offline Search for Your Python Blog Using MiniSearch and Reflex
Read More...

Run Python Scripts for Free with GitHub Actions: A Complete Guide
Schedule and Automate Python Scripts Without Managing Servers or Cloud Bills
Read More...

Stop Running Python Scripts Manually: A Guide to Google Cloud Scheduler
A Step-by-Step Guide to Creating Cron Jobs for Your Cloud Functions.
Read More...
On this page
Back to Blogs
Contact Me
Have a project in mind? Send me an email at hello@davidmuraya.com and let's bring your ideas to life. I am always available for exciting discussions.