Losslessly import an existing, hand-written nginx config into an editable visual topology — and round-trip it back out verbatim, without dropping or fabricating a single directive. From there, design on a canvas, compile to real nginx config with a pure-TypeScript compiler, validate it with
nginx -tin a throwaway sandbox, and deploy to a remote Linux host over SSH.
Nginx Flow Manager (NFM) turns nginx administration into a visual workflow. Its genuine differentiator is the verbatim round-trip import: point it at a live, hand-written nginx tree and it parses the real config — comments, ordering, and unmodeled blocks included — into an editable canvas, then compiles it back out byte-for-faithfully. Tools like Nginx Proxy Manager, Caddy, or Ansible make you adopt their model of your config; NFM adopts yours. You lay out servers, locations, upstreams and global blocks as nodes on an interactive canvas; the app compiles that graph into actual nginx files, tests them against a real nginx binary in an isolated sandbox, and pushes the result to your server — either through a hardened on-server nfm-agent or a direct SSH/local fallback. Because the compiler and parser are designed for exact fidelity, you can import an existing config and round-trip it without losing or fabricating a single directive.
- Visual canvas editor — drag-and-drop nginx topology built on @xyflow/react, with automatic layout.
- Multi-site management — each site maps to a file in
sites-available, with anis_enabledflag driving thesites-enabledsymlink. - Import existing config (verbatim fidelity) — parse a live nginx tree into the canvas and round-trip it; anything not modeled graphically is preserved exactly as
raw_confignodes. The parser ↔ compiler verbatim invariant is a core design rule. nginx -tsandbox validation — every candidate config is tested against a real nginx binary inside a throwaway sandbox (remote/tmpover SSH, or local), so your live config is never touched by validation.- Remote deploy over SSH — atomic write →
nginx -t→ reload, with automatic rollback on failure. Deploys via the hardened nfm-agent when installed, or a legacy direct-SSH / local fallback. - TLS / Let's Encrypt (certbot) — issue, list and renew certificates on the managed host (
certbot certonly/certbot renew), and reuse a certbot cert for the panel's own HTTPS. - Diff view & version history — review changes before deploying; commit topology snapshots with author/message and roll back to any prior version.
- Live log tail — stream
accessanderrorlogs over Server-Sent Events. - Live traffic animation — animate request flow across the canvas edges from parsed log events.
- First-class graphical directives — HTTP/2, HSTS, WebSocket upgrade,
try_files/alias, proxy tuning,expirescaching, upstream response caching (proxy_cache), andallow/denyaccess control are editable as structured fields, not raw text. - conf.d / snippets editing — included files outside the topology are kept as
extra_filesand written back verbatim. - Multi-user RBAC — three roles (admin / operator / viewer) with a single deny-by-default authorization gate: viewers read-only, operators edit + deploy, admins also manage users and system/agent settings. Manage users from the UI; an existing single-admin install is migrated transparently.
- English & Spanish UI (i18n) — the language is auto-detected from the browser locale and switchable any time with the EN/ES toggle in the header (also on the login screen). Config text is never localized — nginx output stays byte-exact in either language.
Canvas ──▶ Compile ──▶ Validate ──▶ Deploy
(topology) (nginxCompiler) (nginx -t) (agent / SSH)
- Canvas — you model the topology as typed nodes and edges (persisted server-side in
workspace-state.jsonso every browser sees the same workspace). - Compile —
src/utils/nginxCompiler.tsturns the graph into aCompiledNginxOutputmap of{ absolute path → file contents }, entirely in TypeScript (no nginx needed to generate the files). - Validate — the candidate files are written into a throwaway sandbox and checked with
nginx -t; the real config is left untouched. - Deploy — validated files are written to the managed host and nginx is reloaded, with rollback if the reload fails.
The Files view shows exactly what your canvas compiles to — the generated nginx.conf and each
sites-available/*.conf, side by side with the running config and a diff. Here the location node's
visual Cache responses toggle produced the real proxy_cache block, and the shared keys-zone was
auto-generated in nginx.conf — no hand-editing:
| Node | Purpose |
|---|---|
server |
A server { } block (a virtual host / site). |
location |
A location { } block, nestable, attached to a server. |
upstream |
An upstream { } pool of backend servers. |
global_core |
Top-level (main-context) core directives. |
global_http |
http { } block settings. |
global_gzip |
gzip / compression settings. |
global_stream |
stream { } (L4 TCP/UDP) settings. |
custom_module |
Loadable-module directives. |
raw_config |
Verbatim nginx text for anything not modeled graphically — auto-generated on import so nothing is dropped. |
- Node.js (with
npm). - A remote Linux host with nginx to manage (the panel can run anywhere — it talks to the host over SSH; a local/offline mode also exists).
- Optional: certbot on the managed host for Let's Encrypt, and the nfm-agent for hardened deploys (see docs/AGENT.md).
npm install
npm run devThe dev server (Vite in middleware mode + Express) serves the panel over HTTPS only at https://localhost:3000. On first boot a self-signed certificate is generated into certs/, so your browser will show a self-signed warning — accept it to continue. On first run you'll be guided through the setup wizard (admin account, nginx path/binary, local vs. remote/SSH target, panel port).
npm run build # vite build + esbuild-bundles server.ts → dist/server.cjs
npm run start # node dist/server.cjs (set NODE_ENV=production)docker compose up -d --build
# open https://localhost:3000 → accept the self-signed cert → run the setup wizardThe image contains no state or secrets — all writable data (workspace-state.json, app-config.json, agent-config.json, the master key, certs/, logs/) lives in the nfm-data named volume, so a fresh container starts clean at the setup wizard and your config survives rebuilds. The container manages a remote nginx host over SSH (or the nfm-agent), so it does not bundle nginx itself. Override the port with -e NFM_PORT=… (and the matching ports: mapping).
| Variable | Effect | Default |
|---|---|---|
NFM_PORT |
Panel listen port (takes precedence over the configured port). | 3000 |
NFM_HOST |
Bind address. Set 127.0.0.1 and front with a reverse proxy when the host isn't network-isolated. |
0.0.0.0 |
NODE_ENV |
production serves the built dist/ and tightens the panel CSP; otherwise Vite dev middleware is used. |
unset |
The panel port can also be changed in-app (persisted to app-config.json, applied on next restart); NFM_PORT always wins while set. See docs/CONFIGURATION.md.
- Backend: Express 4 + ssh2, TypeScript, run with
tsx(dev) / esbuild bundle (prod). HTTPS-only. - Frontend: React 19 + Vite + @xyflow/react, Tailwind CSS, lucide-react, motion.
- Compiler/parser: pure TypeScript (
src/utils/nginxCompiler.ts+src/utils/nginxParser.ts), no nginx dependency to generate or parse files.
server.ts Express API, auth, TLS, deploy, validation, certbot, logs
ssh-helper.ts SSH primitives (exec, read/write/dir, host-key pinning)
agent-client.ts JSON-RPC client for the on-server agent
agent-install.ts Uploads/installs the nfm-agent over SSH
agent/ The hardened on-server nfm-agent (main, ops, rpc, security)
src/
App.tsx, main.tsx React entry / shell
types.ts Topology model (nodes, sites, commits, compiled output)
context/TopologyContext.tsx Canvas + version-history state
components/ Canvas, custom nodes, managers (Site, Cert, Tls, Version, Agent)
utils/
nginxParser.ts nginx config text → tokens → AST (pure TS; for import)
nginxImport.ts Parsed AST → canvas topology (parseSingleConfig)
nginxCompiler.ts Topology → nginx config files (the compiler)
trafficViz.ts Log events → animated canvas traffic
api.ts secureFetch (cookie + CSRF) client
layoutSolver.ts Automatic node layout
The nginx parser (config text → AST) lives in src/utils/nginxParser.ts (tokenizeNginx / parseNginxAST, plus the AST types) — it is pure TypeScript, no longer embedded in server.ts. The import layer that turns a parsed AST into a canvas topology (parseSingleConfig) is being extracted into src/utils/nginxImport.ts.
- docs/ARCHITECTURE.md — topology model, compiler, parser, data flow, and the agent.
- docs/USAGE.md — end-user guide: canvas, node types, deploy, certs, logs, versions.
- docs/CONFIGURATION.md — install/setup,
app-config.json, env vars, panel HTTPS, ports. - docs/SECURITY.md — auth, sessions, secrets, agent confinement, the hardening model.
- docs/AGENT.md — the on-server nfm-agent: install, forced-command SSH, HMAC RPC, ops, fallbacks.
- docs/API.md — HTTP API reference (endpoints, auth, request/response).
- docs/BACKUP.md — backup, restore, and disaster recovery (what to copy, the master-key caveat).
The panel is HTTPS-only and binds 127.0.0.1 by default (opt into 0.0.0.0 via NFM_HOST). Authentication uses an HttpOnly, Secure, SameSite=Strict session cookie (nfm_session) plus an un-forgeable X-NFM-CSRF header on every state-changing request, with scrypt-hashed credentials, session TTLs, and login rate-limiting. Authorization is role-based (admin/operator/viewer) via a single deny-by-default decision the server enforces on every request. Secrets (app-config.json, workspace-state.json, agent-config.json, certs, .env*) are written with restrictive permissions, gitignored, and denied from the dev file server. For hardened deploys, the nfm-agent runs behind an SSH forced command with HMAC-authenticated RPC and filesystem path confinement. See docs/SECURITY.md.
Apache-2.0.

