Hosting on loca.zone

This is the canonical deployment guide for hosting this Quartz site and its live dev environment on 178.18.250.170. The configuration below has been successfully deployed.

Deployment Architecture

1. Nginx Configuration

The file /etc/nginx/sites-available/quartz.loca.zone contains:

# HTTP redirects will be handled by certbot automatically
    server_name quartz.loca.zone;
    root /var/www/quartz-loca/current;
 
    # Static files pattern
    location / {
        try_files $uri $uri.html $uri/ =404;
    }
}
 
server {
    server_name dev.quartz.loca.zone;
 
    # Proxy HTTP to local Quartz server
    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

2. Let’s Encrypt Certificates

Certificates were generated using:

sudo certbot --nginx -d quartz.loca.zone -d dev.quartz.loca.zone

3. Stream Block for Hot-Reload (WebSockets)

Certbot doesn’t configure stream blocks for WebSockets over TLS, so the following was appended to /etc/nginx/nginx.conf (outside the http { ... } block):

stream {
    server {
        listen 3002 ssl;
        ssl_certificate /etc/letsencrypt/live/quartz.loca.zone/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/quartz.loca.zone/privkey.pem;
 
        # Proxy to local Quartz WebSocket port (3003)
        proxy_pass 127.0.0.1:3003;
    }
}

4. Quartz Dev Command

npm run serve:dev
# HTTP :8081 (proxied by Nginx)
# WebSocket server binds :3003 (--wsPort)
# Browser connects to wss://dev.quartz.loca.zone:3002 (--wsPublicPort via Nginx stream)

Alternative Hosting Options

Alternative A — Local-only DNS

Use /etc/hosts or Pi-hole/dnsmasq on your LAN. TLS would be HTTP-only or require mkcert. For hot reload, omit --remoteDevHost and connect to ws://localhost:3002. Not chosen: No trusted public HTTPS.

Alternative B — Split-horizon DNS

Internal resolver returns LAN IP, public returns none. Not chosen: Public reference would be unreachable without a VPN.

Alternative C — Tailscale / WireGuard MagicDNS

Use the mesh hostname (no public A record). Set --remoteDevHost to your MagicDNS name. Not chosen: Limits access to the VPN mesh.

Alternative D — Cloudflare Tunnel

Run cloudflared to :8081. You would need a separate TCP ingress for the WebSocket port 3002. Not chosen: More moving parts than using Nginx on an owned VPS.

Alternative E — Docker-only

Use the repo’s Dockerfile to run npx quartz build --serve on 8080. Not chosen: Lack of persistent homelab Nginx integration.

Alternative F — Static-only

Skip the dev subdomain and systemd service completely; run local npm run serve:docs only when needed. Not chosen: The goal is a persistent live tinker bench.