business-ops
Business operations: strategy, technology, growth, competitive intelligence, support, finance, HR, legal, operations, sales, productivity, product management.
Publish a public website safely: DNS, web server, HTTPS, hardening, verify. Routes raw dev servers through nginx/Caddy/Apache/Cloudflare Pages.
$ npx -y skills add notque/vexjoy-agent --skill public-web-deploy --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/public-web-deployContext preview
The summary Claude sees to decide when to auto-load this skill.
Publish a public website safely: DNS, web server, HTTPS, hardening, verify. Routes raw dev servers through nginx/Caddy/Apache/Cloudflare Pages.
name: public-web-deploy
description: "Publish a public website safely: DNS, web server, HTTPS, hardening, verify. Routes raw dev servers through nginx/Caddy/Apache/Cloudflare Pages."
user-invocable: false
allowed-tools:
- Bash
- Read
- Edit
- Write
- Glob
- Grep
routing:
force_route: true
triggers:
- "public site"
- "public website"
- "static site"
- "landing page"
- "deploy website"
- "deploy site"
- "host a website"
- "serve this website"
- "put this online"
- "put site online"
- "website online"
- "make it public"
- "make public"
- "use my domain"
- "point my domain"
- "set up https"
- "nginx public site"
- "go live"
category: infrastructure
not_for: "domain MODELING / DDD bounded contexts (use a code agent); local-only preview on 127.0.0.1 (no deploy needed); generating HTML artifacts (use html-artifact); HTTPS client/API bugs and internal-only nginx reverse proxies (use a code/infra agent). Fires when a site goes internet-facing."
pairs_with:
- shell-process-patterns
- service-health-check
- kubernetesServe public sites through a real web server. Local preview binds `127.0.0.1`; public sites go through nginx, Caddy, Apache, or Cloudflare Pages — fronted by HTTPS and hardened nginx config.
> Never use `python -m http.server`, Vite, Hugo, Next dev, Flask dev, or any raw dev server as an internet-facing public service. Local preview MUST bind 127.0.0.1. Public sites MUST go through nginx / Caddy / Apache / Cloudflare Pages.
Raw dev servers are single-threaded, unauthenticated, serve the whole working directory (dotfiles, `.env`, `.git`, source, backups), carry no TLS, no rate limiting, and no request filtering. They are correct for `127.0.0.1` local preview and wrong for any internet-facing service. A companion enforcement hook blocks public binds at the tool layer; this skill is the guidance that pairs with it.
**Decide public vs private first.** A public site is reached by HTTPS + hardened nginx — that *is* the security model. Reserve auth (basic-auth, SSO) for private/internal sites, decided explicitly. Adding basic-auth to a public site breaks it for its intended audience and adds no protection to content meant to be public.
---
**Goal**: The domain/subdomain resolves to the host before any web server work.
**Steps**: 1. Confirm the target FQDN (apex `example.com`, subdomain `app.example.com`). 2. Create an `A`/`AAAA` record pointing at the host's public IP (or `CNAME` for managed platforms like Cloudflare Pages). 3. Verify resolution propagated:
dig +short A app.example.com dig +short AAAA app.example.com getent hosts app.example.com
**Gate**: `dig +short` returns the intended host IP. Proceed only when DNS resolves correctly — an HTTPS cert request fails if DNS does not yet point at the host.
**Goal**: A production web server (not a dev server) serves the site from a defined docroot.
**Steps**: 1. Pick the server: nginx or Caddy (self-hosted), Apache (existing stacks), or Cloudflare Pages (managed static). 2. Define a server block bound to the FQDN with an explicit docroot:
server {
listen 80;
server_name app.example.com;
root /var/www/app.example.com;
index index.html;
location / { try_files $uri $uri/ =404; }
}3. If an app backend exists, proxy to it on `127.0.0.1`:
location /api/ { proxy_pass http://127.0.0.1:8000; }The backend binds `127.0.0.1`, never `0.0.0.0` — only nginx faces the internet. 4. Test config and reload:
nginx -t && systemctl reload nginx
**Gate**: `nginx -t` reports syntax OK and the site responds on port 80. The application/dev port is reachable only on `127.0.0.1`.
**Goal**: Valid TLS cert installed, auto-renewal proven, HTTP redirects to HTTPS.
**Steps**: 1. Issue a cert (Let's Encrypt via certbot, or Caddy/Cloudflare automatic TLS):
certbot --nginx -d app.example.com
2. Prove renewal works before trusting it:
certbot renew --dry-run
3. Confirm the HTTP->HTTPS redirect returns a 301/308 to `https://` (certbot's nginx installer adds it; verify both the status and the target):
curl -sI http://app.example.com | grep -iE '^HTTP/.* (301|308)' curl -sI http://app.example.com | grep -i '^location: https'
**Gate**: HTTPS serves a valid chain, `certbot renew --dry-run` exits 0, and plain HTTP 301/308-redirects to HTTPS. Proceed only when all three hold.
**Goal**: Lock the surface to the minimum needed to serve the site.
**Steps**: 1. Firewall — allow only intended ports. UFW applies to both IPv4 and IPv6 when `IPV6=yes` in `/etc/default/ufw`; confirm v6 parity if the host has an `AAAA` record:
ufw allow 80/tcp ufw allow 443/tcp ufw allow OpenSSH ufw --force enable # --force skips the interactive prompt ufw status verbose # entries show (v6) duplicates when IPv6 is on
No raw app/dev ports (8000, 5173, 1313, 3000…) appear in `ufw status`. 2. nginx — deny sensitive paths, disable directory listing, restrict methods:
autoindex off; # no directory listing (default off; set explicitly so it can't be inherited on)
# Deny dotfiles, source, configs, logs, backups, archives, editor/VCS leftovers
location ~ /\. { deny all; } # .env .git .htaccess
location ~* \.(md|env|ini|conf|log|bak|old|orig|swp|sql|yml|yaml|zip|tar|tar\.gz|tgz)$ { deny all; }
location ~ ~$ { deny all; } # editor backup files like index.html~
# Restrict methods to read-only INSIDE the served location.
# limit_except is location-scoped and evaluated per matched location;
# a server-level `if ($requEssays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.
Repo: notque/vexjoy-agent
Business operations: strategy, technology, growth, competitive intelligence, support, finance, HR, legal, operations, sales, productivity, product management.
Design workflows — UX copy, design systems, design critique, accessibility review, design handoff, user research synthesis. Use when writing UI copy, reviewing…
Marketing: SEO audits, campaign planning, content strategy, email sequences, competitive analysis, brand review, performance reporting.