Notesnook Sync Server on Ubuntu 24.04 on Azure User Guide
Overview
This image runs the Notesnook Sync Server, the self-hostable backend behind the Notesnook note taking apps, on Ubuntu 24.04 LTS. Notes are encrypted on your device before they are sent, so the server only ever stores ciphertext that it cannot read. Running it yourself means your notes sync between your devices through infrastructure you own, and never touch the vendor's cloud.
The server self-hosts as a small set of services, orchestrated by Docker Compose and managed by systemd:
- notesnook-server - the sync server proper, on
:5264. It exposes a REST API plus the SignalR hub at/hubs/sync/v2that carries every encrypted note. - identity-server - accounts and tokens, on
:8264 - sse-server - server-sent events, so a change on one device pushes to the others in real time, on
:7264 - monograph-server - renders notes you explicitly choose to publish, on
:6264 - notesnook-db - MongoDB 7.0 as a single-node replica set, holding the encrypted note catalogue and the account store. It has no host port and is reachable only from inside the appliance.
- notesnook-s3 - a MinIO object store holding your encrypted attachment blobs, on
:9000. It is published because the Notesnook clients upload and download attachments directly using server-issued presigned URLs.
nginx on port 80 serves an appliance status page listing your endpoint URLs, and answers an unauthenticated /healthz for load-balancer probes. The client-facing APIs stay on their native upstream ports, so the URLs you enter in the app are exactly the ones upstream documents.
What is included:
- The full Notesnook self-host stack run under Docker Compose via a
notesnook.servicesystemd unit, from images pinned by digest - Every server secret generated fresh per-VM on first boot - the API signing secret, the object-store root credentials and the instance name - so no baked default survives into the image
- Account registration closed by default. Upstream ships open self-registration; this image does not. You open a one-shot window with
sudo notesnook-claim, and it closes itself again the moment your account exists. - A fail-closed secret guard that refuses to open the public listeners if a known upstream default credential is ever live
sudo notesnook-selftest, which pushes a real encrypted note through the sync protocol, fetches it back on a second device and asserts it returns byte-identical- The encrypted note catalogue and attachment blobs on a dedicated Azure data disk at
/var/lib/notesnook, captured into the image - An unauthenticated
/healthzendpoint for Azure Load Balancer health probes - 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; size up if you have many devices or large attachments.
NSG inbound: allow 22/tcp from your management network, and from the devices you sync with allow 5264/tcp (sync), 8264/tcp (identity), 7264/tcp (events), 6264/tcp (published notes) and 9000/tcp (encrypted attachments). Port 80/tcp serves the status page and the health probe.
Assign a static public IP. The endpoint URLs are derived from the VM's public IP on first boot and are what your apps are configured with. If the IP later changes, your apps point at the wrong address until you update them.
The appliance serves plain HTTP. For production, put your own domain and TLS in front of it.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal, search the Marketplace for Notesnook Sync Server on Ubuntu 24.04 LTS by cloudimg and select Create. Choose your subscription, resource group and region, pick Standard_B2s or larger, and select SSH public key authentication with username azureuser. On the Networking tab, open the ports listed in the prerequisites. Leave the data disk that ships with the image attached - it holds your encrypted note store.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-notes-rg \
--name notesnook-01 \
--image cloudimg:notesnook-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az network nsg rule create --resource-group my-notes-rg \
--nsg-name notesnook-01NSG --name notesnook-ports --priority 1010 \
--destination-port-ranges 80 5264 6264 7264 8264 9000 --access Allow --protocol Tcp
First boot generates every per-VM secret and starts the stack. Allow two to three minutes before the endpoints answer.
Step 3 - Connect to your VM
ssh azureuser@<vm-ip>
Step 4 - Confirm the services are running
systemctl is-active docker nginx notesnook notesnook-firstboot
Expected output:
active
active
active
active
sudo docker compose -f /opt/notesnook/compose.yaml ps --format 'table {{.Service}}\t{{.Status}}\t{{.Ports}}'
Expected output - six services, all Up and healthy:
SERVICE STATUS PORTS
identity-server Up 2 minutes (healthy) 0.0.0.0:8264->8264/tcp
monograph-server Up 2 minutes (healthy) 0.0.0.0:6264->3000/tcp
notesnook-db Up 2 minutes (healthy) 27017/tcp
notesnook-s3 Up 2 minutes (healthy) 0.0.0.0:9000->9000/tcp
notesnook-server Up 2 minutes (healthy) 0.0.0.0:5264->5264/tcp
sse-server Up 2 minutes (healthy) 0.0.0.0:7264->7264/tcp

