About this project

Portlyn is a self-hosted reverse proxy and zero-trust control plane built with Go and Next.js. It combines routing, access control, authentication, certificate management, and observability in one stack. The hub runs as a standalone binary or Docker Compose stack, with an optional node agent for machines behind NAT or CGNAT. ## Overview Portlyn exposes private services on your own domain over HTTPS, gates them with per-route authentication methods, and can reach machines behind NAT or CGNAT through an embedded userspace WireGuard tunnel. It is designed for homelabs and small teams that want one process instead of stitching together Traefik, Authelia/Authentik, Crowdsec, and WireGuard separately. Configuration lives in an admin UI; routes, identity providers, certificates, nodes, and audit are first-class resources. ## Features ### Routing and access control - Per-service routes by domain and path - Access modes: public, authenticated, restricted - Access methods per route: session, OIDC SSO, PIN, email code, magic link - User groups and service groups for reusable policy - Access windows with timezone-aware weekday and time ranges - IP allow/block lists, GeoIP allow/block lists per service - CrowdSec LAPI integration with periodic decisions pull ### Identity - Local password authentication with bcrypt - OIDC SSO with role claim mapping and allowed email domains - TOTP MFA with recovery codes - WebAuthn passkeys parallel to TOTP - Magic link sharing per service for one-off access - Email code based one-time passwords for routes without accounts - Optional mandatory MFA for admins with a bootstrap wizard ### Certificates - ACME with HTTP-01 and DNS-01 challenges - Wildcard certificates through DNS-01 - DNS providers: Cloudflare, Hetzner DNS, AWS Route 53, DigitalOcean DNS - Multi-SAN issuance, Let's Encrypt production and staging - Automatic renewal, manual renew, retry, sync status, PEM import - DNS provider credentials encrypted at rest with AES-256-GCM and Argon2id derived keys ### Tunnel - Userspace WireGuard server in the same process (wireguard-go + gVisor netstack) - No kernel module or wg-quick glue - Per-node WireGuard keypairs that never leave the node, enrollment via single-use tokens - Per-service routing through a tunnel node by setting node_id - Subnet routing for LAN subnets - Roaming clients with WireGuard config and QR code ### Audit and operations - Hash-chained audit log with previous hash verification - Webhook fan-out to Slack, Discord, ntfy, and generic JSON, signed with HMAC-SHA256 - Per-service exposure scanner with DNS, TLS, header, redirect, and auth posture checks - Access tester and per-request decision trace for troubleshooting policy denials ### Supply chain - Releases built from GitHub Actions and signed with Cosign keyless via Sigstore - Self-update verifies SHA-256 checksum and full Sigstore certificate chain via sigstore-go against an embedded TUF trust root - No telemetry, no analytics SDK, no automatic update checks ## Installation One-line install on Linux (downloads, verifies checksum + Cosign signature, creates system user, installs systemd unit): ```bash curl -fsSL https://raw.githubusercontent.com/portlyn/Portlyn/main/scripts/install-hub.sh \ | sudo PORTLYN_DOMAIN=portlyn.example.com PORTLYN_ADMIN_EMAIL=admin@example.com sh ``` Or install the single binary manually: ```bash curl -L https://github.com/portlyn/Portlyn/releases/latest/download/portlyn-linux-amd64 -o portlyn chmod +x portlyn sudo mv portlyn /usr/local/bin/portlyn sudo portlyn init sudo portlyn ``` `portlyn init` generates secrets, writes a `.env` file, prepares the data directory, and creates the admin account. Use `portlyn init --non-interactive` for scripted installs, and `portlyn doctor` to validate the environment. For local testing without domain/TLS/root: `PORTLYN_DOMAIN=localhost ./portlyn init --non-interactive && ./portlyn` → dashboard on `http://localhost:8000`. Docker Compose with the published image: ```bash git clone https://github.com/portlyn/Portlyn.git cd Portlyn cp .env.docker.example .env.docker # edit secrets and admin credentials in .env.docker docker compose --env-file .env.docker up -d ``` The Compose stack pulls a pinned `ghcr.io/portlyn/portlyn` tag. Override with `PORTLYN_IMAGE_TAG=v1.2.3`. The admin UI ships inside the image. If pull is denied, `docker login ghcr.io` or build locally with the dev overlay. ## Use cases - Expose a homelab service on your own domain without opening inbound ports (node agent dials hub on VPS) - Put authentication in front of any internal tool (PIN, email code, OIDC SSO, passkey MFA) - Simplify smaller self-hosted setups that would otherwise need Traefik, Authelia/Authentik, and WireGuard separately ## What Portlyn is not - Not a CDN or protection against volumetric L3/L4 DDoS - Not a Web Application Firewall (does not inspect request bodies) - Not multi-tenant (all admins see all services) - Not a mature HA edge cluster (single hub recommended unless you verify the cluster path) - Not a replacement for fixing vulnerable upstream applications ## Documentation - Installation and configuration: docs/INSTALL.md - Production hardening: docs/PRODUCTION-HARDENING.md - Security policy and threat model: SECURITY.md - Release process: docs/RELEASE.md - Backup and restore: docs/BACKUP-RESTORE.md - HA deployment notes: docs/HA-DEPLOYMENT.md - Secret rotation: docs/SECRET-ROTATION.md - Break glass recovery: docs/RECOVERY-BREAKGLASS.md - OpenAPI specification: openapi.yaml - Changelog: CHANGELOG.md ## Contributing Issues, PRs, and discussions are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md. To report a vulnerability privately, see SECURITY.md. ## License Portlyn is released under the MIT License. See LICENSING.md for details.