Skip to content
New Officers starting Fall 2026: Jason Pang (President) Anson Cook (Treasurer)

NGINX (w/ Etherpad)

The purpose of this project will be to make a web server. But of course, we need something to serve… on the web. So, for this project we are going to be using NGINX GitHub Org’s stars to self host Etherpad GitHub Repo stars.

{% include projects/prereq-pi.html %}

{% include projects/debian-start.html %}

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs
sudo adduser --system --group --shell /bin/bash --home /opt/etherpad etherpad
  1. Clone the GitHub branch:

    cd /opt/etherpad
    sudo git clone --branch master https://github.com/ether/etherpad-lite.git .

    Note: The dot (.) at the end of the git clone command is important - it will clone the files into the current directory, rather than into a subdirectory. {:.warning}

  2. Install dependencies:

    sudo ./bin/installDeps.sh
  3. Change the ownership of the cloned & installed files to the user you created:

    sudo chown -vR etherpad:etherpad /opt/etherpad/
  4. Create a log directory and change it’s ownership:

    sudo mkdir /var/log/etherpad
    sudo chown -vR etherpad:etherpad /var/log/etherpad/
  5. Edit settings.json:

    e.g.

    sudo vi settings.json
    • Update the line "trustProxy": false, to be "trustProxy": true,. This is necessary for later when we configure NGINX.
    • Take note of this section (even though we are not changing it). As the content indicates, by default Etherpad is going to run with a “dirty” database. This is only meant for testing purposes, and since we are testing, we are not going to change it, however you would usually want to configure a proper database at this point. But for now, moving on!
      "dbType": "dirty",
      "dbSettings": {
      "filename": "var/dirty.db"
      },
  1. Edit a new file /etc/systemd/system/etherpad.service.

    e.g.

    sudo vi /etc/systemd/system/etherpad.service
  2. Add the following content:

    [Unit]
    Description=Etherpad-lite, the collaborative editor.
    After=syslog.target network.target
    [Service]
    Type=simple
    User=etherpad
    Group=etherpad
    WorkingDirectory=/opt/etherpad
    Environment=NODE_ENV=production
    ExecStart=pnpm run prod
    Restart=always
    StandardOutput=append:/var/log/etherpad/etherpad.log
    StandardError=append:/var/log/etherpad/etherpad-error.log
    [Install]
    WantedBy=multi-user.target
  3. Reload to get the new configuration.

    sudo systemctl daemon-reload
  4. Enable the new service, so that it runs when the Pi starts.

    sudo systemctl enable etherpad
  5. Start the service

    sudo systemctl start etherpad
  6. Check its status (make sure it is active (running))

    sudo systemctl status etherpad
  7. Navigate to http://localhost:9001 in a web browser. You should now see Etherpad. (alternatively, from a different host on the same network, http://<ip-address>:9001)

    Etherpad http://ip-address:9001
sudo apt install nginx

Let’s talk about NGINX configuration generically for a moment.

  1. Server blocks
    server {
    ...
    }
    • A configuration file may contain 1 or more server blocks
    • Each server block is differentiated by which ports they listen on, and the configured server names.
    • NGINX decides which server with which to process the request, then tests the URI specified in the request’s header against the parameters of the location block inside the given server block. (see also: How nginx processes a request)
  2. Location blocks
    location <prefix> {
    ...
    }
    • A server block may contain 1 or more location blocks
    • The <prefix> is compared with the URI from the request
    • If there are several matching location blocks nginx selects from longest to shortest
      • The shortest prefix of length 1, /, will be selected if all other location blocks do not match.
    • Examples of what can go inside a location block:
      • root directive: the URI will be added to the root directive to form the path to the requested file on the local file system

        e.g. http://localhost/some/example.html -> /data/www/some/example.html

        location / {
        root /data/www;
        }
      • proxy server: pass on requests to the proxied servers & send received responses back to client

        e.g.

        location / {
        proxy_pass http://localhost:8080;
        }
  1. Edit a new NGINX configuration file at /etc/nginx/sites-available/etherpad.conf

    e.g.

    sudo vi /etc/nginx/sites-available/etherpad.conf
  2. Add the following content, replacing <DOMAIN> with your domain. Let’s use our <hostname>.local for the purposes of this project (e.g. druid.local). This will allow us to proceed without needing to do other configuration. (see also: Etherpad NGINX examples):

    server {
    listen 80;
    listen [::]:80;
    server_name <DOMAIN>;
    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_pass_header Server;
    # proxy headers
    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;
    # websocket proxying
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    }
    }

    Note: Port 80 instead of port 443. We aren’t doing HTTPS just yet. {:.info}

  3. Enable the configuration by creating a link:

    sudo ln -s /etc/nginx/sites-available/etherpad.conf /etc/nginx/sites-enabled/
  4. Check the configuration file syntax, and reload the NGINX service.

    sudo nginx -t
    sudo systemctl reload nginx
  5. Navigate to the domain that you configured in your web browser (e.g. druid.local). You should now again see Etherpad.

    Etherpad http://<hostname>.local

    Note: If it does not load in the web browser, did you set the domain to <hostname>.local? {:.warning}

  6. We can also test the collaboration aspect, by opening up the site on two devices, and editing a ‘pad’ of the same name. We can edit in both places, and see each others edits.

