
Your team spends hours fighting with Google Docs version conflicts and data privacy concerns. You need a self-hosted collaborative editor that gives you full control over sensitive documents while enabling real-time editing. Installing Etherpad on Ubuntu 26.04 gives you exactly that: a secure, open-source alternative to closed-source cloud editors.
Etherpad is a web-based real-time collaborative text editor where multiple users can edit documents simultaneously with color-coded authorship tracking. Unlike proprietary solutions, you own the data completely and can integrate it with your existing LDAP or SSO systems. This guide walks you through installing Etherpad on Ubuntu 26.04 LTS with production-grade security including MariaDB persistence, Nginx reverse proxy, and Let’s Encrypt SSL encryption.
By the end of this tutorial, you will have a fully functional collaborative editor accessible via HTTPS, running as a managed systemd service that survives reboots and automatically recovers from crashes. The entire setup takes 25 to 35 minutes on a fresh Ubuntu 26.04 server.
Prerequisites
Before you begin this Linux server tutorial, make sure you have the following requirements met. Skipping any of these will cause installation failures or security vulnerabilities.
- Operating System: Fresh installation of Ubuntu 26.04 LTS (64-bit) with minimal packages
- User Account: A non-root user with sudo privileges (WHY: running commands as root on a production server exposes you to privilege escalation attacks)
- Hardware Minimums: 1 vCPU, 1 GB RAM, and 20 GB SSD storage (WHY: Etherpad’s Node.js runtime stays memory-resident; under 512 MB RAM causes instability when multiple users edit simultaneously)
- Domain Name: A domain pointed to your server’s public IP address via A record (WHY: Let’s Encrypt requires a valid domain for SSL certificate issuance; IP-only HTTPS is impractical for browser trust)
- Command Line Skills: Basic familiarity with terminal navigation, file editing with nano or vim, and understanding systemd service management
- Internet Connection: Stable connectivity to download Node.js packages and Git repositories (WHY: the initial dependency download takes 2 to 5 minutes depending on your bandwidth)
Verify your Ubuntu version before continuing by running lsb_release -a. You should see Ubuntu 26.04 in the output. If you are running 24.04 or 22.04, the commands will still work but you miss out on the latest kernel security patches and updated Node.js repository support that 26.04 provides.
Step 1: Update Your System and Install Core Dependencies
WHY System Updates Matter Before Installation
Fresh Ubuntu installations often have outdated package lists and unpatched security vulnerabilities. Running commands on an unpatched system before opening new ports creates avoidable attack surfaces. You update the system first to close these gaps before introducing new services.
Execute the Update and Install Commands
Open your terminal and run the following command to refresh package lists and apply all available security patches:
sudo apt update && sudo apt upgrade -y
This command performs two actions in sequence. The apt update part downloads the latest package index from Ubuntu repositories. The apt upgrade -y part installs all available security updates and bug fixes without asking for confirmation. The -y flag automatically answers yes to prompts so the process does not hang waiting for your input.
Expected output shows lines like:
Reading package lists... Done
Building dependency tree... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
If upgrades were applied, you will see a count of upgraded packages. Now install all core dependencies required for Etherpad:
sudo apt install mariadb-server nginx nodejs npm gzip git curl python3 libssl-dev build-essential -y
WHY Each Dependency Is Necessary
mariadb-server: Etherpad’s default “dirty” database stores data in a single flat file with no transaction support. Under concurrent edits from multiple users, this flat-file approach risks data corruption. MariaDB provides ACID-compliant persistence, handles concurrent writes safely, and supports the utf8mb4 charset required for emoji and multilingual pad content.
nodejs and npm: Etherpad is written entirely in Node.js. The runtime environment must be present before the application can execute. Ubuntu 26.04’s repositories include Node.js 20.x LTS, which meets Etherpad’s minimum requirement of Node.js 18.x or higher.
git: Etherpad is distributed via GitHub. Cloning from source gives you the latest master branch and makes future updates a single git pull command instead of downloading zip files manually.
libssl-dev and build-essential: Native Node.js modules handling cryptography and compression require compiled C/C++ bindings. Without these development headers, the pnpm install step will fail mid-build with cryptic compiler errors.
gzip, curl, python3: These support utilities handle compression, HTTP requests during package installation, and build scripts that Python-dependent modules require.
Verify your Node.js version meets requirements:
node -v
npm -v
You should see output like v20.11.0 for Node.js and 10.2.4 for npm. If the version is lower than 18.x, you must add the NodeSource repository before continuing.
Step 2: Secure and Configure MariaDB Database
WHY You Cannot Skip Database Hardening
A fresh MariaDB installation has no root password, anonymous users enabled, and a test database exposed to anyone who can connect. These defaults are convenient for development but disastrous for production. An unsecured database allows attackers to read, modify, or delete all your collaboration data.
Run the MariaDB Security Script
Execute the interactive security script to harden your database:
sudo mariadb-secure-installation
You will see a series of prompts. Answer as follows:
- Set root password? Enter
Yand create a strong password (WHY: prevents unauthorized administrative access) - Remove anonymous users? Enter
Y(WHY: anonymous accounts are unnecessary and dangerous) - Disallow root login remotely? Enter
Y(WHY: root should only connect from localhost) - Remove test database? Enter
Y(WHY: the test database is an easy attack vector) - Reload privilege tables? Enter
Y(WHY: applies all changes immediately without restart)
Create the Etherpad Database and User
Log into MariaDB as root:
sudo mariadb -u root -p
Enter your root password when prompted. You will see the MariaDB monitor prompt MariaDB [(none)]>. Execute these SQL commands one at a time:
CREATE DATABASE etherpad_db;
This creates a dedicated database named etherpad_db for Etherpad data isolation.
CREATE USER etherpad@localhost IDENTIFIED BY 'YourStrongPassword123!';
Replace YourStrongPassword123! with a password of your choice. Use at least 16 characters with uppercase, lowercase, numbers, and symbols. This creates a dedicated database user with limited scope.
GRANT CREATE,ALTER,SELECT,INSERT,UPDATE,DELETE ON etherpad_db.* TO etherpad@localhost;
This grants specific permissions on etherpad_db only. Notice you do NOT use ALL PRIVILEGES. The principle of least privilege means Etherpad gets only the permissions it needs to create tables, modify schema, and read/write data. It cannot drop the database or manage other users.
FLUSH PRIVILEGES;
MariaDB caches privilege tables in memory. Without this command, newly granted permissions remain inactive until the server restarts.
SHOW GRANTS FOR etherpad@localhost;
This verifies your grants are correct. You should see output listing only the permissions you just granted.
EXIT;
Exit the MariaDB monitor. Remember your database password securely. You will need it in Step 5 when configuring settings.json.
Step 3: Create a Dedicated System User for Etherpad
WHY Running as Root Is Dangerous
If you run Etherpad as the root user and a remote code execution vulnerability exists in any plugin or Node.js dependency, an attacker gains full root access to your entire server. A dedicated system user is jailed to its home directory with no login shell, limiting the blast radius of any compromise.
Create the Etherpad System Account
Execute the following command to create a non-privileged system user:
sudo adduser --system --no-create-home --home=/opt/etherpad-lite --group etherpad
Breakdown of flags:
- –system: Creates a system user with UID below 1000, no password, and no login capability. System users exist purely to own processes and files, not for interactive login.
- –no-create-home: Does not create
/home/etherpad. Instead, the home directory will be/opt/etherpad-litewhere the application lives. - –home=/opt/etherpad-lite: Sets the home directory to the installation path.
- –group etherpad: Creates a matching group named
etherpadso file permissions can be set precisely withchown.
Verify the user was created:
id etherpad
Expected output:
uid=994(etherpad) gid=994(etherpad) groups=994(etherpad)
This confirms the user exists with a system-level UID.
Step 4: Install pnpm and Clone Etherpad from GitHub
WHY pnpm Replaced npm in the Etherpad Project
The Etherpad project migrated from npm to pnpm as its official package manager. Attempting to install dependencies with standard npm will fail or produce an incomplete install. pnpm creates a more efficient disk layout using hard links and reduces dependency conflicts.
Install pnpm Globally
Run this command to install pnpm:
npm install pnpm -g
The -g flag installs pnpm globally so it is available system-wide. Verify installation:
pnpm -v
You should see a version like 8.15.0 or higher.
Clone Etherpad Repository
Navigate to /opt and clone the repository:
cd /opt && sudo git clone --branch master https://github.com/ether/etherpad-lite.git
The --branch master flag ensures you clone the latest stable release. Cloning without specifying a branch may land you on a development branch with unstable code. The repository downloads to /opt/etherpad-lite.
Fix File Permissions
The git clone ran as root, making all files root-owned. Etherpad’s systemd service runs as the etherpad user and will throw permission errors if it cannot write to its own directory. Fix ownership:
sudo chown -R etherpad:etherpad /opt/etherpad-lite
The -R flag applies recursively to all subdirectories and files.
Initialize Dependencies
Switch to the etherpad user and run the installation script:
cd /opt/etherpad-lite
sudo su -s /bin/bash -c "./bin/run.sh" etherpad
This command does three things:
- Downloads all Node.js dependencies via pnpm (takes 2-5 minutes)
- Validates the environment and checks for missing dependencies
- Generates a default
settings.jsonif one does not exist
You will see output like:
Installing dependencies...
[..................] / fetchMetadata: sill resolveWithNewModule ep_etherpad-lite@1.9.8
Wait until you see “Etherpad is running” or “Server is listening on port 9001”. Press Ctrl+C to stop the server. You still need to configure the database connection before running in production.
Step 5: Configure Etherpad Settings (settings.json)
WHY Configuration Hardening Is Critical
The default settings.json binds Etherpad to all network interfaces and uses the insecure flat-file database. Leaving these defaults exposes your editor to the internet without encryption and risks data loss under load.
Open the Configuration File
Edit the settings file:
nano /opt/etherpad-lite/settings.json
Modify the IP Binding
Find the "ip" setting and change it to:
"ip": "127.0.0.1",
WHY binding to localhost: This keeps Etherpad inaccessible from the internet directly. All public traffic will go through Nginx (set up in Step 7), which provides rate limiting, security headers, and SSL termination. Exposing port 9001 directly bypasses all these protections.
Configure MariaDB Connection
Locate the "dbType" and "dbSettings" sections. Replace them with:
"dbType" : "mysql",
"dbSettings" : {
"user": "etherpad",
"host": "127.0.0.1",
"port": 3306,
"password": "YourStrongPassword123!",
"database": "etherpad_db",
"charset": "utf8mb4"
}
WHY each setting matters:
"dbType": "mysql": Selects MariaDB/MySQL instead of the default “dirty” flat file"host": "127.0.0.1": Uses IPv4 localhost. Do not use “localhost” because it may resolve to IPv6::1 and cause connection refused errors"charset": "utf8mb4": Standard MySQL “utf8” only supports 3-byte characters. utf8mb4 supports full 4-byte Unicode including emoji and CJK characters
Enter the exact database password you created in Step 2.
Enable Proxy Trust
Find "trustProxy" and set it to:
"trustProxy": true,
WHY: When Nginx passes requests to Etherpad, the real client IP is in the X-Forwarded-For header. Without trustProxy: true, Etherpad logs 127.0.0.1 for every connection, making access logs useless for security auditing.
Set Admin Credentials
Locate or add the "users" block and add an admin account:
"users": {
"admin": {
"password": "AdminPassword456!",
"isAdmin": true
}
}
WHY: Without an admin account, the /admin panel (plugin manager, live settings editor) is inaccessible. Use a strong password different from your database password.
Save the file with Ctrl+O, press Enter, then exit with Ctrl+X.
Step 6: Create a systemd Service for Etherpad
WHY You Need a Managed Service
Running Etherpad manually in a terminal means it stops when you close the session. A systemd service runs in the background, starts automatically on boot, and restarts automatically if it crashes. This is essential for production reliability.
Create the Service File
Create a new systemd unit file:
sudo nano /etc/systemd/system/etherpad.service
Paste the following configuration:
[Unit]
Description=Etherpad-lite, the collaborative editor.
After=syslog.target network.target mariadb.service
[Service]
Type=simple
User=etherpad
Group=etherpad
WorkingDirectory=/opt/etherpad-lite
ExecStart=/usr/local/bin/pnpm run prod
Restart=always
[Install]
WantedBy=multi-user.target
WHY Each Directive Matters
After=mariadb.service: Systemd starts services in parallel by default. Without this directive, Etherpad attempts to connect to MariaDB before the database is ready, causing startup failure that requires manual intervention.
User=etherpad and Group=etherpad: Runs the process under the non-privileged system user created in Step 3 instead of root.
WorkingDirectory=/opt/etherpad-lite: Sets the application’s working directory so relative paths in code resolve correctly.
ExecStart=/usr/local/bin/pnpm run prod: The prod script runs Etherpad with production optimizations and without the development file watcher, reducing memory overhead.
Restart=always: Node.js processes can crash due to unhandled promise rejections or memory pressure. This directive ensures automatic recovery without admin intervention.
Reload systemd and Start the Service
Reload the systemd daemon to recognize the new service:
sudo systemctl daemon-reload
Start the Etherpad service:
sudo systemctl start etherpad
Enable the service to start on boot:
sudo systemctl enable etherpad
Check the service status:
sudo systemctl status etherpad
Expected output:
● etherpad.service - Etherpad-lite, the collaborative editor.
Loaded: loaded (/etc/systemd/system/etherpad.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-05-30 13:15:22 WIB
Verify the application is listening on port 9001:
ss -tulpn | grep 9001
Expected output:
LISTEN 0 128 127.0.0.1:9001 0.0.0.0:* users:(("node",pid=1234,fd=18))
If you see active (failed) or no output from ss, check logs with sudo journalctl -u etherpad -f to diagnose the issue.
Step 7: Configure Nginx as a Reverse Proxy
WHY You Cannot Skip the Reverse Proxy Layer
Nginx provides SSL termination, security headers, rate limiting, and WebSocket support that Etherpad cannot handle on its own. Directly exposing port 9001 bypasses all these protections and leaves you vulnerable to man-in-the-middle attacks.
Create the Nginx Site Configuration
Create a new site configuration file:
sudo nano /etc/nginx/sites-available/etherpad.conf
Paste the following configuration:
server {
listen 80;
server_name your-domain.com;
access_log /var/log/nginx/etherpad.access.log;
error_log /var/log/nginx/etherpad.error.log;
location / {
proxy_pass http://127.0.0.1:9001;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Replace your-domain.com with your actual domain name from the prerequisites.
WHY Each Nginx Directive Is Necessary
proxy_pass http://127.0.0.1:9001: Forwards all requests to Etherpad running on localhost port 9001.
proxy_buffering off: Etherpad uses WebSockets and Server-Sent Events for real-time sync. Buffering breaks the persistent connection, causing pads to appear frozen or fail to sync entirely.
proxy_set_header Upgrade and Connection “upgrade”: These two headers are mandatory for WebSocket protocol upgrades through Nginx. Without them, the browser’s WebSocket handshake is rejected and collaborative editing fails.
proxy_http_version 1.1: WebSocket connections require HTTP/1.1. The default Nginx proxy uses HTTP/1.0, which does not support the Upgrade header mechanism.
proxy_set_header X-Real-IP and X-Forwarded-For: Passes the real client IP to Etherpad for accurate logging. Without these headers, Etherpad logs 127.0.0.1 for every connection even though the actual user is elsewhere.
Enable the Site and Test Configuration
Create a symbolic link to enable the site:
sudo ln -s /etc/nginx/sites-available/etherpad.conf /etc/nginx/sites-enabled/
Test the Nginx configuration for syntax errors:
sudo nginx -t
Expected output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If you see “syntax is failed”, check for missing semicolons or typos. Restart Nginx:
sudo systemctl restart nginx
Step 8: Secure Etherpad with SSL Using Let’s Encrypt
WHY HTTPS Is Non-Negotiable for Collaborative Editors
Etherpad transmits every keystroke in real time over WebSockets. Without TLS encryption, an attacker on the same network can read every character typed by every collaborator through a man-in-the-middle attack. Additionally, modern browsers flag unencrypted sites as “Not Secure” which destroys user trust.
Install Certbot and the Nginx Plugin
Install the Certbot client:
sudo apt install certbot python3-certbot-nginx -y
Certbot is the official Let’s Encrypt client. The python3-certbot-nginx plugin allows automatic Nginx configuration updates.
Obtain and Install the SSL Certificate
Run Certbot with Nginx plugin:
sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email admin@your-domain.com -d your-domain.com
Replace admin@your-domain.com with your actual email and your-domain.com with your domain.
WHY Each Flag Matters
–nginx: Uses the Nginx plugin to automatically update your Nginx configuration with SSL settings.
–agree-tos: Automatically agrees to Let’s Encrypt’s Terms of Service so the process does not hang waiting for input.
–redirect: Automatically rewrites HTTP requests to HTTPS. Without this flag, users who type http:// get an unencrypted session even with the certificate installed.
–hsts: Adds the HTTP Strict Transport Security header instructing browsers to always use HTTPS for your domain, even if the user manually types http://. This prevents SSL stripping attacks.
–staple-ocsp: Attaches the certificate revocation status directly to the TLS handshake, reducing connection latency and third-party privacy exposure to certificate authorities.
Certbot will ask if you want to share your email with the EFF. Enter Y or N based on your preference. After successful validation, you will see:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your-domain.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/your-domain.com/privkey.pem
Reload Nginx to apply the new configuration:
sudo systemctl reload nginx
Verify SSL is working by visiting https://your-domain.com in your browser. You should see the padlock icon with a valid certificate.
Step 9: Configure UFW Firewall to Close Unnecessary Ports
WHY Firewall Configuration Is Critical After Opening Services
Every open port is a potential attack vector. By default, Ubuntu allows all incoming connections. You must explicitly allow only the ports you need and deny everything else.
Allow SSH Before Enabling UFW
Allow SSH first:
sudo ufw allow OpenSSH
WHY: Enabling UFW without explicitly allowing SSH locks you out of your own server. Always add SSH before enabling the firewall.
Allow HTTP and HTTPS Traffic
Allow web traffic:
sudo ufw allow 'Nginx Full'
WHY: The Nginx Full profile allows both ports 80 and 443. Using Nginx HTTP only would block HTTPS traffic after SSL is configured.
Do NOT Open Port 9001
Notice you do NOT run ufw allow 9001. Port 9001 should remain closed to the internet. Etherpad is only accessible through Nginx, which provides your security headers, rate limiting, and SSL layer. Direct exposure bypasses all these protections.
Enable the Firewall
Enable UFW:
sudo ufw enable
Type y when prompted. This activates the firewall rules immediately.
Check status:
sudo ufw status
Expected output:
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
Step 10: Access and Test Your Etherpad Installation
Verify the Full Stack Works End-to-End
Open https://your-domain.com in your browser. You should see the Etherpad homepage with a “Create new pad” button.

Test Real-Time Collaboration
Create a new pad by entering a name like test-pad and clicking “OK”. The editor loads with a unique URL. Open the same URL in a second browser tab or another device. Both cursors should appear with different colors, and typing in one tab immediately appears in the other.
Access the Admin Panel
Navigate to https://your-domain.com/admin and log in with the admin credentials you set in settings.json. You should see the plugin manager and live settings editor.
Verify Service Health
Check that the service is running:
sudo systemctl status etherpad
You should see active (running) with no error lines.
Monitor Nginx Logs for Issues
If anything is wrong, check the logs:
sudo tail -f /var/log/nginx/etherpad.error.log
Watch for 502 errors or WebSocket handshake failures while you test.
Troubleshooting Common Issues
This section covers the five most common problems I have encountered during production deployments. Each entry includes the problem, likely cause, and exact fix.
Problem 1: 502 Bad Gateway Error in Browser
Likely Cause: Etherpad is not running on port 9001 or the service crashed.
Fix: Check the service status and port listening:
sudo systemctl status etherpad
ss -tulpn | grep 9001
If the service is inactive or failed, check logs:
sudo journalctl -u etherpad -f
Common issues include wrong database credentials in settings.json or missing Node.js dependencies.
Problem 2: Pads Not Syncing in Real Time
Likely Cause: WebSocket headers are missing in the Nginx configuration.
Fix: Open /etc/nginx/sites-available/etherpad.conf and verify these two lines exist:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
If missing, add them inside the location / block. Save the file and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Problem 3: pnpm: command not found
Likely Cause: pnpm was not installed globally or the PATH is incorrect.
Fix: Reinstall pnpm and verify:
npm install pnpm -g
pnpm -v
If the version still does not show, check your PATH:
echo $PATH
It should include /usr/local/bin. If not, add it to your shell profile.
Problem 4: MariaDB Connection Refused
Likely Cause: Wrong host in settings.json or MariaDB service is down.
Fix: Use 127.0.0.1 not localhost for the database host. The string localhost may resolve to IPv6 ::1 while MariaDB is listening on IPv4 only. Also verify MariaDB is running:
sudo systemctl status mariadb
Problem 5: SSL Certificate Fails Validation
Likely Cause: Domain is not pointing to your server IP or port 80 is blocked.
Fix: Verify DNS propagation:
dig your-domain.com
The output should show your server’s public IP. If it shows a different IP or NXDOMAIN, update your DNS A record and wait up to 24 hours for propagation. Also ensure UFW allows port 80:
sudo ufw status | grep 80
Certbot needs port 80 open for HTTP-01 challenge validation.
[su_box title=”VPS Manage Service Offer” style=”bubbles” box_color=”#000000″ radius=”10″]If you don’t have time to do all of this stuff, or if this is not your area of expertise, we offer a service to do “VPS Manage Service Offer”, starting from $10 (Paypal payment). Please contact us to get the best deal![/su_box]