Sign inSign up

jrandombytes/nginx-proxy-manager

By jrandombytes

•Updated 3 days ago

Security-hardened NPM fork. nginx mainline base image, CVE-patched.

Image
0

8.9K

jrandombytes/nginx-proxy-manager repository overview

Note: This Docker container is entirely unofficial and not made by the creators of Nginx Proxy Manager.

⁠jrandombytes/nginx-proxy-manager

version base

⁠What is this?

Nginx Proxy Manager is a self-hosted reverse proxy with a web UI that just works. Route your domains to your services, get automatic SSL, and never write an nginx config file again.

If you're running multiple services on your own server — whether that's a homelab, a VPS, or an on-premise machine — this is the missing piece that ties everything together.

⁠Who it's for

  • Developers hosting multiple Docker containers on a single server
  • Homelab enthusiasts who want real domains and real SSL on their self-hosted apps
  • Small teams managing internal tools without a dedicated ops person
  • Anyone tired of remembering which service runs on which port

⁠Why it works

No config files. Add a proxy host, point it at a service or container, and you're done. What used to take hours of nginx editing takes under a minute.

Docker-friendly out of the box. Running ten containers on ten different ports? Map each one to a clean subdomain — app.yourdomain.com instead of yourdomain.com:8080. No port juggling, no awkward URLs.

SSL that manages itself. Let's Encrypt certificates are issued and renewed automatically. Set it up once, forget about it.

More than just HTTP. Proxy TCP and UDP streams alongside your web services — all from the same interface.

Runs anywhere, depends on nothing. Fully self-hosted, air-gap capable, no cloud account required. Your infrastructure stays yours.

Grows with you. When you need it — access lists, basic auth, rate limiting, custom nginx directives — it's all there.

⁠Best paired with Cloudflare

Nginx Proxy Manager handles routing and SSL at your server. Cloudflare's free tier covers everything in front of it — DDoS protection, CDN, edge caching, and a basic WAF — with no extra cost or complexity.

Together, they give you a production-grade stack that punches well above its weight:

  • Cloudflare — protects and accelerates traffic before it reaches your server
  • Nginx Proxy Manager — routes that traffic to the right service once it arrives

Neither replaces the other. They do different jobs, and they do them well together.

⁠When it makes sense

Good fitProbably overkill
Multiple Docker containers on one serverSingle app, single domain
Teams without dedicated DevOpsFull infrastructure-as-code setup
Mixed HTTP + TCP/UDP workloadsEverything already on a managed platform
Self-hosted, on-prem, or air-gappedCloudflare Tunnel covers all your needs

⁠Why this fork?

The official image (jc21/nginx-proxy-manager) bundles OpenResty and depends on upstream for CVE patches — which can lag weeks or months behind disclosure. This fork owns the entire chain from nginx.org apt → base image → app image, so CVEs can be patched the same day they are disclosed.

⁠Key differences from the official image

