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.

Prerequisites

  1. This project assumes that you have already followed our Raspberry Pi installation project, and have the resulting configured Raspberry Pi. If you are installing on different hardware or a different OS, these steps may not all be the same for you.

Install & Configure: Etherpad

Package installation

Before starting any work, you should always make sure you are fully up to date.

sudo apt update && sudo apt upgrade -y

Install Node.js

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt install -y nodejs

Run as a different user

sudo adduser --system --group --shell /bin/bash --home /opt/etherpad etherpad

Configure Etherpad Lite

  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.

  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"
        },
      

Create a systemd service

  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

Install & Configure: NGINX

Package installation

sudo apt install nginx

Configure NGINX

Basics

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;
          }
        

Our configuration

  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.

  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?

  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.

Self Signed Certificate

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.

Generate the cert

  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
    

Configure NGINX

  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;
      

Edit our NGINX configuration for Etherpad

  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

Recommended Next Steps

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