We can now see Etherpad at our <hostname>.local, but our browser doesn’t like it - its not secure. Let’s fix that (somewhat) by creating a quick self signed cert. For actual projects, you may want to configure a SSL cert with something like certbot.

  1. Generate a self signed cert:

    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /etc/ssl/private/nginx-selfsigned.key \
    -out /etc/ssl/certs/nginx-selfsigned.crt
  2. You will need to give it some values (replace with your values):

    e.g.

    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:US
    State or Province Name (full name) [Some-State]:Maryland
    Locality Name (eg, city) []:College Park
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:Homelab Club at UMD
    Organizational Unit Name (eg, section) []:
    Common Name (e.g. server FQDN or YOUR name) []:druid.local
    Email Address []:[email protected]
  3. Create a strong Diffie-Hellman (DH) group

    sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
  1. Point NGINX at our cert files - edit /etc/nginx/snippets/self-signed.conf:

    e.g.

    sudo vi /etc/nginx/snippets/self-signed.conf
    • Add the content:
      ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
      ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
  2. Edit SSL settings, /etc/nginx/snippets/ssl-params.conf

    e.g.

    sudo vi /etc/nginx/snippets/ssl-params.conf
    • Add content:
      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
      ssl_prefer_server_ciphers on;
      ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
      ssl_ecdh_curve secp384r1;
      ssl_session_cache shared:SSL:10m;
      ssl_session_tickets off;
      ssl_stapling on;
      ssl_stapling_verify on;
      resolver 8.8.8.8 8.8.4.4 valid=300s;
      resolver_timeout 5s;
      # Disable preloading HSTS for now. You can use the commented out header line that includes
      # the "preload" directive if you understand the implications.
      #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
      add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
      add_header X-Frame-Options DENY;
      add_header X-Content-Type-Options nosniff;
      ssl_dhparam /etc/ssl/certs/dhparam.pem;
  1. Edit the NGINX configuration file that we made previously (/etc/nginx/sites-available/etherpad.conf)

    e.g.

    sudo vi /etc/nginx/sites-available/etherpad.conf
  2. Redirect HTTP (port 80) requests to HTTPS. Indicate a temporary move (302 error/redirect). We can use 301 later after we make sure everything works.

    • Add the line return 302 https://$server_name$request_uri; after the server_name line, followed by a closing }. (replacing <DOMAIN> with your domain (e.g. druid.local))

      e.g.

      server {
      listen 80;
      listen [::]:80;
      server_name <DOMAIN>;
      return 302 https://$server_name$request_uri;
      }
      ...
  3. Start listening for HTTPS (port 443) (replacing <DOMAIN> with your domain (e.g. druid.local))

    server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name <DOMAIN>;
    include snippets/self-signed.conf;
    include snippets/ssl-params.conf;
    ...
  4. Add a fix for an Etherpad related error that I ran into. I do think that has to do with the “test” environment that we are creating.

    ...
    location / {
    ...
    add_header X-Frame-Options ALLOW always;
    ...
    }
    ...
    • Your file should now look something like (replacing <DOMAIN> with your domain (e.g. druid.local)):

      server {
      listen 80;
      listen [::]:80;
      server_name <DOMAIN>;
      return 302 https://$server_name$request_uri;
      }
      server {
      listen 443 ssl http2;
      listen [::]:443 ssl http2;
      server_name <DOMAIN>;
      include snippets/self-signed.conf;
      include snippets/ssl-params.conf;
      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_pass_header Server;
      # proxy headers
      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;
      # websocket proxying
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      add_header X-Frame-Options ALLOW always;
      }
      }
  5. Check the NGINX configuration

    sudo nginx -t

    The warning that looks like this is expected:

    labclub@druid:~ $ sudo nginx -t
    2025/04/07 00:53:45 [warn] 2000#2000: "ssl_stapling" ignored, issuer certificate not found for certificate "/etc/ssl/certs/nginx-selfsigned.crt"
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
    labclub@druid:~ $
  6. Restart NGINX

    sudo systemctl restart nginx
  7. Navigate to the domain that you configured in your web browser (e.g. druid.local). Regardless of if you go to http://, https://, or don’t specify, it should redirect you to https://. If promted, accept the risk & continue - we know its self signed. You should now again see Etherpad, but this time, with a cert! We can look at this cert’s info to see it is what we generated.

    Etherpad self signed cert Etherpad self signed cert info
  1. Configure a firewall (e.g. UFW)
  2. Configure a non-self signed cert (e.g. certbot)