If you use Obsidian, you’ve probably wanted to share a note with someone without exporting a PDF or pasting into a Google Doc. The Share Note plugin by Alan Grainger solves this perfectly. One command, and you get a clean shareable link with your theme intact. The problem is that your notes get uploaded to note.sx, a server you don’t control. For personal notes that’s probably fine. For anything you actually care about, it’s worth the 15 minutes to run your own.
The project is fully open source. The server component ships as a Docker image, the plugin supports custom server URLs out of the box, and the whole thing is simple enough that you’ll spend more time picking a subdomain than actually setting it up.
What You Need
You need a VPS with Docker installed, a domain you control, and about 15 minutes. That’s genuinely it. The server is lightweight enough to run alongside other things on the cheapest VPS tier any provider offers. I’m running mine on a Hetzner CX22 that also hosts a PostgreSQL database and other services, and the note-sharing server barely registers on resource usage.
For this guide I’m using Caddy as the reverse proxy because it handles TLS certificates automatically. No certbot cron jobs, no renewal scripts, no thinking about it at all. If you already have Nginx running, you can adapt the proxy config, but Caddy’s auto-TLS is genuinely hard to beat for small self-hosted setups.
A VPS with Docker and Docker Compose installed, SSH access, a domain with DNS you can edit, and Caddy installed on the VPS. If you don’t have Caddy yet, it’s a one-liner on Ubuntu: install from the official Caddy repos.
Setting Up the Server
SSH into your VPS and create a directory for the project. I keep mine in /opt/notesx but anywhere works.
mkdir -p /opt/notesx && cd /opt/notesx
Create the environment file. The only values that really matter are BASE_WEB_URL and HASH_SALT. The salt can be any random string, and the base URL is whatever subdomain you’re going to point at this server.
cat > .env << 'EOF'
BASE_WEB_URL=https://notes.yourdomain.com
HASH_SALT=your-random-string-here
FOLDER_PREFIX=1
MAXIMUM_UPLOAD_SIZE_MB=10
ALLOW_NEW_USERS=false
EOF
Generate a proper salt instead of typing something predictable:
openssl rand -hex 32
Now create the Docker Compose file. The key detail here is binding to 127.0.0.1:3000 instead of 0.0.0.0:3000. You don’t want the server directly exposed to the internet. Caddy handles all the public-facing traffic.
# docker-compose.yml
services:
notesx-server:
image: ghcr.io/note-sx/server:latest
container_name: notesx-server
ports:
- '127.0.0.1:3000:3000'
restart: always
env_file:
- .env
volumes:
- ./data/db:/notesx/db
- ./data/files:/notesx/userfiles
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:3000/v1/ping || exit 1
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
Start it up:
docker compose up -d
Verify it’s running:
curl http://localhost:3000/v1/ping
You should get back ok. If you don’t, check docker logs notesx-server for errors.
DNS and Caddy
Point your subdomain at your VPS. Add an A record in your DNS provider pointing to your server’s IP. If you’re using Cloudflare, make sure the proxy is off (gray cloud, DNS only). Caddy needs direct access to the server to provision TLS certificates via Let’s Encrypt, and Cloudflare’s proxy gets in the way of that.
If you leave Cloudflare’s proxy enabled (orange cloud), Caddy can’t complete the ACME challenge for your TLS certificate. Set it to DNS only. Caddy’s auto-TLS handles everything from there.
Edit your Caddyfile (usually at /etc/caddy/Caddyfile) to reverse proxy to the Docker container:
notes.yourdomain.com {
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
}
reverse_proxy localhost:3000
}
If you already have other sites in your Caddyfile, just add this block alongside them. Caddy handles multiple sites in a single file without any extra configuration.
Open your firewall for HTTP and HTTPS if you haven’t already:
ufw allow 80/tcp
ufw allow 443/tcp
Reload Caddy:
systemctl reload caddy
Within seconds, Caddy will provision a Let’s Encrypt certificate for your subdomain. You can verify by checking the logs:
journalctl -u caddy -n 20 --no-pager
Look for certificate obtained successfully. If you see DNS errors, your A record hasn’t propagated yet. Give it a few minutes and Caddy will automatically retry.
Connecting the Obsidian Plugin
The Share Note plugin stores its configuration in your vault at .obsidian/plugins/share-note/data.json. Open that file and change the server field to your domain. You also need to clear the existing uid and apiKey so the plugin registers a fresh account on your server instead of trying to authenticate against note.sx.
{
"server": "https://notes.yourdomain.com",
"uid": "",
"apiKey": "",
...
}
Before you restart Obsidian, temporarily enable registration on your server. The ALLOW_NEW_USERS flag controls whether the server accepts new accounts. You set it to false earlier, which is the right default, but you need it open for your initial registration.
sed -i 's/ALLOW_NEW_USERS=false/ALLOW_NEW_USERS=true/' /opt/notesx/.env
cd /opt/notesx && docker compose down && docker compose up -d
Running docker compose restart does not reload environment variables. You need docker compose down && docker compose up -d for .env changes to take effect. This one will bite you if you forget it.
Now restart Obsidian (or reload the Share Note plugin) and try sharing a note. The plugin will automatically register with your server and generate a new API key. Your shared note URL will use your custom domain instead of note.sx. If you’ve set up everything correctly, the link should work immediately.
Once you’ve confirmed it works, lock registration back down:
sed -i 's/ALLOW_NEW_USERS=true/ALLOW_NEW_USERS=false/' /opt/notesx/.env
cd /opt/notesx && docker compose down && docker compose up -d
Verify the change took effect inside the container:
docker exec notesx-server env | grep ALLOW
You should see ALLOW_NEW_USERS=false. Now nobody else can create accounts on your server.
What You End Up With
Your shared notes now live on your own server, under your own domain, with TLS certificates that renew themselves. The Docker container handles the note storage and API, Caddy handles TLS and reverse proxying, and the Obsidian plugin works exactly the same as before except the URLs point to your infrastructure.
The data.json config syncs across all your devices through whatever Obsidian sync method you use, so every device will automatically use your self-hosted server. No per-device configuration needed.
If something breaks, the logs are straightforward. docker logs notesx-server shows you the application logs, journalctl -u caddy shows the proxy and TLS logs. Between those two you can diagnose basically anything.
Closing Thoughts
This entire guide is also available as a shared Obsidian note, hosted on the very server it describes. Meta, I know.
Self-hosting isn’t always worth the effort. For a lot of services, the managed version is fine and the maintenance overhead of running your own instance isn’t justified. But note sharing is one of those cases where the setup is genuinely trivial, the maintenance is essentially zero (Docker auto-restarts, Caddy auto-renews), and the payoff is real. Your notes stay on your hardware, your links use your domain, and you don’t have to wonder what happens if someone else’s free service shuts down.
Credit where it’s due: Alan Grainger built both the Share Note plugin and the self-hostable server. The documentation lives at docs.note.sx. The whole stack is open source and the Docker image just works. That’s increasingly rare and worth recognizing.
🎯 Quick Reference
- ✓ The note-sx server runs as a single Docker container on any VPS
- ✓ Caddy handles TLS automatically — no certbot, no cron jobs, no thinking about it
- ✓ Bind the container to 127.0.0.1, not 0.0.0.0 — let Caddy handle public traffic
- ✓ Clear your plugin's uid and apiKey when switching to a self-hosted server
- ✓ docker compose restart doesn't reload .env — use down + up instead
- ✓ Lock ALLOW_NEW_USERS to false after registering your account
Enjoyed this article? Share it with others!