Skip to content
Author Nejat Hakan
eMail nejat.hakan@outlook.de
PayPal Me https://paypal.me/nejathakan


Self-Hosting Standard Notes

Introduction

Welcome to this comprehensive guide on self-hosting Standard Notes. Standard Notes stands out in the crowded field of note-taking applications due to its strong emphasis on privacy, security, and longevity. It achieves this through robust end-to-end encryption (E2EE) and a commitment to open-source principles. By default, your notes sync through Standard Notes' official servers, which is convenient and secure. However, for users seeking the ultimate level of data control, privacy, and independence from third-party services, self-hosting the Standard Notes server infrastructure is the ideal solution.

Self-hosting means running the server software that powers Standard Notes on hardware you control – whether it's a server in your home, a virtual private server (VPS) in the cloud, or dedicated hardware. The benefits are numerous:

  • Complete Data Ownership: Your encrypted notes reside solely on your infrastructure. No third party, not even the Standard Notes developers, has access to your server or data.
  • Enhanced Privacy: You control the logs, access policies, and overall operational security of your note-syncing service.
  • Customization and Control: While the core server offers limited customization, self-hosting the extensions server allows you to control which features (editors, themes) are available.
  • Independence: You are not reliant on the uptime or continued operation of the official Standard Notes servers. Your notes remain accessible as long as your server is running.
  • Learning Opportunity: Setting up and maintaining a self-hosted service provides invaluable experience with technologies like Docker, reverse proxies, HTTPS/TLS, server management, and backups.

This guide is structured progressively, starting with the fundamentals and gradually moving towards more complex configurations and maintenance tasks. We will cover:

  • Basic Concepts: Understanding the Standard Notes architecture, preparing your server environment, and deploying the core syncing server using Docker.
  • Intermediate Setup: Securing your server with HTTPS using a reverse proxy, deploying the extensions server to unlock premium features, and implementing robust data backup strategies.
  • Advanced Management: Monitoring server health, performing updates and maintenance, configuring advanced options like email notifications, and troubleshooting common problems.

Each section includes theoretical explanations followed by practical, hands-on workshops designed to reinforce your understanding and provide real-world experience. We assume a baseline familiarity with Linux command-line operations, but we will strive to explain each step clearly and thoroughly, making this guide suitable for university students and anyone eager to dive deep into self-hosting. Let's begin your journey towards digital sovereignty with Standard Notes!

1. Understanding the Standard Notes Ecosystem

Before diving into the technical aspects of self-hosting, it's crucial to understand the different components that make up the Standard Notes ecosystem and how they interact. This foundational knowledge will help you grasp why certain steps are necessary during the setup process.

Standard Notes is designed with a client-server architecture, heavily emphasizing security through end-to-end encryption (E2EE).

Core Components

  1. Standard Notes Clients: These are the applications you directly interact with to write, organize, and read your notes. Standard Notes offers clients for various platforms:

    • Web App (accessible via any modern web browser)
    • Desktop Apps (Windows, macOS, Linux)
    • Mobile Apps (iOS, Android) All clients are responsible for encrypting your notes before they leave your device and decrypting them after they are retrieved from the server. The server only ever stores encrypted blobs of data.
  2. Standard Notes Server (Syncing Server): This is the central backend component responsible for storing your encrypted notes and synchronizing them across all your connected clients. Key characteristics include:

    • Zero-Knowledge: The server has no access to your encryption keys (derived from your password) and therefore cannot decrypt your notes. It only stores and transmits encrypted data.
    • Synchronization Logic: It handles the logic for updating notes, resolving conflicts (though rare with the SN protocol), and managing account information (like email and password hash, used for authentication).
    • API: Provides an Application Programming Interface (API) that the clients use to send and receive data.
    • Self-Hostable: This is the primary component you will deploy when self-hosting. The official open-source implementation is available for anyone to run.
  3. Standard Notes Extensions Server (Optional): Standard Notes offers extended functionality beyond basic note-taking through extensions. These include advanced editors (Markdown, Code, Spreadsheets), themes, and other features.

    • Serving Extensions: This server component hosts the code for these extensions. When you activate an extension in your client, the client fetches the necessary code from an extensions server.
    • Official vs. Self-Hosted: By default, clients use the official Standard Notes extensions server. When self-hosting, you can optionally deploy your own extensions server. This allows you to use paid/extended features without a Standard Notes subscription, provided you host the extensions yourself. Note that the extensions themselves are often open-source but may have specific licensing if used commercially. For personal self-hosted use, this is generally not an issue.
    • Configuration: The client needs to be configured to point to your self-hosted extensions server URL if you choose to deploy one.

The Role of Encryption

End-to-end encryption is the cornerstone of Standard Notes' security model. Here's how it works:

  1. Account Creation: When you create a Standard Notes account, you set a strong password.
  2. Key Derivation: Your password is used, along with a salt (a random value associated with your account), to derive your master encryption key and authentication key using robust algorithms like PBKDF2. Crucially, this derivation happens entirely on the client-side. Your password is never sent to the server in plaintext.
  3. Encryption: Every note, tag, or other piece of data you create is encrypted on your device using your encryption key (typically using AES-256) before being sent to the server for synchronization.
  4. Synchronization: The server receives and stores only the encrypted data blobs. It cannot read the content.
  5. Decryption: When another client syncs, it downloads the encrypted data blobs. Decryption happens locally on that client device using the keys derived from your password (which you must enter on that device).

This E2EE model ensures that only you, with knowledge of your password, can ever access the content of your notes.

Free vs. Paid Tiers and Self-Hosting

Standard Notes operates on a freemium model:

  • Free Tier: Offers basic note-taking functionality, syncing, and access via all clients using the official servers.
  • Paid Tier (Extended): Unlocks access to extensions (advanced editors, themes, etc.) when using the official servers. This subscription supports the development and maintenance of the platform.

When you self-host:

  • Syncing Server: You can self-host the core syncing server regardless of any subscription. This gives you data ownership and privacy for your basic notes.
  • Extensions Server: If you want to use the advanced editors and themes provided by extensions without paying for a Standard Notes subscription, you must also self-host the Extensions server (or the specific components that serve them). Your self-hosted client needs to be pointed to your self-hosted extensions server.

Self-hosting provides a way to access all features through your own infrastructure, but it requires the technical effort of setting up and maintaining both the syncing and extensions servers.

Workshop Setting Up Your First Standard Notes Account

This workshop aims to familiarize you with the standard user experience using the official Standard Notes service. This context is valuable before you embark on self-hosting.

Goal: Create a Standard Notes account, explore the basic client interface, and understand the importance of your password.

Steps:

  1. Access the Standard Notes Web App:
    • Open your web browser and navigate to https://app.standardnotes.com.
  2. Register a New Account:
    • Click on the "Register" or "Create Account" button.
    • Enter an email address. This is primarily used for account recovery if you set it up and potentially for password resets on the official service, but it's not strictly necessary for the core function if you manage your password securely.
    • Create a very strong password. This password is the only key to decrypt your notes. If you forget this password, your encrypted notes cannot be recovered. Standard Notes staff cannot reset it for you in a way that recovers encrypted data due to the E2EE design.
    • Confirm your password.
    • Read and agree to the terms and privacy policy.
    • Click "Register".
  3. Initial Tour (Optional):
    • The app might offer a brief tour. Take a moment to follow it.
  4. Explore the Interface:
    • Create a Note: Click the "+" icon or "New Note" button. Give it a title and write some content in the main editing area. Notice the simplicity of the default editor.
    • Create a Tag: In the left sidebar, find the "Tags" section. Click the "+" icon next to it. Name your tag (e.g., testing, important). Assign the tag to your newly created note by dragging the tag onto the note in the note list or by editing the note's properties/tags field.
    • Syncing: If you have another device (e.g., your phone), install the Standard Notes app there and log in with the same email and password. Observe how your test note and tag appear after syncing. Make a change on one device and see it reflected on the other.
  5. Understand Account Security:
    • Go to the "Account" settings menu (usually in the bottom-left corner).
    • Look at the security options. Notice the emphasis on the password.
    • Find the "Account Key" or "Secret Key". This key is derived from your password and is critical. Standard Notes provides this for backup purposes. Save this key securely (e.g., in a password manager) along with your password. If you ever need to log in and the password derivation changes slightly (e.g., due to software updates), this key might be required.
    • Log Out and Log In: Log out of your account and log back in using your email and password to reinforce the process.

Outcome: You now have a basic understanding of the Standard Notes client interface, the synchronization process, and the critical importance of your account password and keys for accessing your encrypted data. This provides essential context for why securing your self-hosted server and managing your credentials carefully is paramount.

2. Preparing Your Server Environment

Before deploying the Standard Notes server software, you need a suitable server environment. This section covers the prerequisites, recommended operating systems, essential software installation, and basic security hardening.

Minimum Server Requirements

