Standard Notes is an open-source, and completely encrypted notes app. It offers both free and paid plans and offers both, cloud hosting and the option to host it on your server. You can use your server to sync the notes stored between different devices. Standard Notes offers apps for all desktop operating systems and mobile platforms.

In this tutorial, you will learn how to self-host your standard notes server on a Ubuntu 22.04 machine. You will also learn how to activate paid plan features and file uploads on your self-hosted instance.

Prerequisites

  • A server running Ubuntu 22.04 with a minimum of 2 GB of RAM.

  • A non-root user with sudo privileges.

  • The Uncomplicated Firewall(UFW) is enabled and running.

  • A Fully Qualified domain name pointed to the server. For our tutorial, we will be using the domain standardnotes.example.com. You will need another domain name for your file server. We will be using the domain snotes-files.example.com.

  • Everything is updated.

    $ sudo apt update && sudo apt upgrade
    

Step 1 – Configure Firewall

The first step before installing any packages is to configure the firewall to allow HTTP and HTTPS connections.

Check the status of the firewall.

$ sudo ufw status

You should see something like the following.

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)

Allow HTTP and HTTPs ports.

$ sudo ufw allow http
$ sudo ufw allow https

Check the status again to confirm.

$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)

Step 2 – Install Docker and Docker Compose

Add Docker’s official GPG key.

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg

Run the following command to add the Docker repository.

$ echo 
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu 
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Update the system to include Docker’s repository.

$ sudo apt update

Install Docker.

$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

This tutorial will be using the Docker Compose v2 plugin instead of the older legacy binary. Therefore, the command for running it has changed from docker-compose to docker compose and this is reflected here.

Docker runs with elevated privileges so you will need to use sudo frequently to run commands. The better option is to add your Linux user account to the docker user group.

$ sudo usermod -aG docker ${USER}

The ${USER} variable picks up the currently logged-in system account. If you are not logged in with the user you want to give privileges to, replace ${USER} with the username.

To apply for the new group membership, log out of the server and back in, or use the following command. You will be prompted for the user’s password.

$ su - $(USER)

Step 3 – Install Nginx

Ubuntu 22.04 ships with an older version of Nginx. To install the latest version, you need to download the official Nginx repository.

Import Nginx’s signing key.

$ curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor 
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

Add the repository for Nginx’s stable version.

$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg arch=amd64] 
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" 
| sudo tee /etc/apt/sources.list.d/nginx.list

Update the system repositories.

$ sudo apt update

Install Nginx.

$ sudo apt install nginx

Verify the installation.

$ nginx -v
nginx version: nginx/1.22.0

Step 4 – Install SSL

We need to install Certbot to generate the SSL certificate. You can either install Certbot using Ubuntu’s repository or grab the latest version using the Snapd tool. We will be using the Snapd version.

Ubuntu 22.04 comes with Snapd installed by default. Run the following commands to ensure that your version of Snapd is up to date.

$ sudo snap install core

Install Certbot.

$ sudo snap install --classic certbot

Use the following command to ensure that the Certbot command can be run by creating a symbolic link to the /usr/bin directory.

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

Run the following command to generate an SSL Certificate.

$ sudo certbot certonly --standalone --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d standardnotes.example.com

The above command will download a certificate to the /etc/letsencrypt/live/standardnotes.example.com directory on your server.

We need to do the same for the Files subdomain.

$ sudo certbot certonly --standalone --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d snotes-files.example.com

Generate a Diffie-Hellman group certificate.

$ sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096

Open the file /etc/letsencrypt/renewal/standardnotes.example.com.conf for editing.

$ sudo nano /etc/letsencrypt/renewal/standardnotes.example.com.conf

Paste the following code at the bottom.

pre_hook = systemctl stop nginx
post_hook = systemctl start nginx

Save the file by pressing Ctrl X and entering Y when prompted.

Repeat the same step for the files subdomain by editing the /etc/letsencrypt/renewal/snotes-files.example.com.conf file.

We have generated the SSL certificate using the standalone option of Certbot. It runs its web server to create the certificate which means Nginx should be shut off during the renewal. The pre_hook and post_hook commands run before and after the renewal to automatically shut and restart the Nginx server thereby requiring no manual intervention.

To check whether the SSL renewal is working fine, do a dry run of the process.

$ sudo certbot renew --dry-run

If you see no errors, you are all set. Your certificate will renew automatically.

Step 5 – Download and Configure Standard Notes

Make sure you are in your system’s home directory.

$ cd ~

Clone the Standard Notes Standalone repository.

$ git clone --single-branch --branch main https://github.com/standardnotes/standalone.git

Switch to the downloaded directory.

$ cd standalone

Create the default configuration files for the server.

$ ./server.sh setup

This will create the default environment files which we need to configure. You need to generate six different secret keys. Use the following commands to generate them.