Each server answers its own health endpoint, and the appliance answers an unauthenticated /healthz on port 80 that you can point an Azure Load Balancer probe at:
for s in 'sync 5264/health' 'identity 8264/health' 'sse 7264/health' 'monograph 6264/api/health'; do
n=${s%% *}; p=${s#* }
printf ' %-11s -> HTTP %s\n' "$n" "$(curl -s -o /dev/null -w '%{http_code}' -m 6 http://127.0.0.1:$p)"
done
printf ' %-11s -> HTTP %s\n' 'appliance' "$(curl -s -o /dev/null -w '%{http_code}' -m 6 http://127.0.0.1/healthz)"
Expected output:
sync -> HTTP 200
identity -> HTTP 200
sse -> HTTP 200
monograph -> HTTP 200
appliance -> HTTP 200
Account registration is closed until you claim the instance, so a signup attempt is refused by the server itself:
curl -s -m 20 -X POST http://127.0.0.1:5264/users \
--data-urlencode 'email=someone-else@example.com' \
--data-urlencode 'password=hunter2hunter2' \
--data-urlencode 'client_id=notesnook'
Expected output:
{"error":"Creating new accounts is not allowed."}

Step 5 - Retrieve your endpoint URLs and per-VM secrets
sudo cat /root/notesnook-credentials.txt
Expected output (your values will differ - every secret is unique to your VM):
# Notesnook Sync Server (self-hosted) - generated on first boot by
# notesnook-firstboot.service. These per-VM secrets are unique to THIS VM.
NOTESNOOK_INSTANCE_NAME=cloudimg-a6ad157c
NOTESNOOK_MINIO_ACCESS_KEY=notesnook-s3-********
NOTESNOOK_MINIO_SECRET_KEY=****************************
# --- Point your Notesnook app at this server -------------------------------
# In the Notesnook app: Settings -> Servers -> Custom server URLs
NOTESNOOK_SYNC_URL=http://20.85.247.166:5264
NOTESNOOK_IDENTITY_URL=http://20.85.247.166:8264
NOTESNOOK_SSE_URL=http://20.85.247.166:7264
NOTESNOOK_MONOGRAPH_URL=http://20.85.247.166:6264
NOTESNOOK_ATTACHMENTS_URL=http://20.85.247.166:9000

The object-store credentials are there for administration and backup. You do not need them to use Notesnook.
Step 6 - Claim the instance and create your account
Upstream's self-host default leaves registration open, which means anyone who can reach your identity server could create an account on it. This image ships with registration closed, and gives you a one-shot window instead:
sudo notesnook-claim
Expected output:
Opening the registration window on this instance...
Registration is now OPEN on this instance for up to 60 minutes.
Create your account now from the Notesnook app (desktop, mobile or web):
Settings -> Servers -> Custom server URLs, then Sign up.
The window closes automatically the moment your account exists - you do not
need to run anything else. To close it immediately:
sudo /usr/local/sbin/notesnook-signup-guard.sh --close-now
Now open Notesnook on your device, go to Settings -> Servers -> Custom server URLs, and enter the four URLs from Step 5 (sync, identity, events and published-notes). Save, then choose Sign up and create your account with your e-mail address and a strong password.
Your password is what derives your encryption key, and it never leaves your device. There is no server-side password reset that can recover your notes - if you lose the password, the ciphertext on the server cannot be decrypted by anyone, including you. Save it, and your recovery key, somewhere safe.
Within about twenty seconds of your account being created, the signup guard closes the window again. You can read the current state at any time:
grep '^DISABLE_SIGNUPS=' /opt/notesnook/.env
While the claim window is still open this reports DISABLE_SIGNUPS=false; once your account exists and the guard has closed the window it reports DISABLE_SIGNUPS=true, which is the state your instance should sit in from then on:
DISABLE_SIGNUPS=true
If you later need to add a second account, re-open the window deliberately with sudo notesnook-claim --force.
Step 7 - Prove that sync actually works
The image ships a self-test that exercises the real sync protocol rather than just checking that the servers answer. It registers a throwaway account, pushes an encrypted note from one device, fetches it back as a different device, and asserts the ciphertext returns byte-identical - then deletes the throwaway account and restores your registration setting:
sudo notesnook-selftest
Expected output:
[notesnook-selftest] registered throwaway account cloudimg-selftest-....@notesnook.invalid
[notesnook-selftest] pushed encrypted note cloudimg-selftest-.... from device A
[notesnook-selftest] fetched from device B (server metadata: {'synced': True})
[notesnook-selftest] ciphertext and iv round-tripped byte-identically (44 bytes of cipher)
[notesnook-selftest] PASS: encrypted note synced and returned byte-identical
[notesnook-selftest] throwaway account deleted

This is the check to run after any change to networking, DNS or TLS in front of the appliance.
Step 8 - Connect your other devices
Repeat Step 6's server-URL configuration on every device - Notesnook desktop, iOS, Android and web all support custom server URLs. Sign in with the same account, and your notes sync through your own server. Each device registers itself with the sync server, so a new device performs a full reset-sync and pulls your whole encrypted note history down on first login.
Attachments upload directly from your device to the object store on port 9000, so that port must be reachable from every device, not just from the server.
Step 9 - Optional, enable e-mail for password reset and 2FA
Signup and sign-in work with no mail server configured, because a self-hosted instance marks new accounts confirmed on creation. E-mail is only needed for self-service password reset and e-mail two-factor codes. To enable it, add your SMTP settings and restart the stack:
sudo grep -n '^SMTP_' /opt/notesnook/.env
Fill in SMTP_HOST, SMTP_PORT, SMTP_USERNAME and SMTP_PASSWORD, then run sudo systemctl restart notesnook.service.
Health checks and troubleshooting
Point Azure Load Balancer or Application Gateway health probes at http://<vm-ip>/healthz, which returns 200 ok without authentication.
curl -s -o /dev/null -w 'appliance /healthz -> HTTP %{http_code}\n' http://127.0.0.1/healthz
If a server is not answering, check its logs:
sudo docker compose -f /opt/notesnook/compose.yaml logs --tail 40 notesnook-server
First boot writes its progress to the journal, which is the place to look if the endpoint URLs or the credentials file are missing:
sudo journalctl -u notesnook-firstboot.service --no-pager | tail -30
The appliance refuses to open its public listeners if a known upstream default credential is ever live in its configuration. You can run that guard by hand:
sudo /usr/local/sbin/notesnook-secret-guard.sh
Expected output:
[...] notesnook-secret-guard: OK - no known upstream default credential is live (5 deny-list entries checked)
If your VM's public IP changes, the endpoint URLs stored in /opt/notesnook/.env and in the credentials file still refer to the old address. Update AUTH_SERVER_PUBLIC_URL, MONOGRAPH_PUBLIC_URL and ATTACHMENTS_SERVER_PUBLIC_URL in /opt/notesnook/.env, restart with sudo systemctl restart notesnook.service, and re-enter the new URLs in your apps.
Licensing
This image bundles the Notesnook Sync Server, which is licensed under the GNU Affero General Public License v3.0. MongoDB (SSPL-1.0) and MinIO (AGPL-3.0) are included as internal dependencies of the appliance. The full notice, with the exact pinned image digests and upstream locations, ships in the image:
cat /usr/share/doc/cloudimg/third-party-licences/BUNDLED-COMPONENTS-NOTICE.txt
Note that the Notesnook client apps live in a separate repository under GPL-3.0 and are not part of this image, which ships the server only.
Support
cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk with your Azure subscription ID and the VM name.