Standard Notes server components are relatively lightweight, especially the core syncing server. However, resources required can increase depending on the number of users, the volume of notes, and whether you run the extensions server.

  • CPU: 1 vCPU is generally sufficient for personal use or small groups.
  • RAM: 512MB is often the bare minimum, but 1GB or more is strongly recommended for stable operation, especially if running Docker and potentially other services like a reverse proxy. 2GB provides more comfortable headroom.
  • Disk Space: The Standard Notes database itself is usually small (megabytes to gigabytes unless storing very large notes/files, which isn't the primary use case). However, you need space for the Operating System, Docker images, logs, and potentially backups. 10-20GB of available disk space is a reasonable starting point. SSD storage is recommended for better performance.
  • Network: A stable internet connection with sufficient bandwidth for synchronization.

Choosing an Operating System

While the Standard Notes server can potentially run on various operating systems, Linux is the most common and recommended platform for self-hosting, primarily due to its stability, performance, security features, and excellent support for containerization technologies like Docker.

Popular choices include:

  • Ubuntu Server (LTS versions like 20.04, 22.04): Widely used, excellent community support, extensive documentation, and regular updates. A great choice for both beginners and experienced users.
  • Debian: Known for its stability and adherence to free software principles. Ubuntu is based on Debian. It's another excellent, solid choice.
  • CentOS Stream / RHEL / Fedora: Alternatives in the Red Hat ecosystem. Commands for package management (dnf/yum instead of apt) and configuration might differ slightly.
  • Other Distributions: Arch Linux, openSUSE, etc., are also viable but might require more manual configuration.

This guide will primarily use commands compatible with Ubuntu/Debian.

Essential Software Prerequisites Docker and Docker Compose

While you could install the Standard Notes server components directly on the host system, using Docker is highly recommended and the officially supported method for self-hosting.

  • What is Docker? Docker is a platform for developing, shipping, and running applications in containers. Containers package an application and its dependencies together, ensuring it runs consistently across different environments.
  • Why Use Docker for Standard Notes?

    • Simplified Deployment: Docker abstracts away dependencies. You don't need to manually install specific versions of Node.js, databases, or libraries required by Standard Notes. The Docker image contains everything needed.
    • Isolation: The Standard Notes server runs in an isolated container, preventing conflicts with other software on your server.
    • Consistency: The application runs the same way regardless of your underlying Linux distribution or configuration.
    • Easy Updates: Updating Standard Notes often involves just pulling the latest Docker image and restarting the container.
    • Reproducibility: Your deployment can be easily defined and reproduced using configuration files.
  • What is Docker Compose? Docker Compose is a tool for defining and running multi-container Docker applications. You use a YAML file (docker-compose.yml) to configure your application's services (like the Standard Notes server, maybe a database, and later the extensions server). With a single command (docker-compose up), you can create and start all the services defined in your configuration.

  • Why Use Docker Compose?
    • Manages Multi-Container Setups: Simplifies linking containers (e.g., making the extensions server aware of the syncing server).
    • Configuration Management: Keeps your entire application stack configuration in one readable file.
    • Simplified Commands: Easier than managing individual docker run commands with many options.

You will need to install both docker and docker-compose (or the docker compose plugin) on your server.

Basic Server Security Hardening

Before exposing any service to the internet, even one secured with E2EE like Standard Notes, it's crucial to implement basic server security measures.

  • Firewall: A firewall controls incoming and outgoing network traffic. You should configure it to allow traffic only on necessary ports.
    • SSH (Port 22 by default): Required for remote administration. Access should ideally be restricted to trusted IP addresses if possible, or secured using SSH keys.
    • HTTP (Port 80): Needed initially for Let's Encrypt certificate validation if using HTTP-01 challenge.
    • HTTPS (Port 443): Required for secure access to your Standard Notes instance via a reverse proxy.
    • Standard Notes Port (e.g., 3000): This port should generally not be directly exposed to the internet. It should only be accessible to the reverse proxy running on the same server or within the Docker network.
    • UFW (Uncomplicated Firewall): A user-friendly firewall interface for Linux, common on Ubuntu/Debian.
  • SSH Key Authentication: Using SSH keys instead of passwords for server login is significantly more secure. Password authentication can be vulnerable to brute-force attacks.
  • Regular Updates: Keep your server's operating system and installed packages up-to-date to patch security vulnerabilities. Use sudo apt update && sudo apt upgrade -y (on Ubuntu/Debian) regularly.
  • Fail2Ban (Optional but Recommended): This service scans log files (like SSH logs) and bans IP addresses that show malicious signs, such as too many password failures.

Workshop Preparing a Linux Server for Standard Notes

Goal: Install Docker, Docker Compose, and configure a basic firewall (UFW) on a fresh Ubuntu/Debian server.

Prerequisites: Access to a Linux server (Ubuntu 20.04/22.04 or Debian 11/12 recommended) with sudo privileges.

Steps:

  1. Update System Packages:
    • Connect to your server via SSH.
    • Run the following commands to update the package list and upgrade existing packages:
      sudo apt update
      sudo apt upgrade -y
      
  2. Install Docker Engine:
    • Follow the official Docker installation guide for your distribution. For Ubuntu/Debian, the general steps are:
      • Remove any old Docker versions:
        sudo apt-get remove docker docker-engine docker.io containerd runc -y
        
      • Set up Docker's apt repository:
        sudo apt-get update
        sudo apt-get install ca-certificates curl gnupg lsb-release -y
        sudo mkdir -p /etc/apt/keyrings
        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
        # Use appropriate command for Debian if not Ubuntu
        echo \
          "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
          $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
        
        (Note: For Debian, replace ubuntu with debian in the URL and potentially adjust $(lsb_release -cs) if needed, check Docker docs).
      • Install Docker Engine:
        sudo apt-get update
        sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
        
    • Verify Docker installation:
      sudo docker run hello-world
      
      (This should download and run a simple container, confirming Docker is working).
  3. Add User to Docker Group (Optional but Recommended):
    • To run docker commands without sudo, add your user to the docker group. Note: This has security implications as it grants privileges equivalent to root. Understand the risks before proceeding.
      sudo usermod -aG docker $USER
      
    • You need to log out and log back in or run newgrp docker for this change to take effect in your current session.
  4. Verify Docker Compose Plugin:
    • Check if the Docker Compose plugin is installed:
      docker compose version
      
      (This should output the Docker Compose version).
  5. Configure Firewall (UFW):
    • Check if UFW is installed (it usually is on Ubuntu):
      sudo ufw status
      
      (If inactive, proceed. If active, be careful not to lock yourself out).
    • Set default policies (deny incoming, allow outgoing):
      sudo ufw default deny incoming
      sudo ufw default allow outgoing
      
    • Allow essential ports:
      • SSH (Critical! Ensure this is correct before enabling UFW):
        sudo ufw allow ssh # Or specify the port if non-standard, e.g., sudo ufw allow 2222/tcp
        
      • HTTP (for Let's Encrypt later):
        sudo ufw allow http # Port 80
        
      • HTTPS (for secure access later):
        sudo ufw allow https # Port 443
        
      • (We will not allow the Standard Notes default port like 3000 directly, as it will be accessed via the reverse proxy).
    • Enable UFW:
      sudo ufw enable
      
      (Confirm with 'y' if prompted. Double-check you can still SSH in!)
    • Verify the rules:
      sudo ufw status verbose
      
      (Should show SSH, HTTP, HTTPS allowed).
  6. Set Up SSH Key Authentication (Highly Recommended - Optional for this Workshop):
    • If you haven't already, generate an SSH key pair on your local machine (ssh-keygen).
    • Copy the public key (~/.ssh/id_rsa.pub or similar) to your server's ~/.ssh/authorized_keys file. Use ssh-copy-id user@your_server_ip.
    • Test logging in with the key.
    • Once confirmed, you can disable password authentication for better security by editing /etc/ssh/sshd_config on the server (PasswordAuthentication no) and restarting the SSH service (sudo systemctl restart sshd). Be very careful with this step – ensure key login works perfectly first!

Outcome: Your server now has Docker and Docker Compose installed and running. A basic firewall is configured to allow only necessary ports (SSH, HTTP, HTTPS), providing a more secure foundation for deploying the Standard Notes server. You are ready for the next step: deploying the application itself.

3. Basic Standard Notes Server Deployment (Docker)

With the server environment prepared, you can now deploy the core Standard Notes Syncing Server using Docker. This initial deployment will allow clients to connect directly via the server's IP address, providing a functional, albeit insecure (HTTP), setup for testing and understanding the basics.

The standardnotes/server Docker Image

The Standard Notes team officially maintains a Docker image named standardnotes/server. This image contains the necessary code, dependencies (like Node.js), and a simple file-based database system (suitable for many self-hosting scenarios) to run the Syncing Server. You don't need to manually build anything; Docker will pull this pre-built image from Docker Hub.

Configuration via Environment Variables

Docker containers are typically configured using environment variables passed during runtime. The standardnotes/server image uses several environment variables, but for a basic setup, only a few are strictly necessary. The most critical one is:

  • SECRET_KEY_BASE: This is a mandatory secret random string used by the server for signing session cookies and other security-related functions. It's crucial that this remains secret and is sufficiently random. You need to generate a strong random string for this value. A common way is using openssl rand -hex 64.

Other variables exist for database configuration, email setup, etc., which we will explore in later sections.

Deployment using Docker Compose

While you could use a docker run command, docker-compose provides a more structured and manageable approach, especially as we add more components later (like the extensions server or a reverse proxy).

Here's a minimal docker-compose.yml file to run the Syncing Server:

version: '3.7' # Specify docker-compose version

services:
  syncserver:
    image: standardnotes/server:latest # Use the official image
    container_name: standardnotes_syncserver
    restart: unless-stopped # Automatically restart if it crashes or server reboots
    ports:
      - "127.0.0.1:3000:3000" # Map container port 3000 to localhost:3000
    volumes:
      - ./sn_data:/var/lib/standardnotes # Persist data to a local directory
    environment:
      # - SECRET_KEY_BASE=YOUR_GENERATED_SECRET_KEY_BASE # Set this securely! See below
      # Load environment variables from a .env file for security
      - env_file: .env

Explanation:

  • version: '3.7': Defines the version of the Docker Compose file syntax.
  • services:: Defines the different containers that make up your application.
  • syncserver:: A custom name for our Standard Notes Syncing Server service.
  • image: standardnotes/server:latest: Specifies the Docker image to use. Using :latest is convenient but can sometimes lead to unexpected updates; pinning to a specific version (e.g., standardnotes/server:3.150.0) offers more stability.
  • container_name: standardnotes_syncserver: Assigns a specific name to the running container for easier identification.
  • restart: unless-stopped: Ensures the container restarts automatically if it stops for any reason other than you manually stopping it.
  • ports:
    • "127.0.0.1:3000:3000": This maps port 3000 inside the container to port 3000 on the host server's localhost interface only (127.0.0.1). This is crucial for security initially – it prevents direct access from the internet. We will access it through a reverse proxy later. If you needed direct access (e.g., for testing on a local network without a reverse proxy yet), you might temporarily use "3000:3000", but this is not recommended for production or internet-facing servers.
  • volumes:
    • ./sn_data:/var/lib/standardnotes: This mounts a directory named sn_data (relative to where your docker-compose.yml file is located) on your host machine to the /var/lib/standardnotes directory inside the container. This is where the server stores its data (like the encrypted notes database). This ensures your data persists even if the container is removed and recreated.
  • environment:
    • env_file: .env: Instructs Docker Compose to load environment variables from a file named .env located in the same directory as the docker-compose.yml. This is the recommended way to handle sensitive information like SECRET_KEY_BASE.

Handling SECRET_KEY_BASE Securely

Do not hardcode SECRET_KEY_BASE directly in your docker-compose.yml if you plan to commit this file to version control or share it. Use an .env file:

  1. Generate the Key:
    openssl rand -hex 64
    
    Copy the long hexadecimal string output.
  2. Create the .env file: In the same directory as your docker-compose.yml, create a file named .env with the following content:
    SECRET_KEY_BASE=YOUR_GENERATED_64_CHARACTER_HEX_STRING_HERE
    
    Replace the placeholder with the actual key you generated.
  3. Secure the .env file: Ensure this file has restrictive permissions (e.g., chmod 600 .env) and is added to your .gitignore file if using Git.

Connecting Clients via IP Address (Initial Test)

Once the server is running, you can configure your Standard Notes client (Web or Desktop recommended for easy configuration) to connect to it.

  1. Find Server IP: Get the local IP address of your server (e.g., using ip addr show or hostname -I) if accessing from within the same local network, or the public IP if accessing over the internet (though this direct IP access method is temporary and insecure).
  2. Configure Client:
    • Open the Standard Notes client.
    • Before logging in or registering, look for an "Advanced Options" or similar menu.
    • In the "Sync Server" or "Custom Server" field, enter the address: http://YOUR_SERVER_IP:3000. Note the http, as we haven't set up HTTPS yet. Replace YOUR_SERVER_IP with the actual IP address.
    • Save the settings.
  3. Register/Login: Now, register a new account on your self-hosted server or log in if you previously created one there. Do not use your account from the official service; accounts are specific to the server instance.

Important: Connecting via HTTP sends your login credentials (hashed password) and potentially other metadata unencrypted over the network. This is highly insecure, especially over the internet. This step is purely for initial verification that the server is running. We will secure this with HTTPS in the next section.

Workshop Deploying the Syncing Server via Docker

Goal: Deploy the Standard Notes Syncing Server using Docker Compose and connect a client via the server's IP address for initial testing.

Prerequisites:

  • Server prepared according to the previous workshop (Docker, Docker Compose installed).
  • Access to the server command line.
  • A Standard Notes client (Web or Desktop recommended).

Steps:

  1. Create a Project Directory:
    • On your server, create a dedicated directory for your Standard Notes configuration:
      mkdir standardnotes
      cd standardnotes
      
  2. Generate SECRET_KEY_BASE:
    • Run the command to generate the key:
      openssl rand -hex 64
      
    • Copy the output string.
  3. Create the .env file:
    • Create and open the .env file using a text editor (like nano):
      nano .env
      
    • Add the following line, pasting your generated key:
      SECRET_KEY_BASE=PASTE_YOUR_GENERATED_KEY_HERE
      
    • Save the file (Ctrl+O in nano, then Enter) and exit (Ctrl+X).
  4. Set Permissions for .env file:
    • Restrict permissions:
      chmod 600 .env
      
  5. Create the docker-compose.yml file:
    • Create and open the docker-compose.yml file:
      nano docker-compose.yml
      
    • Paste the following content:
      version: '3.7'
      
      services:
        syncserver:
          image: standardnotes/server:latest
          container_name: standardnotes_syncserver
          restart: unless-stopped
          # Temporarily expose port 3000 directly for IP-based testing
          # WARNING: Insecure for internet exposure! Change back to 127.0.0.1 later.
          ports:
            - "3000:3000"
          volumes:
            - ./sn_data:/var/lib/standardnotes
          env_file:
            - .env
      
      Important Security Note: For this workshop only, we are using "3000:3000" to allow direct IP access for testing. In the next section, when setting up the reverse proxy, you must change this back to "127.0.0.1:3000:3000".
    • Save and exit the editor.
  6. Start the Container:
    • Run Docker Compose in detached mode (-d):
      docker compose up -d
      
    • Docker Compose will pull the standardnotes/server image (if not already present) and start the container.
  7. Check Container Status and Logs:
    • Verify the container is running:
      docker compose ps
      
      (Should show standardnotes_syncserver with State Up).
    • Check the logs for any errors (press Ctrl+C to stop following):
      docker compose logs -f syncserver
      
      (You should see messages indicating the server started successfully, often listening on port 3000).
  8. Configure Standard Notes Client:
    • Find your server's IP address (e.g., hostname -I or check your cloud provider's dashboard). Let's assume it's 192.168.1.100 for this example.
    • Open the Standard Notes Web App (https://app.standardnotes.com) or the Desktop App.
    • Before logging in, click "Advanced Options".
    • In the "Sync Server" field, enter: http://192.168.1.100:3000 (replace 192.168.1.100 with your server's actual IP).
    • Click "Save" or apply the changes.
  9. Register a New Account (on your server):
    • Back on the login/register screen, choose "Register".
    • Use an email (can be fake for local testing, e.g., test@local.host) and a strong password.
    • Complete the registration.
  10. Test Synchronization:
    • Create a test note (e.g., "My Self-Hosted Note").
    • Create a test tag (e.g., selfhosted).
    • Log out of the client.
    • Log back in using the same credentials you just registered on your server.
    • Verify that your test note and tag are still present.

Outcome: You have successfully deployed the Standard Notes Syncing Server using Docker Compose and confirmed it's operational by connecting a client directly via IP address. You've created your first account and note on your own server. Remember that this setup is currently insecure (HTTP). The next crucial step is to implement HTTPS using a reverse proxy.

4. Enabling HTTPS with Reverse Proxy

Running your Standard Notes server over HTTP is insecure. Your login credentials (hashed, but still sensitive) and metadata could be intercepted. Enabling HTTPS (HTTP Secure) encrypts the communication between your clients and your server, protecting your data in transit. The standard way to achieve this for containerized applications like Standard Notes is by using a reverse proxy.

The Importance of HTTPS

  • Encryption: HTTPS uses TLS/SSL protocols to encrypt all data exchanged between the client (your Standard Notes app) and the server (your reverse proxy). This prevents eavesdropping on public Wi-Fi or by network intermediaries.
  • Authentication: The SSL certificate used for HTTPS verifies that the server you are connecting to is genuinely the one associated with the domain name you are using, preventing man-in-the-middle attacks.
  • Data Integrity: HTTPS ensures that the data exchanged hasn't been tampered with during transit.
  • Browser Requirements: Modern browsers increasingly flag HTTP sites as insecure and may block certain features on non-HTTPS connections.

What is a Reverse Proxy?

A reverse proxy is a server that sits in front of one or more web servers (like your Standard Notes container), intercepting requests from clients. It acts as a gateway or intermediary. For self-hosting Standard Notes, a reverse proxy provides several key benefits:

  1. SSL/TLS Termination: The reverse proxy handles the complexity of HTTPS encryption and decryption. It receives secure HTTPS requests from clients, decrypts them, and forwards them as plain HTTP requests to the backend service (your Standard Notes container) over the secure internal Docker network or localhost connection. This simplifies the configuration of the backend application, which doesn't need to manage certificates itself.
  2. Certificate Management: Modern reverse proxies can automatically obtain and renew free SSL/TLS certificates from authorities like Let's Encrypt, significantly simplifying HTTPS setup.
  3. Centralized Access Point: You can run multiple web services on the same server (e.g., Standard Notes, a blog, a file-sharing app) and use the reverse proxy to route traffic to the correct service based on the requested domain name (e.g., notes.yourdomain.com goes to Standard Notes, blog.yourdomain.com goes to your blog).
  4. Load Balancing (Advanced): Can distribute incoming traffic across multiple instances of an application for high availability and performance.
  5. Security: Can provide an additional layer of security, potentially filtering malicious requests, implementing rate limiting, or handling authentication.

Several excellent reverse proxy solutions are popular in the self-hosting community, often available as Docker containers:

  • Nginx: A powerful, high-performance web server and reverse proxy. Very flexible but can have a steeper learning curve for manual configuration.
  • Traefik: A modern reverse proxy designed specifically for Docker and microservices. Features automatic service discovery and configuration based on Docker labels, making it very dynamic. Can be complex initially.
  • Caddy: A modern web server with automatic HTTPS via Let's Encrypt built-in by default. Known for its simple configuration file (Caddyfile). A great choice for ease of use, especially regarding HTTPS.
  • Nginx Proxy Manager (NPM): This is not Nginx itself, but a user-friendly web GUI built on top of Nginx and Let's Encrypt. It allows managing Nginx proxy configurations, SSL certificates, and access lists through a simple web interface, making it very beginner-friendly.

For this guide, we'll focus on Nginx Proxy Manager (NPM) due to its ease of use for university students and beginners, abstracting away much of the underlying Nginx configuration complexity.

DNS Configuration

To use HTTPS with a proper certificate, you need a domain name (e.g., yourdomain.com) or a subdomain (e.g., notes.yourdomain.com).

  1. Obtain a Domain Name: If you don't have one, you'll need to register one through a domain registrar (e.g., Namecheap, Cloudflare, GoDaddy).
  2. Configure DNS: You need to create a DNS record (usually an A record) that points your chosen domain or subdomain to the public IP address of the server where you are running Standard Notes and the reverse proxy.
    • Log in to your domain registrar's or DNS provider's control panel.
    • Go to the DNS management section for your domain.
    • Create a new A record:
      • Host/Name: Enter the subdomain you want to use (e.g., notes if you want notes.yourdomain.com, or @ if you want to use the root domain yourdomain.com).
      • Value/Points to: Enter the public IPv4 address of your server.
      • TTL (Time To Live): You can usually leave this at the default (e.g., "Automatic" or 1 hour).
    • Save the record. DNS changes can take some time to propagate (minutes to hours, potentially up to 48 hours in rare cases). You can use tools like dig or online DNS checkers (e.g., dnschecker.org) to verify propagation.

Nginx Proxy Manager Setup

Nginx Proxy Manager (NPM) itself runs as a Docker container. You'll typically add it to your docker-compose.yml file or run it as a separate stack.

Example docker-compose.yml for Nginx Proxy Manager:

version: '3.7'
services:
  npm:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx_proxy_manager
    restart: unless-stopped
    ports:
      # Public HTTP port
      - '80:80'
      # Public HTTPS port
      - '443:443'
      # Admin GUI port (can be mapped to localhost only for security if desired)
      - '8181:81' # Map internal port 81 to host port 8181
    volumes:
      - ./npm_data:/data # Persist NPM data (configs, users, certs)
      - ./letsencrypt:/etc/letsencrypt # Persist Let's Encrypt certificates
    environment:
      # Set to 1 to allow NPM to manage its own database file within /data
      DB_SQLITE_FILE: "/data/database.sqlite"
      # Set to 'true' to disable IPv6 if causing issues (optional)
      # DISABLE_IPV6: 'true'
networks:
  default:
    # Optional: define a custom network if needed for communication between NPM and SN
    # name: my_shared_network

You would typically run this alongside your Standard Notes docker-compose.yml, perhaps combining them into one file or ensuring they share a common Docker network so NPM can reach the Standard Notes container by its service name (syncserver).

Workshop Securing Your Server with Nginx Proxy Manager and Let's Encrypt

Goal: Deploy Nginx Proxy Manager (NPM), configure it to proxy traffic to your Standard Notes container, and obtain a valid Let's Encrypt SSL certificate for HTTPS access.

Prerequisites:

  • Standard Notes Syncing Server running via Docker Compose (from the previous workshop).
  • A registered domain name.
  • Ability to configure DNS records for your domain.
  • Server prepared with Docker, Docker Compose, and UFW allowing ports 80, 443, and SSH.

Steps:

  1. Configure DNS:
    • Go to your DNS provider's control panel.
    • Create an A record pointing your desired subdomain (e.g., notes.yourdomain.com) to your server's public IP address.
    • Wait for DNS propagation (use ping notes.yourdomain.com from your local machine or dnschecker.org to check).
  2. Modify Standard Notes docker-compose.yml:
    • Crucial Security Step: Edit your standardnotes/docker-compose.yml file.
    • Change the ports mapping for syncserver back to 127.0.0.1:3000:3000. This prevents direct external access and forces traffic through the reverse proxy.
      services:
        syncserver:
          # ... other settings ...
          ports:
      -     - "3000:3000" # <-- Remove or comment out this line
      +     - "127.0.0.1:3000:3000" # <-- Add this line
          # ... other settings ...
      
    • Optional but Recommended: Define a Network: To ensure NPM can reliably reach the syncserver by its service name, define a shared network.
      version: '3.7'
      
      services:
        syncserver:
          image: standardnotes/server:latest
          container_name: standardnotes_syncserver
          restart: unless-stopped
          ports:
            - "127.0.0.1:3000:3000" # Map only to localhost
          volumes:
            - ./sn_data:/var/lib/standardnotes
          env_file:
            - .env
          networks: # Add this
            - sn-network # Add this
      
      networks: # Add this whole section
        sn-network: # Add this
          name: standardnotes_network # Define a custom network name (optional but good practice)
      
    • Save the file.
  3. Create Nginx Proxy Manager docker-compose.yml:
    • In a separate directory (e.g., mkdir npm && cd npm) or within your standardnotes directory (if combining), create a new docker-compose.yml for NPM. Let's assume you put it inside the standardnotes directory and combine them.
    • Edit your main docker-compose.yml to include both services:
      version: '3.7'
      
      services:
        syncserver:
          image: standardnotes/server:latest
          container_name: standardnotes_syncserver
          restart: unless-stopped
          ports:
            - "127.0.0.1:3000:3000" # Map only to localhost
          volumes:
            - ./sn_data:/var/lib/standardnotes
          env_file:
            - .env
          networks:
            - sn-network # Connect to the shared network
      
        npm:
          image: 'jc21/nginx-proxy-manager:latest'
          container_name: nginx_proxy_manager
          restart: unless-stopped
          ports:
            - '80:80'   # Public HTTP port
            - '443:443' # Public HTTPS port
            - '8181:81' # Admin GUI on host port 8181
          volumes:
            - ./npm_data:/data
            - ./letsencrypt:/etc/letsencrypt
          environment:
            DB_SQLITE_FILE: "/data/database.sqlite"
          networks: # Connect to the shared network
            - sn-network
      
      networks:
        sn-network:
          name: standardnotes_network
      
    • Save the combined docker-compose.yml file.
  4. Restart/Apply Changes:
    • Navigate to the directory containing your updated docker-compose.yml.
    • Run docker compose up -d. This will recreate the syncserver container with the new port mapping and network, and create and start the npm container. Use docker compose down && docker compose up -d if you encounter issues.
  5. Access Nginx Proxy Manager Admin UI:
    • Open your web browser and navigate to http://YOUR_SERVER_IP:8181.
    • The default login credentials are:
      • Email: admin@example.com
      • Password: changeme
    • You will be forced to change these immediately upon first login. Use a strong password!
  6. Add Proxy Host for Standard Notes:
    • Inside the NPM admin interface, navigate to "Hosts" -> "Proxy Hosts".
    • Click "Add Proxy Host".
    • Fill in the details on the "Details" tab:
      • Domain Names: Enter your subdomain (e.g., notes.yourdomain.com).
      • Scheme: Select http.
      • Forward Hostname / IP: Enter the service name of your Standard Notes container as defined in docker-compose.yml (syncserver). Docker's internal DNS will resolve this if they share a network. Alternatively, you could use the container's internal IP, but the service name is more robust.
      • Forward Port: Enter the port the Standard Notes container listens on internally, which is 3000.
      • Enable Block Common Exploits: Recommended.
      • Enable Websockets Support: Important for real-time sync. Toggle this ON.
    • Switch to the "SSL" tab:
      • SSL Certificate: Select "Request a new SSL Certificate".
      • Enable Force SSL: Recommended. This automatically redirects HTTP requests to HTTPS.
      • Enable HTTP/2 Support: Recommended for performance.
      • Enable HSTS Enabled: Recommended for security (Strict Transport Security). This tells browsers to only connect via HTTPS in the future. Be sure HTTPS is working reliably before enabling HSTS Subdomains if you plan to host other things.
      • Email Address for Let's Encrypt: Enter your valid email address (Let's Encrypt uses this for renewal notices).
      • Agree to Let's Encrypt Terms: Toggle the agreement switch.
    • Click "Save". NPM will now attempt to contact Let's Encrypt to obtain a certificate. Ensure ports 80 and 443 are open on your server firewall and correctly mapped in the NPM Docker configuration.
  7. Verify HTTPS Access:
    • Wait a minute or two for the certificate process. The status in NPM should change from "Offline" to "Online".
    • Open your web browser and navigate to https://notes.yourdomain.com (using https and your actual domain).
    • You should see a raw text response, likely Cannot GET /. This is expected because the root path / isn't typically used by the SN server API. The important part is that you get a valid HTTPS connection (check the padlock icon in your browser's address bar).
  8. Reconfigure Standard Notes Client:
    • Open your Standard Notes client (Web or Desktop).
    • Go back to "Advanced Options".
    • Change the "Sync Server" URL to your new HTTPS address: https://notes.yourdomain.com (remove the port number, as HTTPS defaults to 443, handled by NPM).
    • Save the settings.
  9. Test Login and Sync:
    • Log in using the account you created earlier on your self-hosted server.
    • Verify that your notes sync correctly over the secure HTTPS connection.