$ openssl rand -hex 32

First, we have to edit the file .env in the main folder. Open it for editing.

$ nano .env

Change the values of the following variables.

NODE_ENV=production
..
AUTH_JWT_SECRET=c0f5bcf6f0f0dcca5b9078c3095e4255a055dfd6376b376733af0e50483cc629
..
DB_USERNAME=std_notes_user
DB_PASSWORD=changeme123
..
VALET_TOKEN_SECRET=977c978ca1d5ea22fe2fda65058905b191f724e33db6e47d0a41e034a082cb3b
..
FILES_SERVER_URL=https://snotes-files.example.com

Save the file by pressing Ctrl X and entering Y when prompted.

Next, open the file docker/auth.env file.

$ nano  docker/auth.env

Change the values of the following variables.

JWT_SECRET=54deb1b0b2499e8d875b0d5266dabef9003e13c1787a959a94e339363c10e56e
LEGACY_JWT_SECRET=c36aae01803a616213f22422b6d3f998a2beb2cb53af8b95bf578a8a3d046cec
..
PSEUDO_KEY_PARAMS_KEY=ea09d3f9122b49c653524cd2285a45fee88beb94f9b76d4d25420b521b080fcd
..
ENCRYPTION_SERVER_KEY=04decf379fbe3bb48cf95dbb5997031418b308e724a25d88cb0b2ed6da725efe

Save the file by pressing Ctrl X and entering Y when prompted.

Step 6 – Start Standard Notes Server

Run the following command to start the server.

$ ./server.sh start

This command will take a few minutes to complete. During this time, the process will pull all the relevant Docker images and create containers for all the services. It will also populate the database and perform appropriate migrations.

You can check the logs for the process using the following command.

$ ./server.sh logs

Press Ctrl C to exit the logs. You can check the status of the running containers using the following command.

$ ./server.sh status

You will receive a similar output.

Services State:
NAME                                  COMMAND                  SERVICE                    STATUS              PORTS
api-gateway-standalone                "./wait-for.sh auth …"   api-gateway                running             0.0.0.0:3000->3000/tcp, :::3000->3000/tcp
auth-standalone                       "./wait-for.sh db 33…"   auth                       running
auth-worker-standalone                "./wait-for.sh db 33…"   auth-worker                running
cache-standalone                      "docker-entrypoint.s…"   cache                      running             6379/tcp
db-standalone                         "docker-entrypoint.s…"   db                         running             3306/tcp
files-standalone                      "./wait-for.sh db 33…"   files                      running             0.0.0.0:3125->3000/tcp, :::3125->3000/tcp
syncing-server-js-standalone          "./wait-for.sh db 33…"   syncing-server-js          running
syncing-server-js-worker-standalone   "./wait-for.sh db 33…"   syncing-server-js-worker   running

You can check the health of the server using the following command.

$ curl http://localhost:3000/healthcheck
OK

Standard Notes uses port 3000 by default. If you configured a different port in the .env file, you should update that in the command above.

Step 7 – Configure Nginx

Open the file /etc/nginx/nginx.conf for editing.

$ sudo nano /etc/nginx/nginx.conf

