You already write everything in Obsidian. You’ve built a vault full of notes, links, and ideas that actually reflect how you think. The question is: why is getting those notes onto the public web so painful?
Most answers to this problem involve a proprietary sync service, a SaaS publishing platform, or some Rube Goldberg contraption that breaks the moment you change the folder structure. This guide shows you a different way — one you control end to end. By the time you’re done, you’ll have your Obsidian vault served as a fast, beautiful static site from your own Ubuntu server, published with a single command.
What you’ll end up with:
- Your Obsidian notes published as a Quartz static site
- Served by nginx over HTTPS with a Let’s Encrypt certificate
- Auto-renewed TLS, no ongoing maintenance
- A one-command publish script you can invoke from anywhere — or wire into Obsidian’s command palette
Prerequisites:
- A Mac with Homebrew installed
- An Ubuntu 24.04 server with SSH access and a domain pointing to it
- Obsidian installed and a vault you’re ready to publish (partially or fully)
- Basic comfort with the terminal
Architecture Overview
The pieces fit together like this:
- Obsidian vault — your notes live here, organized however you like
- Quartz — a static site generator built specifically for Obsidian vaults; it understands wiki-links, tags, backlinks, and graph view out of the box
- rsync — ships the compiled site to your server over SSH
- nginx — serves the static files to visitors
- Let’s Encrypt / Certbot — handles HTTPS automatically
Quartz runs on your Mac and produces a public/ directory of static HTML, CSS, and JS. rsync syncs that directory to your server. nginx serves it. Nothing runs on the server to generate pages — it’s all static, which means it’s fast, cheap, and resilient.
Part 1: Mac Setup (Quartz)
Step 1: Install Node.js
Quartz v4 requires Node.js v22 or higher.
brew install node
node -v # should be v22 or higherWhat this does: Installs the Node.js runtime that Quartz uses to build your site. If node -v shows an older version, run brew upgrade node to update.
Step 2: Clone and Initialize Quartz
cd ~
git clone https://github.com/jackyzha0/quartz.git quartz-user
cd quartz-user
npm iWhat this does: Pulls down the Quartz source code into a directory called quartz-user in your home folder, then installs its dependencies. You’ll work from this directory going forward.
Step 3: Link Your Obsidian Vault
Instead of copying your notes into Quartz’s content folder, you symlink it. This means Quartz always reads from your live vault — no manual syncing needed.
rm -rf content
ln -s "/Users/user/Notes" contentWhat this does: Removes the placeholder content/ directory and replaces it with a symbolic link pointing to your Obsidian vault. When Quartz looks for content, it reads directly from your notes. Edit in Obsidian, publish from Quartz — same files, no duplication.
Step 4: Configure Quartz
Edit quartz.config.ts in your Quartz directory and update these fields:
const config: QuartzConfig = {
configuration: {
pageTitle: "Your Site Name",
pageTitleSuffix: " | example.com",
enableSPA: true,
enablePopovers: true,
analytics: null,
locale: "en-US",
baseUrl: "example.com", // no protocol, no trailing slash
ignorePatterns: ["private", "templates", ".obsidian"],
// ... rest of defaults are fine
},
plugins: {
// defaults are good — includes full Obsidian compatibility
},
}What each setting does:
pageTitle— the name that appears in the browser tab and site headerpageTitleSuffix— appended to every page title; useful for SEObaseUrl— your domain, withouthttps://or a trailing slash; Quartz uses this to build absolute URLsignorePatterns— folders and patterns Quartz will skip entirely when building the site; add any folder you don’t want published (personal notes, drafts, Obsidian system folders)analytics: null— disables analytics by default; add Plausible or Umami later if you want privacy-respecting tracking
The .obsidian system folder is excluded by default but double-check your ignorePatterns list against your own vault structure. Any folder with private in the path won’t be published.
Step 5: Preview Locally
npx quartz build --serveOpen http://localhost:8080 in your browser. You’ll see your notes rendered as a navigable site with backlinks, graph view, and search. Edit notes in Obsidian, rebuild, and refresh to see changes.
Part 2: Server Setup (Ubuntu 24.04)
SSH into your server:
ssh user@example.comStep 6: Install nginx
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginxWhat this does: Updates your package list and installs nginx, a lightweight, high-performance web server. It starts automatically after installation.
Step 7: Create the Web Root
sudo mkdir -p /var/www/example.com
sudo chown user:user /var/www/example.comWhat this does: Creates the directory where your site’s static files will live, then transfers ownership to your user account so rsync can write to it without sudo.
Step 8: Configure nginx
sudo nano /etc/nginx/sites-available/example.comPaste this configuration:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
error_page 404 /404.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
# Cache static assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 7d;
add_header Cache-Control "public, immutable";
}
}What this does: Defines a virtual host for your domain. The try_files directive tells nginx to try serving the requested path as a file, then as an .html file (so /about works without typing /about.html), then as a directory, and return 404 if nothing matches. The cache block tells browsers to keep static assets for 7 days — those files don’t change between deploys, so this saves bandwidth and speeds up repeat visits.
Enable the site and remove the default placeholder:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginxWhat this does: The symlink activates your config. nginx -t tests the config for syntax errors before you reload — always run this before reloading. systemctl reload nginx applies the new configuration without dropping active connections.
Step 9: Set Up HTTPS with Let’s Encrypt
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.comWhat this does: Installs Certbot and its nginx plugin, then automatically obtains a TLS certificate from Let’s Encrypt and modifies your nginx config to enable HTTPS. Certbot will prompt for your email (used for renewal notices) and ask you to agree to the terms of service. It also sets up a systemd timer for automatic renewal — your cert stays valid without any manual intervention.
Verify renewal works:
sudo certbot renew --dry-runThis simulates the renewal process without touching anything. If it exits cleanly, you’re set.
Step 10: Firewall (if using ufw)
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
sudo ufw statusWhat this does: Nginx Full opens both port 80 (HTTP) and 443 (HTTPS). After Certbot configures your site to redirect HTTP to HTTPS, you can optionally remove the plain HTTP rule — but it’s fine to leave it, since nginx will redirect those requests automatically.
Part 3: The Publish Script (Mac)
Back on your Mac, create the publish script:
nano ~/quartz-user/publish.sh#!/usr/bin/env bash
set -euo pipefail
QUARTZ_DIR="$HOME/quartz-user"
REMOTE_USER="user"
REMOTE_HOST="example.com"
REMOTE_PATH="/var/www/example.com"
cd "$QUARTZ_DIR"
echo "Building site..."
npx quartz build
echo "Deploying to $REMOTE_HOST..."
rsync -avz --delete public/ "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/"
echo "Published to https://example.com"Make it executable:
chmod +x ~/quartz-user/publish.shWhat this does: The script changes into your Quartz directory, runs a fresh build, then uses rsync to sync the compiled public/ directory to your server. The --delete flag removes files on the server that no longer exist locally — this matters when you rename or delete notes; otherwise old pages stick around indefinitely. set -euo pipefail makes the script exit immediately if any command fails, so you won’t silently deploy a broken build.
Usage
Whenever you’re ready to publish:
~/quartz-user/publish.shOr add an alias to your shell config (~/.zshrc):
alias publish="~/quartz-user/publish.sh"Then type publish from anywhere and you’re done.
Part 4: Optional Enhancements
Obsidian Shell Command Plugin
Install the community plugin Shell Commands in Obsidian. Configure it to run ~/quartz-user/publish.sh. Once set up, you can publish directly from Obsidian’s command palette — open the palette, type “Publish”, and hit enter. No terminal required.
Excluding Notes from Publishing
Add patterns to ignorePatterns in quartz.config.ts to keep specific folders private:
ignorePatterns: ["private", "templates", "drafts", ".obsidian"],Or mark individual notes with frontmatter:
---
draft: true
---Quartz respects draft: true and skips those notes at build time. This is the easiest way to keep a note in your vault without it appearing on your site.
Custom Domain Redirect (www → apex)
After Certbot runs, verify your nginx config includes a redirect from www to your apex domain:
server {
listen 443 ssl;
server_name www.example.com;
return 301 https://example.com$request_uri;
# ssl certs managed by certbot
}Certbot usually handles this automatically, but it’s worth confirming the redirect is in place so visitors don’t land on www.example.com as a separate site.
Quick Reference
| Action | Command |
|---|---|
| Preview locally | cd ~/quartz-user && npx quartz build --serve |
| Publish | publish (after alias) or ~/quartz-user/publish.sh |
| Exclude a note | Add draft: true to frontmatter |
| Update Quartz | cd ~/quartz-user && git pull && npm i |
Closing Thoughts
The whole stack here is remarkably boring, which is the point. Quartz handles the Obsidian-specific complexity — wiki-links, backlinks, graph view — so you don’t have to. rsync is a 40-year-old tool that does exactly what it says. nginx is battle-tested and well-documented. Let’s Encrypt has made TLS maintenance a non-problem.
What you end up with is a site that’s entirely yours: no platform lock-in, no subscription, no algorithm deciding who sees your work. Your notes are files on your machine. Your site is files on your server. If any part of this stack stops working, you can swap it out without losing anything.
That’s the way it should be.