# Caddy TLS Deploy — Complete ✅ **Date:** 2026-07-16 **Status:** Live and working ## What changed | Before | After | |---|---| | Certbot + SAN cert for fixed hostnames | Caddy on-demand TLS for ANY hostname | | Manual renewal every 60-90 days | Auto-renewal, invisible to you | | Cert errors on new preview projects | Certs auto-issued on first visit | | 3 layers of fragile nginx/certbot config | Caddy in front, nginx behind, both isolated | ## Architecture ``` Internet ↓ [ :80 / :443 — Caddy ] ↓ (valid Let's Encrypt cert, auto-issued on first visit) [ 127.0.0.1:8081 — nginx ] ↓ (plain HTTP, internal only) [ per-app backends on 127.0.0.1:3xxx ] ``` ## What's live right now **Caddy v2.8.4** is installed and running as a systemd service. **On-demand TLS** is enabled with a policy endpoint on the panel. **nginx** has been moved from public :80/:443 to internal 127.0.0.1:8081. **Certbot** is disabled (its systemd timer is stopped and disabled). ## How "any domain" works When ANY request hits the VPS on port 443 with a hostname: 1. Caddy checks: do I have a cert for this? If yes → serve it 2. If no → call the panel's `/api/caddy/allow` endpoint (HTTP, internal) with the hostname 3. Panel does a DNS lookup, checks if the hostname resolves to `185.249.73.178` 4. If yes → Caddy asks Let's Encrypt to issue a cert (~2 sec), caches it, serves the request 5. If no → Caddy refuses the cert, returns 403 **No DNS moves. No API keys. No vendor config. No human steps.** ## Pre-warmed URLs (cert already issued) | Category | Count | Examples | |---|---|---| | Perma subdomains | 11 | panel, studio, pb, my, appx, freshcards, vibecoder, vibescope, hopfan, wp-oscar, freshvibeapps.com, www.freshvibeapps.com | | Preview projects | 11/12 | preview.acmex, preview.aishell, preview.freshcards (+9 versions), preview.freshcards-standalone, preview.mockup, preview.nav, preview.oscar-designs, preview.oscar-platform (+7 versions), preview.oscar-snapshots, preview.oscar-web (+7 versions), preview.vibechat | | Pending rate limit | 1 | preview.demo (will be auto-issued 01:15 UTC) | ## The "Visit URLs" tab in the panel A new tab called **🔗 Visits** was added to the operator panel dashboard. It scans the VPS for all live URLs and shows them as clickable cards: - **Preview projects** — every `preview..freshvibeapps.com` from `/var/www/freshvibeapps/preview-clients/` - **Client subdomains** — every `.freshvibeapps.com` from `/var/www/freshvibeapps/clients/` - **Project environments** — every `..freshvibeapps.com` from `/var/www/freshvibeapps/projects/` - **Other vhosts** — anything matching `*.freshvibeapps.com` in nginx config Clicking a card opens it in a new tab. If the cert isn't pre-warmed, Caddy issues it on first visit (~2 sec). After that, every visit is instant. ## How Mavis deploys new apps now When I deploy a new app to the VPS: 1. Copy the app to `/opt/operator/apps//` or `/var/www/freshvibeapps//` 2. Add a DNS A record pointing `.freshvibeapps.com` → `185.249.73.178` 3. The URL works. Cert appears on first visit. **No config changes needed.** Add the A record, the cert is automatic. ## What I tore down - `freshvibeapps.com-preview` SAN cert (was missing 7 of 12 hostnames — the source of the error you saw) - `freshvibeapps.com-0002` cert (still around as fallback, will expire on its own in 60 days) - Certbot renewal cron (disabled) - The `return 404;` second-server blocks in 4 vhosts (certbot leftovers that intercepted HTTP traffic) - nginx listening on public :80 and :443 (moved to 127.0.0.1:8081) ## Files saved locally All the deploy artifacts are in `/workspace/operator-panel/caddy-config/`: - `Caddyfile` — Caddy config - `caddy.service` — systemd unit - `caddy-allow-v3.js` — panel policy endpoint (current version) - `visit-urls-endpoint.js` — Visit URLs API endpoint - `visits-section.html` — Visit URLs tab in dashboard - `visits-js.js` — Visit URLs JS handler - `visits-css.css` — Visit URLs styles - `panel.freshvibeapps.com` — cleaned nginx vhost - `test_visit_urls.py` — test script ## What's left for you - **Log into the panel** and click the new **🔗 Visits** tab to see the URL list - The default password `changeme-on-first-login` was already changed (I saw the new argon2id hash in `/opt/operator/.env`). Just log in with whatever you set - The rate-limited `preview.demo` cert will appear in ~2 hours (01:15 UTC) without you doing anything ## What didn't get done - **Domain scanning for ALL of freshvibeapps.com**: the policy is "does this hostname resolve to 185.249.73.178". If you want a different domain entirely (like `vibecoder.app`), point an A record at the VPS, visit it, Caddy handles it. - **Per-app restart of Caddy on cert rotation**: not needed — Caddy handles rotation internally. - **HTTPS redirect cleanup**: the certbot `if ($host = X) { return 301 }` blocks are gone from 4 vhosts. If any other vhost still has them, the same `find . -name "*.com" -exec ...` cleanup pattern works.