Add the following line before the line include /etc/nginx/conf.d/*.conf;.

server_names_hash_bucket_size  64;

Save the file by pressing Ctrl X and entering Y when prompted.

Create and open the file /etc/nginx/conf.d/standardnotes.conf for editing.

$ sudo nano /etc/nginx/conf.d/standardnotes.conf

Paste the following code in it. Replace standardnotes.example.com with your domain name. We have set the value of the client_max_body_size to 50MB. You can change it as per your requirements.

server {

    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name standardnotes.example.com;

    client_max_body_size 50M;

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

    ssl_certificate      /etc/letsencrypt/live/standardnotes.example.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/standardnotes.example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/standardnotes.example.com/chain.pem;
    
    ssl_session_timeout  5m;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_cache off;
    }
}
# enforce HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name  standardnotes.example.com;
    return 301   https://$host$request_uri;
}

Save the file by pressing Ctrl X and entering Y when prompted.

The above file is for the main Standard Notes application. Next, we need to configure another file for the Files subdomain.

$ sudo nano /etc/nginx/conf.d/files-standardnotes.conf

Paste the following code in it. Replace snotes-files.example.com with your domain name. We have set the value of the client_max_body_size variable to 50MB. You can change it as per your requirements.

server {

    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name snotes-files.example.com;

    client_max_body_size 50M;

    access_log  /var/log/nginx/files-standardnotes.access.log;
    error_log   /var/log/nginx/files-standardnotes.error.log;

    ssl_certificate      /etc/letsencrypt/live/snotes-files.example.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/snotes-files.example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/snotes-files.example.com/chain.pem;
    
    ssl_session_timeout  5m;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    location / {
        proxy_pass http://127.0.0.1:3125;
        proxy_cache off;
    }
}
# enforce HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name  snotes-files.example.com;
    return 301   https://$host$request_uri;
}

Verify your Nginx configuration.

$ sudo nginx -t

Restart the Nginx server to enable the configuration files.

$ sudo systemctl restart nginx

Step 8 – Use Standard Notes

If you open the URL https://standardnotes.example.com in your browser, you should see the following output.

<img alt="Standard Notes URL home" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/07/echo/standardnotes-url-home.png62d138b81593d.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="36" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”647″>

This means your server is up and running. To use Standard Notes, you will need to use the official apps. For our tutorial, we will use their web app but the method will stay the same for the desktop and mobile apps.

Open the URL https://app.standardnotes.com to access the web app. Click the Create free account link at the bottom left of the page and fill in your email address and password. Click on the Advanced features button and uncheck the option Custom sync server and fill in the URL https://standardnotes.example.com in the box.

<img alt="Standard Notes Create Account" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/07/echo/standardnotes-create-account.png62d138b841802.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="614" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”750″>

Once signed in, you can start creating notes and using the application.

Step 9 – Enable Paid features

So far, we have enabled basic functionality for the Standard Notes application. The application offers some advanced features like multiple note formats, encrypted cloud storage, longer revision history, and more.

For the cloud-hosted application, you can pay directly to enable advanced features. But for the self-hosted application, you can’t pay for the advanced features since the payment application doesn’t work. You can donate them though. But to enable paid features in a self-hosted application, you need to run the following command from the Standard Notes directory.

$ cd ~/standardnotes
$ bash ./server.sh create-subscription [email protected]

Reload the web application and the paid features should be activated for your account.

Step 10 – Configure the Server for Files upload

File upload is a paid feature of Standard Notes. We have enabled the custom API URL for the file uploads. But they will still not work. To make them work, we need to give proper permissions to the uploads directory. The uploads are stored in the ~/standardnotes/data/uploads directory. Run the following commands to change permissions.

$ chmod -R 775 data
$ mkdir -p data/uploads
$ sudo chmod -R 755 data/uploads
$ sudo chown -R 1001.1001 data/uploads

Now, Standard Notes sets zero as an upload limit for every user. It means no user can upload files unless given a quota manually. Therefore the final step in making file uploads work is to enable the file quota for the paid user account. We can do that by performing an SQL query inside the database container.

Log in to the MySQL shell inside the Database container.

$ docker exec -it db-standalone mysql -u std_notes_user -p
Enter password:

Once inside the MySQL shell, let us check the list of databases.

mysql > show databases;
 -------------------- 
| Database           |
 -------------------- 
| information_schema |
| standard_notes_db  |
 -------------------- 
2 rows in set (0.00 sec)

Switch to the Standard Notes database.

mysql > use standard_notes_db;

Run the following SQL command to add a 10GB file quota to the paid user-activated above.

mysql> INSERT INTO subscription_settings(uuid, name, value, created_at, updated_at, user_subscription_uuid) VALUES (UUID(), "FILE_UPLOAD_BYTES_LIMIT", 10737418240, FLOOR(UNIX_TIMESTAMP(NOW(6))*1000000), FLOOR(UNIX_TIMESTAMP(NOW(6))*1000000), (SELECT us.uuid FROM user_subscriptions us INNER JOIN users u ON us.user_uuid=u.uuid WHERE u.email="[email protected]"));

Here 10737418240 refers to total bytes which translates to 10GB. You can modify this number to anything you need.

Exit the MySQL shell and the database container.

mysql > exit

Step 11 – Testing File Uploads

Log in to the Standard Notes web app and click on the attachment icon on the top row.

<img alt="Standard Notes Attachment Icon" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/07/echo/standardnotes-attachment-icon.png62d138b86a6c7.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="313" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”456″>

Click on the Upload files button and select the file you want to upload. The file will upload successfully and you can see it listed by clicking the icon again.

<img alt="Standard Notes Attached File" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/07/echo/standardnotes-attached-file.png62d138b892b12.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="278" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”423″>

Click on the three dots against the file name to bring up additional options related to the file.

<img alt="Standard Notes File Options" data-ezsrc="https://kirelos.com/wp-content/uploads/2022/07/echo/standardnotes-file-options.png62d138b8c916a.jpg" ezimgfmt="rs rscb5 src ng ngcb5" height="271" loading="lazy" referrerpolicy="no-referrer" src="data:image/svg xml,” width=”381″>

Click the Attach to Note link to add the image to the note. The remaining options are self-explanatory.

Conclusion

This concludes our tutorial on installing and configuring the Standard Notes server on a Ubuntu 22.04 machine. If you have any questions, post them in the comments below.