Outcome: You have successfully secured your Standard Notes Syncing Server using Nginx Proxy Manager as a reverse proxy and obtained a valid Let's Encrypt SSL certificate. All communication between your clients and server is now encrypted with HTTPS. You have also configured the components to communicate securely over an internal Docker network.

5. Deploying the Extensions Server

While the core Standard Notes experience is functional with just the Syncing Server, the real power of customization and enhanced productivity comes from Extensions. These include different editors (like Markdown, Code, Spreadsheets), themes, and other tools. To use these features on your self-hosted setup without relying on the official Standard Notes subscription service, you need to self-host the Extensions Server components.

What are Standard Notes Extensions?

Extensions are essentially small web applications (usually built with JavaScript, HTML, CSS) that integrate with the Standard Notes client interface. When you select an extension (e.g., switch to the "Markdown Pro" editor), the client fetches the code for that extension from a designated URL and runs it within the client's sandboxed environment.

The extensions themselves are often open-source and available in repositories maintained by Standard Notes or the community.

The Self-Hosted Extensions Endpoint

To self-host extensions, you need a web server component capable of serving the static files (HTML, JS, CSS) that constitute these extensions. Historically, Standard Notes provided a single standardnotes/extensions Docker image. However, the architecture has become more modular. Often, self-hosters now deploy a separate, minimal web server (like Nginx or Caddy) specifically configured to serve the extension files, or use specific components provided by the Standard Notes team if available.