FeatureOfficial (jc21)This fork (jrandombytes)
Base OSDebian 12 (bookworm)Debian 13 (trixie) (v2.15.0)
nginx versionOpenResty 1.27.1.2 (nginx 1.27.1)nginx mainline 1.31.0+
CVE-2026-42945 (CVSS 9.2)❌ Unpatched✅ Patched
CVE-2025-6965 (SQLite)❌ Unpatched✅ Patched
Base image controlUpstream-controlledOwn pipeline
Build frequencyManual upstream releaseWeekly auto-rebuild
Timing oracle (user enumeration)Vulnerable✅ Fixed
Shell escape RCE (DNS credentials)Vulnerable (PR#5498 incomplete)✅ Fixed (correct POSIX idiom)
SSRF guardNot available✅ Opt-in (BLOCK_PRIVATE_UPSTREAM=true)
Per-host rate limitingNot available✅ limit_req_zone / limit_req with UI controls
Cloudflare Turnstile on loginNot available✅ Opt-in bot protection (Settings UI)
Login + 2FA rate limitingNot available✅ express-rate-limit (10 req / 15 min)
Cloudflare IP restrictionNot available✅ Drop non-CF origin requests (return 444, Settings UI)
Session token storagelocalStorage (XSS-readable)✅ HttpOnly cookie + CSRF double-submit (v2.14.28)
Per-host nginx log viewer (admin)❌ Not available✅ Logs tab on proxy / dead / redirection-host modals; newest-first by default (v2.14.36; admin-gate fix v2.14.38; newest-first + real-IP + TZ v2.14.40)
Real client IP behind Cloudflare❌ Logs show CF edge IP✅ real-ip-header setting (CF-Connecting-IP / X-Real-IP / X-Forwarded-For) + Settings UI card + auto-detect when Cloudflare IP Restriction is enabled — logs show actual visitor IP
Timezone for log timestamps❌ UTC only✅ TZ env var (e.g. Asia/Manila); fails closed to UTC on invalid value
Logrotate scheduler⚠️ Config ships but never fires (no cron)✅ s6 longrun runs logrotate daily
Admin dashboard metrics❌ Static "Hello" + 4 count tiles✅ Tabler-grid dashboard: traffic sparklines, status-class stacked-area chart, top hosts, top client IPs (with Cloudflare-range badge), top 4xx/5xx (deep-link to error logs); hand-rolled inline SVG (no new deps)

⁠Quick start

services:
  npm:
    image: jrandombytes/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    volumes:
      - npm_data:/data
      - npm_letsencrypt:/etc/letsencrypt
    environment:
      PUID: 1000
      PGID: 1000

volumes:
  npm_data:
  npm_letsencrypt:

Access the admin UI at http://<your-server>:81

First-time setup: the container ships with no users. On first visit you will be shown a one-shot setup wizard — fill in your name, email, and password. That account becomes the initial administrator.

For automated deployments, set INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD env vars instead — the container will provision the admin at boot. Unset both vars after first successful login.

⁠Environment variables

VariableDefaultPurpose
PUID1000UID to run the npm process
PGID1000GID to run the npm process
BLOCK_PRIVATE_UPSTREAMfalseBlock proxy hosts targeting LAN/private IPs (SSRF hardening)
DISABLE_IPV6falseDisable IPv6 in generated nginx configs
DB_SQLITE_FILE/data/database.sqliteSQLite database path
DB_MYSQL_HOST—MySQL host (if using MySQL instead of SQLite)
DB_POSTGRES_HOST—PostgreSQL host (if using PostgreSQL instead of SQLite)
LE_STAGINGfalseUse Let's Encrypt staging environment
LOGROTATE_INTERVAL86400Seconds between logrotate cycles (must be a positive integer; falls back to default on invalid value)
TZ—IANA timezone (e.g. Asia/Manila, Asia/Singapore, Etc/UTC). Applied at boot to nginx log timestamps, audit log, Node logs, certbot renewal logs. Invalid value → falls back to UTC. (v2.14.40)
FORCE_SECURE_COOKIES—Force Secure flag on npm_session / npm_csrf cookies regardless of req.secure. Recommended true when behind Cloudflare or any TLS-terminating edge.
CORS_ALLOWED_ORIGINS—Comma-separated allowlist of CORS origins. Unset = same-origin only. Never use * with credentials.

⁠Database backends

SQLite is the default. MySQL/MariaDB and PostgreSQL are also supported via environment variables.

⁠Ports

PortPurpose
80HTTP proxy traffic
81Admin UI
443HTTPS proxy traffic

⁠Security fixes in this fork

  • CVE-2026-42945 (NGINX Rift, CVSS 9.2) — nginx ≤ 1.30.0 heap overflow RCE in rewrite module. Own base image uses nginx 1.31.0 mainline.
  • CVE-2025-6965 (SQLite < 3.50.2) — Memory corruption. better-sqlite3 upgraded to bundle SQLite 3.52.0.
  • Timing oracle — Login always runs bcrypt even for unknown users, preventing email enumeration.
  • Shell escape RCE — DNS provider credentials correctly escaped with POSIX '\'' idiom.
  • Schema injection — Pattern constraints on user-supplied fields prevent nginx config injection.
  • Per-host rate limiting — limit_req_zone / limit_req with UI controls (rate req/s, burst, nodelay).
  • Cloudflare Turnstile — Opt-in bot protection on the login page (Settings UI). Includes secret key redaction, nonce replay protection, and CSP headers for the widget.
  • Login + 2FA rate limiting — express-rate-limit on /api/tokens (10 failed/15 min) and /api/tokens/2fa (10/5 min).
  • Cloudflare IP restriction — Global toggle (Settings UI) that silently drops (return 444) any proxy host request not from a Cloudflare edge IP. Protects origins from bypass attacks when all traffic flows through Cloudflare.
  • Session token security — JWT is no longer stored in window.localStorage. Any XSS in the official image yields a full session token via localStorage.getItem("authentications") — no further exploit needed, ~24 h access. This fork moves the token to an HttpOnly + SameSite=Strict cookie (npm_session) that JavaScript cannot read. A CSRF double-submit token (npm_csrf) prevents cross-site request forgery now that the credential is cookie-bound. Token rotated on login, 2FA, impersonation, and logout; preserved on 5-minute refresh to avoid in-flight 403 races. Bearer Authorization header still accepted for API clients and CI pipelines. Set FORCE_SECURE_COOKIES=true when the admin UI is behind a TLS-terminating edge (e.g. Cloudflare).
  • Per-host log viewer (admin-only) — Triage 5xx and unexpected 4xx responses from the admin UI without SSHing into the container. New "Logs" tab on the proxy-host, dead-host, and redirection-host modals shows the tail of /data/logs/{type}-{id}_{access,error}.log via a bounded seek-from-end reader (256 KiB chunk, 1000-line max). Hidden from non-admin users. Every read is audit-logged. Path-traversal proof — host_type is on a closed allow-list, id is asserted positive integer, stream is access|error only.
  • Logrotate enforcement — The base image ships /etc/logrotate.d/nginx-proxy-manager but the container has no cron daemon, so it never fired upstream. This fork adds an s6 longrun service that runs logrotate /etc/logrotate.conf every LOGROTATE_INTERVAL (default 86400 = 24 h). Access logs rotate weekly × 4, error logs weekly × 10, both compressed.
  • TLS — ssl_prefer_server_ciphers on; TLS 1.2+ only.

⁠Source

Tag summary

Content type

Image

Digest

sha256:4132e5e57…

Size

256.4 MB

Last updated

3 days ago

docker pull jrandombytes/nginx-proxy-manager