A self-hosted reverse proxy manager with single sign-on built in — one Go binary.
Point-and-click like Nginx Proxy Manager · identity-aware like Pomerium · HTTP/3 · no nginx, no Traefik, no sidecars.
quicgate is a reverse proxy manager for self-hosters and homelabs: a web UI for HTTPS proxy hosts, automatic Let's Encrypt certificates, TCP/UDP streams, access lists — and OpenID Connect SSO in front of any host, run by quicgate itself against Keycloak, Entra ID, Authentik or any spec-compliant identity provider.
It replaces this stack with one container:
| Instead of | quicgate gives you |
|---|---|
| Nginx Proxy Manager / nginx | proxy hosts, certificates, redirects, static sites, a UI |
| Traefik / Caddy | routing, ACME, Docker label discovery, HTTP/3 |
| Authelia / oauth2-proxy / Pomerium | identity-aware access: OIDC login, group policy, Remote-User upstream |
| fail2ban, a metrics exporter, a log viewer | auto-ban, Prometheus /metrics, JSON access logs |
It exists because I loved Nginx Proxy Manager's workflow but not its internals, and loved Pangolin's engine but not its complexity. So: the NPM experience, rebuilt on a modern native-Go data plane, in a single
FROM scratchcontainer.
# docker-compose.yml
services:
quicgate:
image: ghcr.io/quicgate/quicgate:latest
restart: unless-stopped
network_mode: host # engine owns 80/443 (tcp+udp), admin UI on 81
environment:
- QG_ACME_EMAIL=you@example.com
volumes:
- ./data:/datadocker compose up -dOpen http://<host>:81, sign in with admin@example.com / changeme (a password change is forced),
add your first proxy host, and the certificate issues automatically.
Never expose port 81 to the internet. Put the admin UI behind quicgate itself with an access list, a VPN, or a firewall rule — like any other private service.
Most proxies hand SSO to a second service. quicgate runs the OpenID Connect login itself: PKCE and a nonce, ID-token signature verification, and a signed session cookie bound to the host and to the provider that issued it.
Define an identity provider once, then per host choose who gets in — by email, by email domain or by
group — and optionally pass the identity upstream as Remote-User / Remote-Email / Remote-Groups,
so apps with proxy-auth support log the user straight in. Inbound copies of those headers are always
stripped, so a client can never forge them.
Authentication can differ per URL on the same host, which is what real apps need:
| Path | Gate |
|---|---|
/ValidationService.asmx |
public — a licensing callback that cannot log in |
/webhooks/ |
public, POST only |
/admin/ |
OIDC, group platform-admins |
| everything else | OIDC, any employee |
Different paths can even use different identity providers — staff on the company IdP, a partner endpoint on theirs. Already running Authelia or Authentik? Point a host at its verify endpoint with forward-auth instead. Detail in the Access control & SSO guide.
Proxying — proxy, redirect (301/302/307/308), 404 and static hosts · wildcard domains · load-balanced pools with health checks and sticky sessions · custom locations · path rewrites (strip/add prefix, RE2) · maintenance mode · response caching · gzip
TLS & HTTP/3 — automatic Let's Encrypt (HTTP-01), DNS-01 wildcards, custom ACME CAs (ZeroSSL, step-ca) · upload your own certificates or generate self-signed · mTLS client certificates · HSTS · h1/h2/h3 (QUIC) on every host, with a per-host toggle
Access control — ordered access lists by IP/CIDR, dynamic-DNS hostname or GeoIP country · basic auth · per-rule HTTP-method scoping · built-in OIDC SSO and forward-auth · per-path rules · rate limiting · exploit and bad-bot filters · fail2ban-style auto-ban · real client IP behind Cloudflare or another load balancer
Streams (TCP/UDP) — L4 forwards with source whitelists · PROXY protocol v1/v2 · TLS termination · SNI passthrough routing · port ranges · plus router port-forwards managed over UPnP, self-healing after a reboot
Docker — opt a container in with quicgate.enable=true and its host (and streams) are derived
from labels: Traefik's provider idea without the router/service/middleware soup. Multi-host, and
every derived route shows why it is or is not routing
Dual-stack — IPv6 clients, IPv6-literal and AAAA upstreams, IPv6 CIDRs in access lists and trusted-proxy lists, GeoIP and rate limits on v6 alike
Ops — Overview dashboard · JSON access logs with a built-in viewer · Prometheus /metrics ·
one-click backup/restore · declarative JSON import · certificate-renewal alerts (ntfy/Gotify) ·
TOTP 2FA · API tokens · OIDC/LDAP admin login · offline guides in the UI · light/dark and a choice of
themes
| quicgate | Nginx Proxy Manager | Pangolin | |
|---|---|---|---|
| Data plane | native Go (net/http, quic-go) | nginx | Traefik |
| Deployment | 1 container, ~25 MB, scratch | 1 container (+optional db) | 3+ containers |
| HTTP/3 (QUIC) | default, per-host toggle | no | via Traefik config |
| Config model | typed, validated options | UI + free-text nginx snippets | UI + Traefik config |
| Reloads | instant atomic swap | nginx reload | Traefik provider push |
| SSO on your services | built-in OIDC + forward-auth | no | built-in IdP/SSO |
| Per-URL auth policy | yes | no | per-resource |
| Access lists | IP/CIDR + GeoIP + dynamic DNS | IP/CIDR | yes |
| TCP/UDP streams | yes + PROXY protocol + SNI + TLS termination | basic | via tunnels |
| WireGuard tunnels to remote sites | no | no | yes — Pangolin's killer feature |
| Config from container labels | yes, flat labels + streams | no | via Traefik labels |
| Metrics / API | Prometheus + full REST + OpenAPI | none / undocumented REST | via Traefik / REST |
| Maturity | young — read the caveats | battle-tested, huge community | growing fast |
Choose NPM for the most battle-tested option and years of community answers. Choose Pangolin if you need WireGuard tunnels to reach services on remote machines. Choose quicgate if you want one small container to replace the whole stack — proxy, certificates, access control and SSO — on a modern engine.
- Young project with one production deployment (mine, ~50 hosts), so expect rough edges. Issues welcome.
- No WireGuard tunnelling — quicgate proxies to network-reachable upstreams only.
- Single admin user (with 2FA / OIDC / LDAP), no multi-tenant roles.
On a Ryzen 7 9800X3D: ~45,000 proxied requests/sec to a local backend, ~180,000/sec for cache
hits, ~9 ns routing lookups, and ~8,900 TLS-proxied req/s on a single core. Access lists add no
measurable overhead. In short, the proxy is never the bottleneck at self-hosting scale. Reproduce
with go test -bench=. ./internal/engine; methodology in BENCHMARKS.md.
Guides live in web/docs/ and are built into the binary — open Help (? in the top
bar) and pick one. They work offline, air-gapped installs included.
- Getting started — run it, first host, TLS modes, host types, themes
- Configuration reference — env vars and settings, real client IP, GeoIP, HTTP/3, IPv6
- Access control & SSO — access lists, OIDC login, forward auth, per-path rules, security model
- Docker labels — hosts and streams from container labels, multi-host
- Streams & port forwards — TCP/UDP forwarding, PROXY protocol, SNI routing, UPnP
Everything the UI does is a REST call — interactive Swagger at /docs.html, spec at /openapi.yaml,
prose in API.md. Create a bearer token under Profile → API tokens:
curl -H "Authorization: Bearer $TOKEN" http://<host>:81/api/hostsPOST /api/import does idempotent declarative bulk import, which is how my own NPM and Pangolin
migrations were scripted.
go build . # single static binary
docker build -t quicgate . # multi-stage, FROM scratchDev mode without TLS: QG_TLS=off QG_HTTP=:8090 QG_ADMIN=:8091 QG_DATA=./devdata go run .
- SPEC.md — the NPM feature-parity matrix and architecture decisions
- ROADMAP.md — features mined from NPM's issue tracker (all five phases implemented)
- CHANGELOG.md — what changed, release by release
- CONTRIBUTING.md — quicgate is deliberately opinionated: one binary, typed options, no free-text config
- SECURITY.md — found a vulnerability? Report it privately, never as a public issue