A common approach involves:

  1. Obtaining Extension Code: Cloning the relevant Git repositories containing the extension code (e.g., standardnotes/standard-extensions).
  2. Serving the Files: Using a simple web server container (like nginx:alpine) configured to serve the directory containing the extension code.
  3. Reverse Proxy Configuration: Configuring your existing reverse proxy (Nginx Proxy Manager) to route a specific path (e.g., /extensions) or a dedicated subdomain (e.g., ext.notes.yourdomain.com) to this new web server container.
  4. Client Configuration: Telling your Standard Notes clients where to find your self-hosted extensions.

Simpler Alternative (Using Pre-built Index): Some community members or potentially Standard Notes themselves might provide pre-built extension listings or simpler Docker images that bundle common extensions. For this guide, we'll use a method often cited which involves serving a specific index.json file that points to the locations of individual extensions. The official standardnotes/web image (the client web app) itself can sometimes be used or adapted for this, or a dedicated simple server.

Let's focus on a pragmatic approach using a simple Nginx container to serve a directory, which is a common and flexible method.

Relationship Between Servers

  • The Syncing Server (syncserver) handles your note data.
  • The Extensions Server (our new Nginx container) serves the code for editors, themes, etc.
  • The Reverse Proxy (Nginx Proxy Manager) directs traffic:
    • notes.yourdomain.com -> syncserver:3000
    • notes.yourdomain.com/ext (or similar path) -> extensions_server:80 (the Nginx container serving files)
  • The Standard Notes Client needs two URLs configured:
    • Sync Server URL: https://notes.yourdomain.com
    • Extensions Server URL: https://notes.yourdomain.com/ext (or your chosen path/subdomain)

Configuration Steps

  1. Prepare Extension Files: You need a directory on your host server containing the extensions you want to serve. The easiest way to start is often by cloning the official extensions repository.
  2. Add Nginx Service to docker-compose.yml: Add a new service for the Nginx container that will serve these files.
  3. Configure Nginx: Provide a basic Nginx configuration file (nginx.conf) to tell Nginx how to serve the files (usually just defining the root directory and listening port).
  4. Update Reverse Proxy (NPM): Add a "Location" or custom Nginx configuration within your existing Proxy Host definition in NPM to route the extensions path (e.g., /ext) to the new Nginx container.
  5. Configure SN Client: Update the "Extensions" URL in the Standard Notes client settings.

Workshop Adding Self-Hosted Extensions

Goal: Deploy a simple Nginx container to serve Standard Notes extensions, configure the reverse proxy, and enable custom extensions in the client.

Prerequisites:

  • Working self-hosted Standard Notes Syncing Server with HTTPS via Nginx Proxy Manager (from previous workshops).
  • git installed on your server (sudo apt install git -y).
  • Access to the server command line and NPM web UI.

