# quicgate A single-binary reverse proxy manager in Go. The feature set and UI flow of Nginx Proxy Manager, but the proxy engine is native Go (no nginx, no Traefik), with HTTP/3 (QUIC) out of the box and every "advanced" option expressed as a structured, typed setting instead of a free-text config blob. ## Goals - Full functional parity with Nginx Proxy Manager (v2.12.x feature set). - Everything NPM users reach for in the "Advanced" custom-nginx box becomes a first-class form field. There is deliberately NO raw-config escape hatch: if an option is missing, it gets added to the engine as a typed option. - One binary, one SQLite file. Config changes apply instantly (atomic in-memory swap, no reload). - UI: dark "Console" design language (near-black + lime, Geist), vanilla JS, no build step, embedded via go:embed. ## Non-goals (explicitly out) - Kubernetes ingress, clustering. This fronts a homelab. (Docker label discovery was a non-goal here and was built anyway, in v1.2.0.) - WAF beyond NPM's "block common exploits" level. - Response body rewriting (nginx sub_filter). Revisit only if a real need appears. ## Engine: load-bearing packages | Concern | Package | Notes | |---|---|---| | HTTP/3 + QUIC | github.com/quic-go/quic-go (http3.Server) | Same lib Caddy/Traefik use. Shares the http.Handler with the TCP listeners. | | HTTP/1.1 + HTTP/2 | net/http | Stdlib. | | Proxying | net/http/httputil.ReverseProxy | Stdlib. Streaming, websockets, h2 upstreams work out of the box. | | Certificates | github.com/caddyserver/certmagic | ACME issue/renew, HTTP-01 / TLS-ALPN-01 / DNS-01, OCSP stapling, on-demand TLS. | | DNS-01 providers | libdns (incl. libdns/transip) | Wildcards via the existing TransIP key. | | Storage | modernc.org/sqlite | Pure Go, no CGO, builds FROM scratch. | | Compression | stdlib gzip + github.com/klauspost/compress (zstd) | Response compression toggle. | Routing core: `map[hostname]*HostConfig` behind an `atomic.Pointer`, rebuilt from SQLite on every mutation. Lookup is exact match then wildcard. Port 80 handler does ACME HTTP-01 + force-HTTPS redirects. ## Feature parity matrix (NPM -> quicgate) Legend: M1/M2/M3 = milestone. ### Proxy Hosts | NPM feature | quicgate | Milestone | |---|---|---| | Multiple domain names per host | Host list, wildcard support | M1 | | Forward scheme http/https, host, port | Upstream config | M1 | | Websockets support (toggle) | Always on (httputil handles it); no toggle needed | M1 | | Block common exploits (toggle) | Request filter middleware (SQLi/traversal/bad UA patterns) | M2 | | Cache assets (toggle) | Static asset cache middleware (memory + disk, by extension) | M3 | | Access list assignment | Access list reference per host | M2 | | SSL: cert selection | Pick managed (ACME) or uploaded cert, or "auto" | M1 | | SSL: Force SSL | 80 -> 443 redirect per host | M1 | | SSL: HTTP/2 support toggle | Always on; per-host min protocol setting instead | M1 | | SSL: HSTS + subdomains | Typed HSTS options (max-age, includeSubDomains, preload) | M1 | | Custom locations (path -> other upstream) | Path routes per host; every host-level option overridable per path | M2 | | Advanced free-text nginx config | Replaced by structured options (see below) | M1/M2 | ### Redirection Hosts | NPM feature | quicgate | Milestone | |---|---|---| | Domains, forward scheme+domain | Redirect host type | M2 | | HTTP code 300/301/302/307/308 | Typed enum | M2 | | Preserve path toggle | Toggle | M2 | | SSL options | Same TLS block as proxy hosts | M2 | ### Streams | NPM feature | quicgate | Milestone | |---|---|---| | TCP port forward | net.Listener + io.Copy pump | M2 | | UDP port forward | net.PacketConn relay with session map | M2 | ### 404 Hosts (dead hosts) Host type that serves a branded 404 with valid TLS. M2. ### Default site (Settings) What unmatched hostnames get: 404 page / redirect / custom HTML. M2. ### SSL Certificates screen | NPM feature | quicgate | Milestone | |---|---|---| | Let's Encrypt via HTTP challenge | certmagic HTTP-01 | M1 | | DNS challenge (wildcards) | certmagic DNS-01 via libdns; TransIP provider wired in | M2 | | Upload custom certificate | PEM upload, stored in the database (not encrypted at application level; protect the data volume and backups) | M2 | | Renewal, expiry overview | certmagic auto-renew; UI lists cert status/expiry | M1 | ### Access Lists | NPM feature | quicgate | Milestone | |---|---|---| | Basic auth users | bcrypt users per list | M2 | | Allow/deny CIDR rules | Ordered rule list | M2 | | Satisfy any/all | Enum | M2 | | Pass auth to upstream | Toggle (forward or strip Authorization) | M2 | ### Users, audit log | NPM feature | quicgate | Milestone | |---|---|---| | Multi-user + roles/visibility | Admin + limited users | M3, **not implemented** (single admin; see FEATURES.md) | | Audit log of all changes | Append-only audit table, UI viewer | M3, **not implemented** | M1 ships with single admin login (bcrypt + session cookie). ## Structured "advanced" options This is the replacement for NPM's raw nginx textarea. Grouped, typed, validated server-side, JSON in one `options` column. As built, options apply to the whole host; a custom location overrides only its upstream and path rewrite. Not every option listed below was built: [FEATURES.md](FEATURES.md) is the source of truth for what exists. **Upstream** - Preserve incoming Host header / override with fixed value - Skip TLS verification for https upstreams (self-signed backends) - Upstream SNI override - Timeouts: dial, response header, idle (durations) - Max request body size - Response buffering on/off (off = pure streaming, SSE-safe) **Request** - Header rules: ordered set/add/remove with value templating (client IP, host, scheme) - X-Forwarded-For/Proto/Host handling + trusted proxy CIDRs (real IP) - Path rewrite: strip prefix, add prefix, regex rewrite **Response** - Header rules: ordered set/add/remove - Hide upstream headers (e.g. X-Powered-By, Server) - Compression: off/gzip/zstd + minimum size - Custom error pages (per status code, HTML upload) **Security** - Rate limit: requests/sec + burst, per client IP, per host - Max concurrent connections per host - Block common exploits toggle (shared filter, versioned patterns) **TLS (per host)** - Min TLS version - HTTP/3 advertise toggle (Alt-Svc) - HSTS block (max-age, includeSubDomains, preload) Options render as form controls in a tabbed "Advanced" panel per host. Unknown/removed keys are rejected on save, never silently dropped. ## Data model (SQLite) - `hosts` (id, type: proxy|redirect|stream|dead, domains JSON, upstream JSON, cert_id, access_list_id, options JSON, enabled) - `locations` (id, host_id, path, match type, upstream JSON, options JSON) - `certs` (id, type: acme|custom, domains, PEM refs, meta) - `access_lists` (id, name, satisfy, pass_auth) + `access_list_users`, `access_list_rules` - `users` (id, email, bcrypt, role, disabled) - `audit_log` (id, ts, user_id, action, entity, before JSON, after JSON) - `settings` (key, value) ## Milestones - **M1 (usable core):** proxy hosts CRUD + UI, certmagic HTTP-01 auto-TLS, HTTP/1.1+2+3 serving, force-SSL, HSTS, header rules, timeouts, body size, streaming, single admin login. Success test: add a host in the UI, cert issues automatically, site serves over HTTP/3. - **M2 (NPM parity):** access lists, block-exploits filter, rate limiting, custom locations, redirection hosts, dead hosts, default site, TCP/UDP streams, DNS-01 wildcards (TransIP), custom cert upload, error pages, compression. - **M3 (polish):** multi-user + audit log, static asset cache, Prometheus /metrics, config export/import (JSON). ## Deployment Ship as a single container: FROM scratch + static binary + CA bundle; one volume for the SQLite db + certmagic storage. Needs 80/443 (TCP) and 443 (UDP, HTTP/3) published. Can run alongside NPM/Pangolin on different ports until trusted.