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:

  1. Obsidian vault — your notes live here, organized however you like
  2. Quartz — a static site generator built specifically for Obsidian vaults; it understands wiki-links, tags, backlinks, and graph view out of the box
  3. rsync — ships the compiled site to your server over SSH
  4. nginx — serves the static files to visitors
  5. 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 higher

What 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 i

What 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.


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" content

What 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 header
  • pageTitleSuffix — appended to every page title; useful for SEO
  • baseUrl — your domain, without https:// or a trailing slash; Quartz uses this to build absolute URLs
  • ignorePatterns — 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 --serve

Open 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.com

Step 6: Install nginx

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx

What 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.com

What 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.com

Paste 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 nginx

What 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.com

What 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-run

This 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 status

What 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.sh

What 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.sh

Or 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

ActionCommand
Preview locallycd ~/quartz-user && npx quartz build --serve
Publishpublish (after alias) or ~/quartz-user/publish.sh
Exclude a noteAdd draft: true to frontmatter
Update Quartzcd ~/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.