---
name: vault-web-access
description: Serve the Syncthing-mirrored vault as browsable HTML over HTTPS, bypassing Windows file-association issues that cause .html downloads instead of opening in browser. Covers the Python HTTP server + nginx reverse proxy pattern, Let's Encrypt cert setup for vault subdomain, and systemd service for reboot survival.
tags:
  - vault
  - nginx
  - certbot
  - http-server
---

# Vault Web Access

## Trigger
When the user needs to view vault HTML files in a browser and:
- `.html` files attached via MEDIA: download instead of opening (Windows file association issue)
- A "not secure" warning blocks viewing
- The user needs clickable URLs rather than downloaded files

## Architecture
The vault lives at `/root/.hermes/vault/` on the VPS. It cannot be served directly by nginx because:
- `/root` is mode 700 (root-only), nginx runs as `www-data`
- Bind mounts at `/var/www/vault` fail — nginx sees empty directory despite files present
- AppArmor local rules do not unblock traversal of the bind mount source
- MEDIA: prefix causes `.html` downloads on Windows

The working pattern:
1. **Python HTTP server** (`python3 -m http.server`) serves `/root/.hermes/vault/` on `127.0.0.1:9124`
2. **nginx** proxies `https://vault.robblake.cloud/` → `http://127.0.0.1:9124`
3. **Let's Encrypt cert** for `vault.robblake.cloud` (separate from `robblake.cloud` wildcard)
4. **systemd unit** `vault-browser.service` keeps the Python server alive across reboots

## Setup Steps

### 1. Create systemd unit
Path: `/etc/systemd/system/vault-browser.service`

```ini
[Unit]
Description=Vault Browser (Python HTTP Server)
After=network.target

[Service]
Type=simple
WorkingDirectory=/root/.hermes/vault
ExecStart=/usr/bin/python3 -m http.server 9124 --bind 0.0.0.0
Restart=always
RestartSec=5
Environment=HOME=/root

[Install]
WantedBy=multi-user.target
```

Enable and start:
```bash
systemctl daemon-reload
systemctl enable vault-browser.service
systemctl start vault-browser.service
```

### 2. Get Let's Encrypt cert for vault subdomain
```bash
systemctl stop nginx
certbot certonly --standalone -d vault.robblake.cloud --non-interactive --agree-tos --email rob@robblake.cloud
systemctl start nginx
systemctl start vault-browser.service
```

Cert paths:
- Fullchain: `/etc/letsencrypt/live/vault.robblake.cloud/fullchain.pem`
- Privkey: `/etc/letsencrypt/live/vault.robblake.cloud/privkey.pem`

### 3. Create nginx server block
Path: `/etc/nginx/sites-available/vault.robblake.cloud`

```nginx
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name vault.robblake.cloud;

    ssl_certificate /etc/letsencrypt/live/vault.robblake.cloud/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vault.robblake.cloud/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:9124;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name vault.robblake.cloud;
    return 301 https://$host$request_uri;
}
```

Enable and reload:
```bash
ln -sf /etc/nginx/sites-available/vault.robblake.cloud /etc/nginx/sites-enabled/vault.robblake.cloud
nginx -t && systemctl reload nginx
```

### 4. DNS
Add A record in hPanel:
- Type: `A`
- Hostname: `vault`
- Value: `2.25.172.164`
- TTL: `300`

## Delivery URLs
Give these to the user as copy-paste URLs:

**Vault index:**
```
https://vault.robblake.cloud/
```

**Motion canvases:**
```
https://vault.robblake.cloud/Projects/Active/motion-canvases/premium-line-bail-bonds.html
https://vault.robblake.cloud/Projects/Active/motion-canvases/free-website-local-services.html
https://vault.robblake.cloud/Projects/Active/motion-canvases/performance-lead-gen-offer.html
https://vault.robblake.cloud/Projects/Active/motion-canvases/website-audit-track.html
```

## Fallback
If DNS has not propagated or the HTTPS URL fails, use:
```
http://2.25.172.164:9124/
```
Note: this shows "not secure" because it is plain HTTP. Content is still safe — it is all local vault HTML with no forms or secrets.

## Pitfalls
- **Do not bind the Python server to 127.0.0.1** — external users will get timeout. Always use `--bind 0.0.0.0`.
- **Do not use the robblake.cloud wildcard cert** for vault.robblake.cloud — it only covers `robblake.cloud` and `www.robblake.cloud`. Get a separate cert via certbot.
- **Do not use `--nginx` authenticator** for vault cert — it fails with "Server is speaking HTTP/2 over HTTP" because the vault proxy is already handling HTTP/2. Use `--standalone` instead.
- **Do not attempt nginx alias/bind mount** of `/root/.hermes/vault` — every approach fails: directory permissions (700), bind mount emptiness, AppArmor rules. The Python server + proxy is the only reliable path.
- **Do not use `cp -al` hard-link copy** from `/root/.hermes/vault` to `/var/www/vault` — cross-device link errors on Hostinger's ext4, and even successful copies appear empty to nginx due to the same traversal issue.
- **Hostinger firewall:** port 9124 may be blocked. If `2.25.172.164:9124` times out, ask Hostinger support to open TCP 9124, or check hPanel → VPS → Network/Firewall. The "accept any" rule usually covers it, but confirm.
- **Conflicting server_name warnings:** nginx warns about duplicate `robblake.cloud` server blocks. These are harmless if only one block matches the request host. Verify with `nginx -T | grep server_name` if behavior is wrong.
- **certbot renewal:** certbot auto-renews via systemd timer. No manual renewal needed for the vault subdomain.