Steps:

  1. Clone Extensions Repository:

    • Navigate to your main standardnotes project directory on the server.
    • Clone the official extensions repository (this contains the code and the necessary index.json file structure that lists available extensions):
      git clone https://github.com/standardnotes/standard-extensions.git extensions-repo
      
    • This creates a directory named extensions-repo containing the extensions. We will serve the contents of this directory.
  2. Create Nginx Configuration for Extensions:

    • Create a configuration file for the Nginx container that will serve the extensions. Create a file named nginx-extensions.conf in your standardnotes directory:
      nano nginx-extensions.conf
      
    • Paste the following basic Nginx configuration:
      server {
          listen 80;
          server_name _; # Listen for any server name
      
          root /usr/share/nginx/html; # Root directory inside the container
          index index.html index.htm index.json; # Default files to look for
      
          location / {
              try_files $uri $uri/ =404;
          }
      
          # Add headers to prevent cross-origin issues
          location ~* \.(?:css|js|json|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
              add_header Access-Control-Allow-Origin '*' always;
              expires 1M; # Cache static assets for 1 month
              access_log off;
              log_not_found off;
              try_files $uri =404;
          }
      }
      
    • Save and exit. This configuration tells Nginx to listen on port 80 and serve files from /usr/share/nginx/html, adding CORS headers which are often necessary for extensions.
  3. Update docker-compose.yml:

    • Edit your main docker-compose.yml file to add the new extensions_server service:
      version: '3.7'
      
      services:
        syncserver:
          # ... (syncserver configuration remains the same) ...
          image: standardnotes/server:latest
          container_name: standardnotes_syncserver
          restart: unless-stopped
          ports:
            - "127.0.0.1:3000:3000"
          volumes:
            - ./sn_data:/var/lib/standardnotes
          env_file:
            - .env
          networks:
            - sn-network
      
        # NEW: Service to serve extensions
        extensions_server:
          image: nginx:alpine # Use a lightweight Nginx image
          container_name: standardnotes_extensions
          restart: unless-stopped
          volumes:
            # Mount the cloned repo into the container's web root
            - ./extensions-repo:/usr/share/nginx/html:ro
            # Mount our custom Nginx config
            - ./nginx-extensions.conf:/etc/nginx/conf.d/default.conf:ro
          networks:
            - sn-network # Must be on the same network as NPM
      
        npm:
          # ... (NPM configuration remains the same) ...
          image: 'jc21/nginx-proxy-manager:latest'
          container_name: nginx_proxy_manager
          restart: unless-stopped
          ports:
            - '80:80'
            - '443:443'
            - '8181:81'
          volumes:
            - ./npm_data:/data
            - ./letsencrypt:/etc/letsencrypt
          environment:
            DB_SQLITE_FILE: "/data/database.sqlite"
          networks:
            - sn-network
      
      networks:
        sn-network:
          name: standardnotes_network
      
      Explanation of Changes:
      • Added a new service extensions_server.
      • Uses the nginx:alpine image.
      • Mounts the extensions-repo directory (read-only :ro) into Nginx's default web root /usr/share/nginx/html.
      • Mounts our nginx-extensions.conf file (read-only :ro) to overwrite the default Nginx configuration inside the container.
      • Connects it to the sn-network.
  4. Apply Docker Compose Changes:

    • Run docker compose up -d. This will pull the nginx:alpine image and create/start the extensions_server container.
  5. Configure Nginx Proxy Manager Location:

    • Log in to your NPM Admin UI (http://YOUR_SERVER_IP:8181).
    • Go to "Hosts" -> "Proxy Hosts".
    • Click the edit icon for your existing notes.yourdomain.com host.
    • Go to the "Locations" tab.
    • Click "Add Location".
    • Configure the location:
      • Define location URI: /ext (This is the path clients will use. You can choose something else, but /ext is common).
      • Scheme: Select http.
      • Forward Hostname / IP: Enter the service name: extensions_server
      • Forward Port: Enter the port Nginx listens on inside the container: 80
      • Enable Websockets Support: Should not be necessary for serving static extension files, leave OFF unless testing proves otherwise.
    • Click "Save". Now, requests to https://notes.yourdomain.com/ext/... will be routed by NPM to your extensions_server container.
  6. Configure Standard Notes Client:

    • Open your Standard Notes client (Web or Desktop) connected to your self-hosted server (https://notes.yourdomain.com).
    • Go to "Account" -> "Preferences" or "Account" -> "General". Look for the "Advanced Options" or a dedicated "Extensions" section if available.
    • Find the field for "Custom Extensions Repository" or "Extensions URL".
    • Enter the full URL that points to the root of where your extensions are served by the reverse proxy. Based on our setup, this is the index.json file located at the root of the extensions-repo directory, accessed via the /ext path we configured. The URL should be: https://notes.yourdomain.com/ext/index.json
    • Important: Ensure the URL points directly to the index.json file within the path you configured in NPM.
    • Save the settings. The client might reload or prompt you to.
  7. Install and Test Extensions:

    • Navigate to the "Extensions" section within the Standard Notes client (often in the bottom-left menu).
    • You should now see a list of available extensions (themes, editors) loaded from your self-hosted repository. If it's empty or shows an error, double-check the URL, NPM location configuration, and container logs (docker compose logs extensions_server).
    • Try installing a theme (e.g., "Midnight"). Click "Install".
    • Try installing an editor (e.g., "Bold Editor" or find one of the Markdown editors listed). Click "Install".
    • Activate the theme from the "Appearance" or "Themes" menu.
    • Create a new note or edit an existing one. Use the editor switcher (usually at the bottom right or top of the editor pane) to select the newly installed editor.
    • Verify that the theme applies correctly and the new editor functions as expected.

Outcome: You have successfully deployed a separate container to serve Standard Notes extensions, configured your reverse proxy to route traffic to it, and connected your Standard Notes client to use your self-hosted extensions repository. You can now use advanced editors, themes, and other extensions without relying on the official Standard Notes subscription service.

6. Data Persistence and Backups

Running a self-hosted service means you are solely responsible for the safety and integrity of your data. While Standard Notes uses robust end-to-end encryption, protecting the encrypted data blobs stored on your server is critical. Hardware failure, accidental deletion, software bugs, or security breaches could lead to data loss if you don't have a reliable backup strategy.

How Data is Stored (Docker Volumes)

In our Docker Compose setup, we used a volume mount to persist the Standard Notes data:

volumes:
  - ./sn_data:/var/lib/standardnotes

This line tells Docker to map the directory /var/lib/standardnotes inside the syncserver container to a directory named sn_data on the host machine, located in the same directory as the docker-compose.yml file.

  • Host Directory (./sn_data): This directory on your server's filesystem contains the actual database files and any other persistent data generated by the Standard Notes server.
  • Container Directory (/var/lib/standardnotes): This is the path the application inside the container uses to read and write its data.

By using this volume mount:

  • Data Persists: If you stop and remove the syncserver container (docker compose down), the sn_data directory on your host remains untouched. When you restart the container (docker compose up -d), it remounts this directory, and the application finds its previous data.
  • Data is Accessible: The data is directly accessible on the host filesystem within the sn_data directory, which makes backups much easier.

If you had used a named Docker volume instead (e.g., - sn_volume:/var/lib/standardnotes), the data would be stored in a Docker-managed area on the host (usually /var/lib/docker/volumes/). Backing up named volumes requires slightly different techniques. Using host directory mounts is often simpler for direct backup access.

Importance of Regular Backups

You need backups to recover from:

  • Hardware Failure: Server disk crashes, power surges, etc.
  • Data Corruption: Software bugs (in Standard Notes, Docker, or the OS), filesystem errors.
  • Accidental Deletion: Mistakes made while managing files or containers.
  • Security Incidents: Ransomware, unauthorized access (though E2EE protects note content, account data could be targeted).
  • Disaster Recovery: Fire, flood, or other physical disasters affecting your server location.

Remember, RAID is not a backup! RAID protects against single disk failures but not against file deletion, corruption, or disasters.

Backup Strategies for Docker Volumes

Since we mapped the Standard Notes data to the host directory ./sn_data, backing it up involves backing up that specific directory.

  1. Manual Backup (tar, rsync):

    • You can manually create compressed archives of the data directory.
    • Stopping the Container (Recommended for consistency): For the most consistent backup, it's best to stop the Standard Notes container before copying the files to ensure the database is not being written to during the backup process.
      cd /path/to/your/standardnotes # Navigate to docker-compose dir
      docker compose stop syncserver
      # Create a timestamped backup
      tar czvf /path/to/backups/standardnotes_backup_$(date +%Y%m%d_%H%M%S).tar.gz ./sn_data
      docker compose start syncserver
      
    • Using rsync: rsync can efficiently copy the directory to another location (e.g., a backup disk or remote server). It only transfers changed files after the initial copy.
      # Example: Sync to a locally mounted backup drive
      rsync -avh --delete ./sn_data/ /mnt/backup_drive/standardnotes_backup/
      # --delete ensures files deleted from source are removed from destination
      
    • Automation: These commands can be placed in a shell script and scheduled using cron.
  2. Dedicated Backup Tools:

    • Use general Linux backup tools like BorgBackup, Restic, Duplicati, or Kopia. These offer features like deduplication, encryption, compression, and support for various storage backends (local disk, SSH, cloud storage). Configure them to include the ./sn_data directory in their backup sets. They often handle snapshotting or consistency better than simple tar or rsync on live data, although stopping the container is still the safest approach for database files.
  3. Volume Backup Utilities (for Named Volumes):

    • If you were using named Docker volumes, utilities exist specifically to back them up, often by running a temporary container that mounts the volume and archives its content (e.g., loffel/docker-volume-backup).

Backup Frequency and Retention:

  • Frequency: Depends on how often your notes change and how much data you can afford to lose. Daily backups are common for actively used note systems.
  • Retention: Keep multiple backup copies (e.g., daily for a week, weekly for a month, monthly for a year) to allow recovery from older points in time.
  • Location: Store backups in a separate physical location from your primary server (e.g., another computer, external hard drive stored offsite, cloud storage). The 3-2-1 backup rule is a good guideline: 3 copies of your data, on 2 different media types, with 1 copy offsite.

Restoration Process

Restoring from a backup generally involves:

  1. Stop the Service: docker compose stop syncserver (or docker compose down).
  2. Remove/Replace Existing Data: Delete or move the current contents of the ./sn_data directory (or the named volume).
  3. Extract/Copy Backup: Restore the contents of your backup archive or rsync backup into the ./sn_data directory. Ensure file permissions and ownership are correct (usually Docker handles this if the volume mount is set up, but worth checking).
  4. Restart the Service: docker compose start syncserver (or docker compose up -d).
  5. Verify: Check the logs and connect with a client to ensure the data has been restored correctly.

Workshop Implementing a Basic Backup Strategy

Goal: Create a simple shell script to back up the Standard Notes data directory (./sn_data) to a local backup location and schedule it with cron.

Prerequisites:

  • Working self-hosted Standard Notes setup with data persisted to ./sn_data.
  • Access to the server command line with sudo privileges (for cron).

Steps:

  1. Create a Backup Directory:
    • Choose a location on your server to store backups. Ideally, this should be a separate physical disk or mount point. For this workshop, we'll create a directory in the user's home directory (adjust as needed for a real setup).
      mkdir -p ~/sn_backups
      
  2. Create the Backup Script:
    • Navigate to a suitable location for scripts, e.g., your home directory or /usr/local/bin.
      cd ~
      nano backup_standardnotes.sh
      
    • Paste the following script content:
      #!/bin/bash
      
      # --- Configuration ---
      # Path to the directory containing your docker-compose.yml
      SN_COMPOSE_DIR="/path/to/your/standardnotes"
      # Path to the Standard Notes data directory (relative to SN_COMPOSE_DIR)
      SN_DATA_DIR="sn_data"
      # Path where backups should be stored
      BACKUP_DIR="/home/YOUR_USERNAME/sn_backups" # <-- CHANGE YOUR_USERNAME
      # Number of days to keep backups
      RETENTION_DAYS=7
      # --- End Configuration ---
      
      # Create timestamp
      TIMESTAMP=$(date +%Y%m%d_%H%M%S)
      BACKUP_FILENAME="standardnotes_backup_${TIMESTAMP}.tar.gz"
      BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILENAME}"
      
      echo "Starting Standard Notes backup..."
      
      # Navigate to the docker-compose directory
      cd "${SN_COMPOSE_DIR}" || { echo "Error: Cannot cd to ${SN_COMPOSE_DIR}"; exit 1; }
      
      # Stop the Standard Notes container for data consistency
      echo "Stopping syncserver container..."
      docker compose stop syncserver
      if [ $? -ne 0 ]; then
          echo "Error: Failed to stop syncserver. Aborting backup."
          exit 1
      fi
      
      # Create the compressed archive
      echo "Creating backup archive: ${BACKUP_PATH}"
      tar czf "${BACKUP_PATH}" "${SN_DATA_DIR}"
      if [ $? -ne 0 ]; then
          echo "Error: Failed to create tar archive. Check permissions and disk space."
          # Attempt to restart container even if backup failed
          docker compose start syncserver
          exit 1
      fi
      
      # Restart the Standard Notes container
      echo "Starting syncserver container..."
      docker compose start syncserver
      if [ $? -ne 0 ]; then
          echo "Error: Failed to restart syncserver after backup."
          # Backup was created, but investigate container startup issue
          exit 1
      fi
      
      # Prune old backups
      echo "Pruning backups older than ${RETENTION_DAYS} days in ${BACKUP_DIR}..."
      find "${BACKUP_DIR}" -name "standardnotes_backup_*.tar.gz" -type f -mtime +${RETENTION_DAYS} -delete
      
      echo "Backup completed successfully: ${BACKUP_PATH}"
      exit 0
      
    • Crucially:
      • Replace /path/to/your/standardnotes with the actual absolute path to the directory containing your docker-compose.yml file.
      • Replace YOUR_USERNAME with your actual username in the BACKUP_DIR path, or change the path entirely if desired.
    • Save and exit (Ctrl+O, Enter, Ctrl+X in nano).
  3. Make the Script Executable:
    • bash chmod +x backup_standardnotes.sh
  4. Perform a Manual Test Run:
    • Execute the script directly:
      ./backup_standardnotes.sh
      
    • Check for any errors in the output.
    • Verify that a .tar.gz file was created in your ~/sn_backups directory.
    • Check that the syncserver container was stopped and restarted (docker compose ps).
  5. Schedule with Cron:
    • Open the cron table for editing (usually runs jobs as your user, use sudo crontab -e to run as root if needed, but ensure permissions are handled):
      crontab -e
      
      (Select an editor like nano if prompted for the first time).
    • Add a line to schedule the script. For example, to run it every day at 3:00 AM:
      # Example: Run Standard Notes backup daily at 3:00 AM
      0 3 * * * /home/YOUR_USERNAME/backup_standardnotes.sh >> /home/YOUR_USERNAME/sn_backups/backup.log 2>&1
      
      • Replace YOUR_USERNAME with your actual username and ensure the path to the script is correct.
      • 0 3 * * *: Cron timing (minute 0, hour 3, any day of month, any month, any day of week).
      • >> /home/YOUR_USERNAME/sn_backups/backup.log 2>&1: This redirects both standard output (>>) and standard error (2>&1) to a log file, appending (>>) each time the script runs. This is useful for checking if the backups ran successfully later.
    • Save and exit the crontab editor. Cron will automatically pick up the schedule.
  6. Verify Cron Setup (Optional):
    • Check that the cron job is listed: crontab -l.
    • After the scheduled time passes, check the ~/sn_backups directory for new backups and examine the backup.log file for output.

Outcome: You have created and scheduled a basic automated backup script for your Standard Notes data. While this script provides a fundamental level of protection, remember to enhance it for a production environment by storing backups off-server and potentially using more sophisticated backup tools. You now understand the process of backing up data persisted via Docker host mounts.

7. Server Monitoring and Maintenance

Self-hosting is not a "set it and forget it" affair. Regular monitoring and maintenance are essential to ensure your Standard Notes service remains available, performant, and secure. This involves keeping an eye on server resources, checking application logs, and applying updates in a timely manner.

Importance of Monitoring

Monitoring helps you:

  • Detect Problems Early: Identify issues like low disk space, high CPU usage, or application errors before they cause outages.
  • Understand Resource Usage: Determine if your server specifications are adequate or if you need to upgrade.
  • Ensure Availability: Confirm that your Standard Notes server and related services (like the reverse proxy) are running and accessible.
  • Troubleshoot Performance: Pinpoint bottlenecks by observing resource utilization patterns.
  • Security: Unusual resource usage or log entries can sometimes indicate security issues.

Key Areas to Monitor

  1. System Resources:
    • CPU Usage: High sustained CPU usage might indicate performance issues or runaway processes.
    • RAM Usage: Running out of memory can cause instability or crashes. Monitor swap usage as well (high swap usage often indicates insufficient RAM).
    • Disk Space: Crucial for storing the OS, application data, Docker images, logs, and backups. Running out of disk space will cause failures. Monitor the usage of the filesystem where your Docker data (./sn_data, ./npm_data, etc.) resides.
    • Network I/O: Monitor bandwidth usage, especially if you have data caps or notice slow synchronization.
  2. Container Health:
    • Status: Are the standardnotes_syncserver, extensions_server, and nginx_proxy_manager containers running? (docker compose ps)
    • Resource Consumption: How much CPU and RAM are the individual containers using? (docker stats)
  3. Application Logs:
    • Standard Notes Server Logs: Check for errors, warnings, or unusual patterns (docker compose logs syncserver).
    • Nginx Proxy Manager Logs: Check for access patterns, HTTP errors (like 5xx server errors or 4xx client errors), and SSL certificate issues (docker compose logs npm).
    • Extensions Server Logs: Check for errors serving extension files (docker compose logs extensions_server).
  4. Service Availability:
    • HTTPS Endpoint: Is your Standard Notes URL (https://notes.yourdomain.com) reachable and returning a valid response (even if it's just the Cannot GET / message)? Is the SSL certificate valid?
    • NPM Admin UI: Is the Nginx Proxy Manager UI accessible?

Monitoring Tools

  • Command-Line Tools (Built-in):
    • top / htop: Real-time system resource monitoring (CPU, RAM, processes). htop is more user-friendly (sudo apt install htop).
    • df -h: Check disk space usage (-h for human-readable).
    • free -h: Check RAM and swap usage.
    • docker stats: Real-time resource usage per container.
    • docker compose logs [-f] [service_name]: View container logs (-f to follow).
  • Dedicated Monitoring Applications:
    • Uptime Kuma: A popular, easy-to-use, self-hostable monitoring tool. It provides a web UI to monitor HTTP(S) endpoints, ports, Docker containers, and more, with notifications. Excellent for availability monitoring.
    • Prometheus + Grafana: A powerful combination for collecting time-series metrics (Prometheus) and visualizing them in dashboards (Grafana). Can monitor system resources (using node_exporter), Docker stats (cadvisor), and application-specific metrics. Steeper learning curve but very comprehensive.
    • Netdata: Real-time performance monitoring with auto-discovery and detailed charts. Can run as a Docker container.

Performing Updates

Keeping your software up-to-date is crucial for security and access to new features or bug fixes.

  1. System Updates: Regularly update your host operating system:
    sudo apt update
    sudo apt upgrade -y
    sudo apt autoremove -y # Remove unused dependencies
    # Consider rebooting if kernel or critical libraries were updated
    # sudo reboot
    
  2. Docker Container Updates (Using Docker Compose):
    • Check Release Notes: Before updating, always check the release notes or changelog for the Docker images you are using (standardnotes/server, jc21/nginx-proxy-manager, nginx). Look for any breaking changes or specific upgrade instructions.
    • Pull Latest Images: Navigate to your docker-compose.yml directory and run:
      docker compose pull
      
      (This downloads the newer versions of the images specified in your compose file, if available).
    • Recreate Containers: Apply the updates by recreating the containers:
      docker compose up -d
      
      (Docker Compose is smart enough to only recreate containers whose images have changed).
    • Verify: Check logs and test functionality after updating.
  3. Prune Old Docker Resources (Optional): Over time, unused Docker images, networks, and volumes can consume disk space. Periodically clean them up:
    docker image prune -a # Remove unused images (use with caution, maybe just 'docker image prune' first)
    docker volume prune # Remove unused named volumes
    docker network prune # Remove unused networks
    docker system prune -a # Comprehensive cleanup (read warnings carefully!)
    

Handling Breaking Changes

Sometimes, updates (especially major version bumps) might introduce changes that are incompatible with your current configuration or data format.

  • Read Release Notes: This is the most important step. Developers usually document breaking changes.
  • Backup Before Updating: Always ensure you have a recent, restorable backup before applying major updates.
  • Test in Staging (Advanced): For critical systems, consider setting up a separate staging environment to test updates before applying them to your production server.
  • Pin Image Versions: In your docker-compose.yml, you can specify exact image versions (e.g., standardnotes/server:3.150.0) instead of latest. This prevents automatic updates to potentially incompatible versions when you run docker compose pull, giving you control over when you upgrade. Update the version tag manually when you are ready to upgrade.

Workshop Setting Up Uptime Kuma for Monitoring

Goal: Deploy Uptime Kuma using Docker Compose and configure it to monitor the availability of your self-hosted Standard Notes service and the server itself.

Prerequisites:

  • Working self-hosted Standard Notes setup with HTTPS.
  • Server with Docker and Docker Compose.
  • Port 3001 (or another chosen port) available on the host for Uptime Kuma's UI.

Steps:

  1. Create Uptime Kuma docker-compose.yml:
    • You can add Uptime Kuma to your existing standardnotes/docker-compose.yml or run it separately. Let's add it to the existing file for simplicity, ensuring it's on the same network is not strictly necessary for basic HTTP checks but doesn't hurt.
    • Edit your docker-compose.yml:
      version: '3.7'
      
      services:
        syncserver:
          # ... (syncserver configuration) ...
          networks:
            - sn-network
      
        extensions_server:
          # ... (extensions_server configuration) ...
          networks:
            - sn-network
      
        npm:
          # ... (NPM configuration) ...
          networks:
            - sn-network
      
        # NEW: Uptime Kuma service
        uptime-kuma:
          image: louislam/uptime-kuma:latest
          container_name: uptime_kuma
          volumes:
            - ./uptime_kuma_data:/app/data # Persist Uptime Kuma data
          ports:
            - "3001:3001" # Map internal port 3001 to host port 3001
          restart: unless-stopped
          networks: # Optional: can connect to sn-network if monitoring internal services by name
            - sn-network
      
      networks:
        sn-network:
          name: standardnotes_network
      
    • Note: We map host port 3001 to the container's port 3001. Ensure port 3001 is allowed through your firewall if you want to access the Uptime Kuma UI from outside your local network (e.g., sudo ufw allow 3001/tcp). For internal access only, you could map it like - "127.0.0.1:3001:3001".
    • Save the file.
  2. Start Uptime Kuma:
    • Run docker compose up -d. This will pull the Uptime Kuma image and start the container.
  3. Access Uptime Kuma UI:
    • Open your web browser and navigate to http://YOUR_SERVER_IP:3001.
    • Create an administrator account when prompted.
  4. Add Monitor for Standard Notes HTTPS Endpoint:
    • Click "+ Add New Monitor".
    • Monitor Type: Select HTTP(s).
    • Friendly Name: Standard Notes Server
    • URL: Enter your full HTTPS URL: https://notes.yourdomain.com
    • Check Interval: Adjust as needed (e.g., 60 seconds).
    • Accepted Status Codes: Leave default 200-299. Even though SN root returns Cannot GET / (which might be a 404), the HTTPS connection itself succeeds. You might need to adjust this if Uptime Kuma flags it down due to the 404. Alternatively, monitor a known API path if one exists that returns 200, or simply rely on the successful TLS handshake. A better check might be to monitor the specific sync endpoint if known, e.g., https://notes.yourdomain.com/items/sync, but this might require authentication or specific request types Uptime Kuma can't easily do. For basic availability, checking the domain itself is often sufficient. You can also enable "Ignore TLS/SSL error for HTTPS websites" temporarily for debugging, but it should be off for production monitoring.
    • Configure Notifications (Optional): Set up email, Discord, Telegram, etc., notifications if desired.
    • Click "Save".
  5. Add Monitor for Server Ping (Basic):
    • Click "+ Add New Monitor".
    • Monitor Type: Select Ping.
    • Friendly Name: My Server (Ping)
    • Hostname / IP: Enter your server's public IP address or a domain name pointing to it.
    • Click "Save".
  6. Add Monitor for SSH Port (Optional):
    • Click "+ Add New Monitor".
    • Monitor Type: Select TCP Port.
    • Friendly Name: Server SSH Port
    • Hostname / IP: Enter your server's IP or domain.
    • Port: Enter 22 (or your custom SSH port).
    • Click "Save".
  7. Observe Monitoring:
    • Go back to the Uptime Kuma dashboard. You should see your monitors listed.
    • Initially, they will be gray (pending), then should turn green (up) if everything is configured correctly and the services are reachable. If they turn red (down), investigate the error message provided by Uptime Kuma.

Outcome: You have deployed Uptime Kuma and configured basic monitors to track the availability of your Standard Notes HTTPS endpoint and the server itself via Ping/TCP checks. This provides automated checks and a visual dashboard to quickly assess the health of your service. Explore Uptime Kuma's other monitor types (like Docker Container monitoring) and notification options for more comprehensive oversight.

8. Advanced Configuration and Customization

Beyond the basic setup and extensions, the Standard Notes server offers further configuration options for tailoring its behavior, integrating with email services, and potentially using different database backends (though this adds significant complexity).

Exploring Environment Variables

The standardnotes/server Docker image accepts various environment variables beyond the essential SECRET_KEY_BASE. You can find a list in the official documentation or sometimes by inspecting the image's Dockerfile or startup scripts. Some potentially useful ones include:

  • Database Configuration:
    • DB_CONNECTION: Specifies the database type. Defaults to sqlite. Can potentially be set to postgres or mysql.
    • DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD: Required if using PostgreSQL or MySQL instead of the default SQLite. Note: Setting up and managing an external database significantly increases complexity (requires deploying/managing the database server, networking, backups). SQLite is often sufficient and much simpler for personal or small-group use.
  • Email Configuration (for Password Resets, etc.):
    • EMAIL_HOST: SMTP server hostname (e.g., smtp.gmail.com, smtp.mailgun.org).
    • EMAIL_PORT: SMTP server port (e.g., 587 for TLS, 465 for SSL).
    • EMAIL_HOST_USER: SMTP username.
    • EMAIL_HOST_PASSWORD: SMTP password (use Docker secrets or secure methods).
    • EMAIL_FROM: The "From" email address for outgoing emails (e.g., no-reply@notes.yourdomain.com).
    • EMAIL_SECURE: Set to true if using port 465 (SSL), often false or omitted for port 587 (STARTTLS). Check your provider's requirements.
    • EMAIL_IGNORE_TLS, EMAIL_REQUIRE_TLS: Fine-tuning TLS requirements.
  • Rate Limiting (Advanced): Variables might exist to control API request limits, helping prevent abuse. Check documentation for specifics.
  • Other Options: Settings related to file uploads (if using FileSafe - usually requires S3-compatible storage), logging levels, etc., may be available.

Always consult the official Standard Notes server documentation for the most accurate and up-to-date list of supported environment variables and their usage.

Using Alternative Database Backends (PostgreSQL/MySQL)

While the default SQLite backend (stored in the /var/lib/standardnotes volume) is simple and performs well for many use cases, you might consider PostgreSQL or MySQL if:

  • You anticipate a very large number of users or notes, potentially exceeding SQLite's practical limits for concurrent writes.
  • You already have an existing, managed PostgreSQL or MySQL server that you want to leverage.
  • You require advanced database features or replication capabilities offered by these systems.

Complexity Considerations:

  • Deployment: You need to deploy and manage the PostgreSQL or MySQL server itself (often as another Docker container or a separate service).
  • Networking: Ensure the Standard Notes container can securely connect to the database container/server.
  • Configuration: Set the DB_* environment variables correctly in your Standard Notes docker-compose.yml.
  • Backup: You now need to back up the external database in addition to any file-based volumes used by Standard Notes. Database backups often require specific tools (pg_dump, mysqldump).
  • Migration: Migrating existing data from SQLite to PostgreSQL/MySQL can be complex.

Recommendation: Stick with the default SQLite backend unless you have a compelling, specific reason and the technical expertise to manage an external database system reliably.

Setting Up Email Functionality

Configuring email allows your self-hosted server to send notifications, most importantly password reset emails. Without this, if a user forgets their password, there is no self-service recovery mechanism on your instance.

Steps:

  1. Obtain SMTP Credentials: You need access to an SMTP server. Options include:
    • Transactional Email Services: Mailgun, SendGrid, Amazon SES, Postmark. Often have free tiers suitable for low-volume personal use. Recommended for reliability.
    • Regular Email Providers: Gmail, Outlook, etc. May work, but often have stricter sending limits and might require enabling "less secure app access" or generating app-specific passwords, making them less ideal for server applications.
    • Self-Hosted Email Server: If you run your own email server (e.g., Mailcow, Poste.io), you can use its SMTP credentials. Get the Hostname, Port, Username, and Password for your chosen SMTP service.
  2. Configure Environment Variables: Add the EMAIL_* variables to your .env file or directly under the environment: section for the syncserver in docker-compose.yml (using Docker secrets for the password is best practice if available).
  3. Restart Container: Run docker compose up -d to apply the new environment variables.
  4. Test: Use the "Forgot Password" link on your self-hosted Standard Notes login page (https://notes.yourdomain.com) with an existing account's email address. Check if the email arrives. Debug using container logs (docker compose logs syncserver) if emails fail to send.

Customizing Default Extensions

When you self-host the extensions server (as done in Workshop 5), the list of extensions presented in the client is determined by the index.json file located at the root of the served directory (e.g., ./extensions-repo/index.json).

You can potentially customize this list:

  • Editing index.json: Carefully edit the index.json file within your ./extensions-repo directory. You could remove entries for extensions you don't want to offer or potentially add entries for third-party or custom extensions (if you host their code correctly). Be mindful of the file format and the url field for each extension, which must point to the correct manifest file (usually another .json file) for that specific extension within the served directory structure.
  • Filtering Repositories: Instead of cloning the entire standard-extensions repo, you could selectively clone or copy only the specific extension directories you want and construct a simpler index.json file manually.

Caution: Modifying the extensions list requires understanding the structure and ensuring the paths in index.json correctly resolve to the actual extension manifest files served by your extensions_server container. Incorrect modifications can break the extensions feature.

Workshop Enabling Email Password Resets

Goal: Configure the Standard Notes server to send password reset emails using a transactional email service (Mailgun example, adapt for others).

Prerequisites:

  • Working self-hosted Standard Notes setup with HTTPS.
  • Account with an SMTP provider (e.g., Mailgun - free tier available). You'll need your SMTP Hostname, Port (usually 587), Username, and Password.
  • An existing user account created on your self-hosted server with a valid email address associated with it.

Steps:

  1. Get SMTP Credentials from Provider:
    • Sign up for a service like Mailgun. You might need to verify your domain.
    • Navigate to the SMTP credentials section in your provider's dashboard. Note down:
      • SMTP Hostname (e.g., smtp.mailgun.org)
      • Port (e.g., 587 for TLS/STARTTLS)
      • SMTP Username (e.g., postmaster@sandbox...mgsend.net or your custom domain user)
      • SMTP Password (generate one specifically for this use).
  2. Update .env File:
    • Edit the .env file in your standardnotes project directory:
      nano .env
      
    • Add the following lines, replacing the placeholder values with your actual credentials:
      # Existing SECRET_KEY_BASE=...
      
      # Email Configuration
      EMAIL_HOST=smtp.mailgun.org # <-- Replace with your SMTP host
      EMAIL_PORT=587 # <-- Replace with your SMTP port (587 for TLS, 465 for SSL)
      EMAIL_HOST_USER=your_smtp_username # <-- Replace with your SMTP username
      EMAIL_HOST_PASSWORD=your_smtp_password # <-- Replace with your SMTP password
      EMAIL_FROM=noreply@notes.yourdomain.com # <-- Replace with a desired 'From' address
      EMAIL_SECURE=false # Set to true ONLY if using port 465 (SSL), false/omit for 587 (TLS/STARTTLS)
      # EMAIL_REQUIRE_TLS=true # Often needed for port 587
      
      • Adjust EMAIL_SECURE and potentially add EMAIL_REQUIRE_TLS=true based on your provider's documentation for the chosen port. Port 587 typically uses STARTTLS (EMAIL_SECURE=false, EMAIL_REQUIRE_TLS=true). Port 465 uses direct SSL (EMAIL_SECURE=true).
    • Save and exit.
  3. Ensure env_file is used in docker-compose.yml:
    • Double-check that your syncserver service definition in docker-compose.yml includes the line env_file: - .env.
  4. Restart Standard Notes Server:
    • Apply the configuration changes:
      docker compose up -d --force-recreate syncserver
      
      (Using --force-recreate ensures the container restarts and picks up the new environment variables from the .env file).
  5. Check Logs (Optional):
    • Monitor the logs immediately after restart to catch any obvious configuration errors related to email:
      docker compose logs syncserver
      
  6. Test Password Reset:
    • Open your Standard Notes instance in a browser: https://notes.yourdomain.com.
    • Log out if you are currently logged in.
    • Click the "Log in" button.
    • Click the "Forgot password?" link.
    • Enter the email address associated with an existing account on your self-hosted server.
    • Click "Send Email".
    • Check your email inbox (and spam folder) for the password reset email from the EMAIL_FROM address you configured.
    • Follow the instructions in the email to reset the password.
    • Try logging in with the new password.

Outcome: You have successfully configured your self-hosted Standard Notes server to send emails via SMTP. Users who forget their passwords can now use the self-service password reset functionality, improving the usability of your instance. If emails are not sending, carefully review the SMTP settings, credentials, and the syncserver logs for specific error messages.

9. Troubleshooting Common Issues

Even with careful setup, you might encounter issues when running your self-hosted Standard Notes instance. This section covers common problems and provides a systematic approach to diagnosing and resolving them.

General Troubleshooting Strategy

  1. Identify the Symptom: What exactly is not working? (e.g., Cannot connect, notes not syncing, extensions not loading, container crashing). Be specific.
  2. Check Logs: This is almost always the first step. Examine the logs of the relevant containers (syncserver, npm, extensions_server).
    docker compose logs syncserver
    docker compose logs npm
    docker compose logs extensions_server
    # Use -f to follow logs in real-time
    docker compose logs -f syncserver
    
    Look for error messages, warnings, stack traces, or clues around the time the issue occurred.
  3. Verify Container Status: Are all necessary containers running?
    docker compose ps
    
    If a container is stopped or restarting, investigate its logs.
  4. Check Network Connectivity:
    • DNS: Can your client machine and the server itself resolve the domain name (notes.yourdomain.com) correctly? Use ping and nslookup (or dig).
    • Firewall: Are the necessary ports (usually 80, 443 for NPM; SSH for access) open on the server's firewall (sudo ufw status)?
    • Reverse Proxy: Is NPM running and configured correctly? Check its logs. Can you access the NPM admin UI?
    • Docker Network: Are the containers connected to the shared Docker network (docker network inspect standardnotes_network)? Can containers ping each other by service name within the network (use docker exec -it <container_name> ping <other_service_name>)?
  5. Check Resource Usage: Is the server running out of CPU, RAM, or disk space?
    htop
    df -h
    free -h
    docker stats
    
  6. Configuration Review: Double-check your docker-compose.yml, .env file, NPM proxy host settings, and Standard Notes client settings (Sync URL, Extensions URL) for typos or misconfigurations.
  7. Restart Containers: Sometimes a simple restart can resolve temporary glitches.
    docker compose restart syncserver npm extensions_server
    # Or restart everything
    # docker compose down && docker compose up -d
    
  8. Search Online: Use specific error messages from logs to search online forums, GitHub issues for Standard Notes Server, Nginx Proxy Manager, or Docker. Someone else may have encountered the same problem.

Specific Issues and Solutions

  • Cannot Connect to https://notes.yourdomain.com (Timeout/Connection Refused)
    • DNS: Verify DNS propagation using ping notes.yourdomain.com or dnschecker.org. Ensure the A record points to the correct public IP.
    • Firewall: Check if ports 80 and 443 are allowed in UFW (sudo ufw status).
    • NPM Container: Is the npm container running (docker compose ps)? Are ports 80 and 443 correctly mapped in docker-compose.yml? Check NPM logs (docker compose logs npm).
    • Server Reachability: Can you ping the server's IP address?
  • Getting 502 Bad Gateway Error
    • This usually means the reverse proxy (NPM) successfully received the request but could not connect to the backend service (syncserver or extensions_server).
    • Backend Container Status: Is the syncserver (or extensions_server if accessing /ext) container running (docker compose ps)?
    • Backend Container Logs: Check the logs of the backend container (docker compose logs syncserver) for errors during startup or request processing.
    • NPM Configuration: In NPM's Proxy Host settings:
      • Is the "Forward Hostname / IP" correct (should be the service name, e.g., syncserver)?
      • Is the "Forward Port" correct (e.g., 3000 for syncserver, 80 for extensions_server)?
    • Docker Network: Are NPM and the backend service on the same Docker network defined in docker-compose.yml? Use docker network inspect <network_name> to verify.
  • Getting 500 Internal Server Error
    • This indicates an error occurred within the backend application (syncserver) while processing the request.
    • Check syncserver Logs: docker compose logs syncserver is essential here. Look for detailed error messages or stack traces. The error might be related to database issues, configuration problems, or bugs in the Standard Notes server code.
  • Notes Not Syncing Between Clients
    • Server Connection: Ensure all clients are configured to point to the correct self-hosted server URL (https://notes.yourdomain.com) in Advanced Options.
    • Network Issues: Check for intermittent network problems on the client or server side.
    • Server Logs: Check syncserver logs for any errors related to /items/sync requests.
    • Client Logs (If Available): Some Standard Notes clients might have developer consoles or logging options that could provide clues.
    • Resource Limits: Is the server under heavy load (CPU/RAM)?
    • WebSockets: Ensure Websockets support is enabled in your NPM Proxy Host configuration for notes.yourdomain.com. While sync can work without it, Websockets improve real-time performance.
  • Extensions Not Loading / Extensions List Empty
    • Client Configuration: Double-check the "Custom Extensions Repository" URL in the client's Advanced Options. It should point to the index.json file served via your reverse proxy (e.g., https://notes.yourdomain.com/ext/index.json).
    • extensions_server Container: Is the extensions_server (Nginx) container running? Check its logs (docker compose logs extensions_server). Are there errors about file permissions or configuration?
    • NPM Location Configuration: In NPM, check the "Location" /ext (or your chosen path) defined for your notes.yourdomain.com proxy host. Ensure it forwards correctly to the extensions_server service on port 80.
    • File Paths/Permissions: Verify the extensions-repo directory was cloned correctly and the index.json file exists at the root. Ensure the Nginx container has read access (the :ro flag in the volume mount helps prevent accidental changes but shouldn't block reads).
    • CORS Issues: Check the browser's developer console (usually F12) on the Standard Notes client page for Cross-Origin Resource Sharing (CORS) errors when it tries to load extensions. The add_header Access-Control-Allow-Origin '*'; line in the nginx-extensions.conf is intended to prevent this, but ensure it's correctly applied.
  • Container Crashing or Restarting (docker compose ps shows restarting)
    • Check Logs Immediately: docker compose logs <container_name> (without -f). Look at the very last messages before it crashed. This often contains the fatal error.
    • Resource Exhaustion: Did the container run out of memory? Check docker stats or system monitoring tools around the time of the crash. You might need to increase the server's RAM or configure memory limits for the container in docker-compose.yml (though often better to give Docker access to sufficient host RAM).
    • Configuration Errors: Check environment variables, mounted configuration files, and volume paths for typos or incorrect settings.
    • Disk Space: Is the host server out of disk space? (df -h)
    • Data Corruption: In rare cases, corrupted data in a volume could cause crashes on startup. Try restoring from a backup.
  • Password Reset Email Not Arriving
    • Check syncserver Logs: Look for errors related to SMTP connection, authentication, or sending emails after triggering a password reset.
    • SMTP Credentials/Configuration: Double-check EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_FROM, EMAIL_SECURE in your .env file. Ensure they match your provider's requirements exactly.
    • Firewall (Outbound): Does your server's firewall allow outgoing connections on the SMTP port (e.g., 587 or 465)? (UFW default allow outgoing usually covers this, but check if you have custom rules).
    • Email Provider Issues: Check your SMTP provider's dashboard for sending logs or blocks. Are you hitting sending limits? Is your account active? Did they block the attempt?
    • Spam Folder: Check the recipient's spam/junk folder.
    • Recipient Server Issues: The receiving email server might be rejecting the email.

Workshop Debugging a Failing Connection

Goal: Systematically diagnose why a Standard Notes client cannot connect to the self-hosted server URL (https://notes.yourdomain.com), simulating a common failure scenario.

Scenario: Your Standard Notes client suddenly shows "Cannot connect to server" or similar errors when trying to sync or log in to https://notes.yourdomain.com.

Steps:

  1. Verify Client Configuration:

    • Action: Open Standard Notes client -> Advanced Options.
    • Check: Confirm the "Sync Server" URL is exactly https://notes.yourdomain.com (no typos, correct HTTPS).
  2. Check Basic Network Reachability (Client Side):

    • Action: Open a terminal or command prompt on your client machine.
    • Command: ping notes.yourdomain.com
    • Interpretation:
      • Successful Ping: DNS is likely resolving correctly, and there's basic network connectivity. Move to Step 3.
      • Unknown Host / Cannot Resolve: DNS issue. Check your DNS records at the registrar. Wait for propagation. Check your client device's network settings/DNS servers.
      • Request Timeout: DNS might be resolving, but the server isn't responding to pings (ICMP might be blocked by a firewall), or there's a network routing issue. Proceed to Step 3, but keep firewall/routing in mind.
  3. Check HTTPS Endpoint (Client Side):

    • Action: Open a terminal/command prompt on your client machine.
    • Command: curl -I https://notes.yourdomain.com (The -I flag fetches headers only).
    • Interpretation:
      • HTTP/2 200 OK (or similar 2xx/3xx): Server is responding correctly over HTTPS at the domain root. The issue might be specific to the sync API path or client state. Check server logs (Step 5).
      • HTTP/1.1 404 Not Found (or similar 4xx): Still indicates the server is responding over HTTPS, just not for the root path /. This is often normal for the SN server root. Check server logs (Step 5).
      • Connection Refused / Timeout: The server is not accepting connections on port 443. Go to Step 4 (Server Firewall/NPM).
      • SSL Certificate Error: Problems with the SSL certificate (expired, wrong domain, untrusted). Check NPM configuration (Step 4) and ensure certs are renewing.
      • 5xx Server Error (e.g., 500, 502, 503, 504): The reverse proxy or backend server encountered an error. Go to Step 5 (Server Logs).
  4. Check Server Firewall and Reverse Proxy (Server Side):

    • Action: SSH into your server.
    • Command (Firewall): sudo ufw status
    • Check: Ensure ports 80 (http) and 443 (https) show ALLOW IN from Anywhere (or your specific IP range). If not, allow them: sudo ufw allow https.
    • Command (NPM Status): docker compose ps (in your standardnotes directory)
    • Check: Is the npm container running (State: Up)? Are ports 80:80 and 443:443 listed in the PORTS column?
    • Command (NPM Logs): docker compose logs npm
    • Check: Look for recent errors related to your domain, SSL certificates, or failures connecting to the backend (syncserver). Fix any issues found (e.g., regenerate certs in NPM UI, correct forward host/port).
  5. Check Standard Notes Server Logs (Server Side):

    • Action: SSH into your server.
    • Command: docker compose logs syncserver
    • Check: Look for errors around the time the client tried to connect. Are there database errors, configuration errors, or errors processing requests? Address any specific errors found.
  6. Check Docker Networking (Server Side):

    • Action: SSH into your server.
    • Command: docker network inspect standardnotes_network (or your network name)
    • Check: Verify that both the npm container and the syncserver container are listed under "Containers".
    • Command (Internal Ping Test): docker exec -it standardnotes_syncserver ping npm and docker exec -it nginx_proxy_manager ping syncserver (Use actual container names).
    • Interpretation: If pings fail, there's a Docker networking issue. Check docker-compose.yml network definitions. Ensure containers are attached to the correct network. Sometimes recreating the network and containers helps (docker compose down, docker network rm standardnotes_network, docker compose up -d).
  7. Check Server Resources (Server Side):

    • Action: SSH into your server.
    • Commands: htop, df -h, free -h, docker stats
    • Check: Is CPU pegged at 100%? Is RAM/Swap full? Is the disk partition containing /path/to/standardnotes/sn_data or /var/lib/docker full? Resource exhaustion can cause connection failures or instability. Free up resources or upgrade the server if necessary.

Outcome: By following these steps, you systematically checked potential failure points from the client-side DNS and connectivity, through the server's firewall and reverse proxy, down to the backend application logs and server resources. This methodical approach helps pinpoint the root cause of the connection failure, allowing you to apply the correct fix.

Conclusion

Embarking on the journey of self-hosting Standard Notes is a rewarding endeavor that places you firmly in control of your most valuable digital asset: your personal notes and knowledge base. Throughout this guide, we've navigated the process from fundamental concepts to advanced configurations, equipping you with the knowledge and practical skills needed to run your own secure and private note-syncing service.

We began by understanding the Standard Notes ecosystem, appreciating the roles of the client applications, the zero-knowledge Syncing Server, and the optional Extensions Server, all underpinned by robust end-to-end encryption. We then meticulously prepared a suitable Linux server environment, emphasizing the power and convenience of Docker and Docker Compose for streamlined deployment and management.

Our practical workshops led you through deploying the core Syncing Server, initially testing connectivity, and then crucially securing it with HTTPS using Nginx Proxy Manager and Let's Encrypt certificates – an essential step for protecting data in transit. We unlocked the full potential of Standard Notes by deploying the Extensions Server, granting access to powerful editors and themes hosted entirely on your infrastructure.

Recognizing that self-hosting comes with responsibility, we delved into critical operational aspects: implementing data persistence using Docker volumes and establishing a basic automated backup strategy with cron and tar. We explored server monitoring using tools like Uptime Kuma to ensure availability and performance, and discussed the importance of regular maintenance, including system and container updates. Finally, we addressed advanced configurations like enabling email password resets and provided a systematic troubleshooting methodology to tackle common issues.

By successfully setting up and managing your own Standard Notes instance, you've gained more than just a private note-taking app. You've practiced essential sysadmin skills, worked with containerization, managed web servers and reverse proxies, handled TLS/SSL certificates, and taken tangible steps towards greater digital sovereignty.

The path of self-hosting is one of continuous learning. We encourage you to delve deeper, explore alternative tools, refine your backup and monitoring strategies, and perhaps even contribute back to the open-source projects that make this possible. Your self-hosted Standard Notes instance is a testament to your ability to manage your own digital infrastructure, ensuring your thoughts and ideas remain truly yours, secure and accessible under your control.