@rexaray008/nodeproxy 1.0.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
1
+ # nodeproxy
2
+
3
+ A tiny, zero-dependency reverse proxy for Node.js ("a mini nginx") with built-in Cloudflare Tunnel support — so a project running on your own machine can be reached at a real domain, with no port forwarding and no static IP.
4
+
5
+ This README is a complete walkthrough from a fresh clone to a live public domain. Follow it top to bottom the first time; after that, day-to-day usage is a single command (Step 7).
6
+
7
+ ---
8
+
9
+ ## What you'll end up with
10
+
11
+ ```
12
+ Internet --https--> Cloudflare edge --tunnel--> cloudflared (your machine) --> nodeproxy (port 8080) --> your project (e.g. port 3000)
13
+ ```
14
+
15
+ Cloudflare terminates TLS and routes traffic through an outbound-only tunnel to `cloudflared` on your machine. `cloudflared` hands it to `nodeproxy`, which routes it to whichever local project you've configured. Nothing needs to be exposed on your router or firewall.
16
+
17
+ ---
18
+
19
+ ## Prerequisites
20
+
21
+ Before you start, make sure you have:
22
+
23
+ - **Node.js 16 or newer** — check with `node -v`. Get it from https://nodejs.org if missing.
24
+ - **A project already running locally** that you want to expose — e.g. something listening on `http://localhost:3000`. It can be anything (Express, Next.js, a static site server, etc.) — nodeproxy just forwards to it.
25
+ - **(Only if using a real domain) A domain added to a Cloudflare account**, with its nameservers pointed at Cloudflare. The free plan is enough. If you just want to test locally without a public domain, skip Steps 5–7.
26
+ - **(Linux only, for auto-install) `curl` and `sudo`** available in your shell.
27
+ - **(macOS only, for auto-install) Homebrew** installed (https://brew.sh).
28
+
29
+ ---
30
+
31
+ ## Step 1 — Get the files
32
+
33
+ Unzip the package and move into it:
34
+
35
+ ```bash
36
+ unzip nodeproxy.zip
37
+ cd nodeproxy
38
+ ```
39
+
40
+ ## Step 2 — Make the `nodeproxy` command available
41
+
42
+ ```bash
43
+ npm link
44
+ ```
45
+
46
+ This registers `nodeproxy` as a global command pointing at this folder. Verify it worked:
47
+
48
+ ```bash
49
+ nodeproxy help
50
+ ```
51
+
52
+ You should see the full command list. (If you'd rather not touch global npm links, you can run everything below as `node bin/cli.js ...` instead of `nodeproxy ...` — they're equivalent.)
53
+
54
+ ## Step 3 — Start the proxy for the first time
55
+
56
+ ```bash
57
+ nodeproxy start
58
+ ```
59
+
60
+ Expected output:
61
+ ```
62
+ [nodeproxy] Using config: /path/to/nodeproxy.config.json
63
+ [nodeproxy] (edit this file, or use "nodeproxy add <host> <target>", to change routing)
64
+ [nodeproxy] HTTP listening on port 8080
65
+ ```
66
+
67
+ On this first run, nodeproxy auto-creates `nodeproxy.config.json` next to the CLI, defaulting to: listen on port 8080, forward everything to `http://localhost:3000`.
68
+
69
+ Leave this running and, in a second terminal, check it works:
70
+
71
+ ```bash
72
+ curl http://localhost:8080
73
+ ```
74
+
75
+ If your project is already running on port 3000, you'll see its response. If not, you'll get a `502 Bad Gateway` — that's expected until your project is up; it just means the proxy itself is working correctly.
76
+
77
+ Stop it for now with `Ctrl+C` — you'll restart it with the tunnel in Step 7.
78
+
79
+ ## Step 4 — Point it at your actual project(s)
80
+
81
+ If your project runs on a port other than 3000, edit `nodeproxy.config.json` directly, or:
82
+
83
+ ```bash
84
+ nodeproxy add "*" http://localhost:YOUR_PORT
85
+ ```
86
+
87
+ **Running more than one project?** Give each its own listener/port on the proxy side:
88
+
89
+ ```bash
90
+ nodeproxy listen 9090
91
+ nodeproxy add "*" http://localhost:4000 --port 9090
92
+ ```
93
+
94
+ Now port 8080 and port 9090 on the proxy are two completely independent routes. You can also route by domain name instead of `*` if a single port should serve multiple domains — see `nodeproxy.config.json` for the `routes` array format (each entry is `{ host, targets, staticDir }`).
95
+
96
+ Skip to Step 8 if you only want local routing and don't need a public domain yet.
97
+
98
+ ## Step 5 — One-time Cloudflare login and tunnel creation
99
+
100
+ ```bash
101
+ nodeproxy tunnel setup --domain app.yourdomain.com --port 8080
102
+ ```
103
+
104
+ Replace `app.yourdomain.com` with the actual subdomain you want live, and `8080` with whichever proxy port you want it to reach (from Step 4).
105
+
106
+ What happens, in order:
107
+
108
+ 1. **cloudflared install check.** If missing, nodeproxy tries to install it automatically (Homebrew on macOS, direct binary download on Linux). If auto-install fails on your platform, it prints the exact manual install command and stops — just run that command and re-run `tunnel setup`.
109
+ 2. **Browser login.** If this machine hasn't authorized with Cloudflare before, your browser opens to a Cloudflare page. Log in and click **Authorize**. This is the one manual step in the entire setup — it's how Cloudflare's OAuth flow works and can't be scripted by any tool. It only happens once per machine.
110
+ 3. **Tunnel creation.** Creates a named tunnel (defaults to your domain with dots replaced by dashes, e.g. `app-yourdomain-com`).
111
+ 4. **Ingress config.** Writes `~/.cloudflared/<name>.yml` mapping your domain to `http://localhost:8080` (or whichever port you passed).
112
+ 5. **DNS routing.** Automatically adds the CNAME record in your Cloudflare DNS pointing your domain at the tunnel.
113
+
114
+ Expected final output:
115
+ ```
116
+ [tunnel] Done! "app.yourdomain.com" now points at your local port 8080.
117
+ [tunnel] From now on, just run: nodeproxy start --tunnel --name app-yourdomain-com
118
+ ```
119
+
120
+ ## Step 6 — DNS propagation
121
+
122
+ Cloudflare DNS usually updates within seconds to a couple of minutes. You can check with:
123
+
124
+ ```bash
125
+ dig app.yourdomain.com
126
+ ```
127
+
128
+ You should see it resolving to Cloudflare's IPs (not your own). This confirms DNS is correctly routed — the tunnel itself only carries traffic once it's actually running (next step).
129
+
130
+ ## Step 7 — Go live (the one command you'll use from now on)
131
+
132
+ ```bash
133
+ nodeproxy start --tunnel
134
+ ```
135
+
136
+ This single command starts both the local proxy and the Cloudflare tunnel together. Expected output:
137
+ ```
138
+ [nodeproxy] Using config: /path/to/nodeproxy.config.json
139
+ [nodeproxy] HTTP listening on port 8080
140
+ [tunnel] Starting cloudflared tunnel "app-yourdomain-com"...
141
+ ```
142
+
143
+ Visit `https://app.yourdomain.com` in a browser — it should now serve your local project. TLS is handled entirely by Cloudflare's edge, so `https://` works immediately with no certificates on your end.
144
+
145
+ If you created the tunnel with a custom `--name` in Step 5, pass it here too:
146
+ ```bash
147
+ nodeproxy start --tunnel --name app2
148
+ ```
149
+
150
+ ## Step 8 — Verify everything end to end
151
+
152
+ ```bash
153
+ # 1. Confirm the local proxy responds
154
+ curl -H "Host: app.yourdomain.com" http://localhost:8080
155
+
156
+ # 2. Confirm the public domain responds the same way
157
+ curl https://app.yourdomain.com
158
+ ```
159
+
160
+ Both should return the same content from your backend project. If step 1 works but step 2 doesn't, the issue is in the tunnel/DNS layer (see Troubleshooting). If step 1 itself fails, the issue is your local project or the `add`/`listen` routing config, not Cloudflare.
161
+
162
+ ---
163
+
164
+ ## Running multiple domains/projects through tunnels
165
+
166
+ Repeat Steps 4–7 per project, giving each a distinct proxy port and tunnel name:
167
+
168
+ ```bash
169
+ # Project A
170
+ nodeproxy listen 8080 # if not already there by default
171
+ nodeproxy add "*" http://localhost:3000 --port 8080
172
+ nodeproxy tunnel setup --domain appA.yourdomain.com --port 8080 --name appA
173
+
174
+ # Project B
175
+ nodeproxy listen 9090
176
+ nodeproxy add "*" http://localhost:4000 --port 9090
177
+ nodeproxy tunnel setup --domain appB.yourdomain.com --port 9090 --name appB
178
+ ```
179
+
180
+ Each tunnel needs its own `nodeproxy start --tunnel --name <name>` process, so run them in separate terminals (or as separate background services — see below).
181
+
182
+ ---
183
+
184
+ ## Keeping it running permanently (survive terminal close / reboot)
185
+
186
+ `nodeproxy start --tunnel` runs in the foreground and stops if you close the terminal. For a server you want always-on, run it as a background service.
187
+
188
+ **Linux (systemd)** — create `/etc/systemd/system/nodeproxy.service`:
189
+ ```ini
190
+ [Unit]
191
+ Description=nodeproxy with Cloudflare Tunnel
192
+ After=network.target
193
+
194
+ [Service]
195
+ WorkingDirectory=/path/to/nodeproxy
196
+ ExecStart=/usr/bin/env nodeproxy start --tunnel
197
+ Restart=always
198
+ User=YOUR_USERNAME
199
+
200
+ [Install]
201
+ WantedBy=multi-user.target
202
+ ```
203
+ Then:
204
+ ```bash
205
+ sudo systemctl enable --now nodeproxy
206
+ sudo systemctl status nodeproxy
207
+ ```
208
+
209
+ **macOS (launchd)** or a process manager like `pm2`/`forever` work equally well if you prefer those — the command to keep alive is just `nodeproxy start --tunnel`.
210
+
211
+ ---
212
+
213
+ ## Troubleshooting
214
+
215
+ | Symptom | Likely cause | Fix |
216
+ |---|---|---|
217
+ | `502 Bad Gateway` from `curl localhost:8080` | Your local project isn't running, or is on a different port than configured | Start your project, or run `nodeproxy add "*" http://localhost:CORRECT_PORT` |
218
+ | `404 Not Found (no matching route...)` | The `Host` header doesn't match any route and there's no `*` catch-all on that listener | Add a catch-all: `nodeproxy add "*" http://localhost:PORT --port PROXY_PORT` |
219
+ | `tunnel setup` fails at install | Your platform isn't macOS/Linux auto-install, or missing `sudo`/Homebrew | Run the manual install command it printed, then re-run `tunnel setup` |
220
+ | Browser doesn't open during login | Headless/remote machine (e.g. SSH session) | `cloudflared tunnel login` prints a URL to the terminal too — open it manually on any device and authorize |
221
+ | `curl localhost:8080` works but the public domain doesn't | Tunnel isn't running, or DNS hasn't propagated yet | Confirm `nodeproxy start --tunnel` is actually running and check `dig yourdomain.com`; wait a minute and retry |
222
+ | "Port X already has a listener" | You tried `nodeproxy listen` on a port that's already configured | Use `nodeproxy add ... --port X` directly instead of `listen` |
223
+ | Changes to `nodeproxy.config.json` don't take effect | The proxy only reads the config at startup | Restart `nodeproxy start` (Ctrl+C, then run it again) |
224
+
225
+ ---
226
+
227
+ ## Command reference
228
+
229
+ ```
230
+ nodeproxy start [--config <file>] [--tunnel] [--name <name>]
231
+ Start the proxy. --tunnel also starts
232
+ the Cloudflare tunnel alongside it.
233
+ nodeproxy init [--config <file>] Just create the config file.
234
+ nodeproxy add <host> <target> [--port <n>] [--config <file>]
235
+ Add/update a route on a listener.
236
+ <host> can be a real domain or "*".
237
+ nodeproxy listen <port> [--config <file>]
238
+ Open a new, independent listener on
239
+ a new port.
240
+ nodeproxy tunnel setup --domain <domain> [--name <name>] [--port <n>]
241
+ One-time: install cloudflared, log
242
+ in, create tunnel, route DNS.
243
+ nodeproxy tunnel start [--name <name>] Run just the tunnel (no local proxy).
244
+ nodeproxy help Show all commands.
245
+ ```
246
+
247
+ ## Config file reference (`nodeproxy.config.json`)
248
+
249
+ ```json
250
+ {
251
+ "logging": true,
252
+ "servers": [
253
+ {
254
+ "port": 8080,
255
+ "https": { "enabled": false, "key": "", "cert": "" },
256
+ "routes": [
257
+ { "host": "*", "targets": ["http://localhost:3000"], "staticDir": null }
258
+ ]
259
+ }
260
+ ]
261
+ }
262
+ ```
263
+
264
+ - `servers` — one entry per port the proxy listens on; each is fully independent.
265
+ - `routes[].host` — a real hostname, or `"*"` as a catch-all for that listener.
266
+ - `routes[].targets` — one or more backend URLs; 2+ enables automatic round-robin load balancing.
267
+ - `routes[].staticDir` — set this and leave `targets` empty to serve static files from a folder instead of proxying.
268
+ - `https` — only needed if you're terminating TLS yourself instead of using Cloudflare Tunnel (uncommon once you're using a tunnel, since Cloudflare's edge already handles TLS).
269
+
270
+ ## Project layout
271
+ ```
272
+ bin/cli.js CLI entrypoint
273
+ lib/server.js Core proxy engine (multi-listener routing, load balancing, static serving)
274
+ lib/config.js Config file loading/creation
275
+ lib/cloudflare.js Cloudflare Tunnel install/login/create/route/run helpers
276
+ ```
277
+
278
+ ## Known limitations
279
+ - No automatic Let's Encrypt renewal for self-managed HTTPS (not needed with Cloudflare Tunnel)
280
+ - One cert per listener if self-managing HTTPS (no per-domain SNI certs)
281
+ - No rate limiting, caching, or WAF
282
+ - Round-robin load balancing only
283
+ - The Cloudflare browser-login click is inherently manual and one-time per machine — this is Cloudflare's security design, not a gap in this tool
package/README.md CHANGED
@@ -1,100 +1,309 @@
1
1
  # nodeproxy
2
2
 
3
- A tiny, zero-dependency reverse proxy / web server for Node.js — think "a mini nginx" you can read in one sitting. No native code, no config-language DSL, just JSON.
3
+ A tiny, zero-dependency reverse proxy for Node.js ("a mini nginx"), with:
4
+ - **pm2 integration** — nodeproxy can start and keep your own project alive, so you don't have to manually run it first
5
+ - **Cloudflare Tunnel integration** — expose your local machine on a real domain, no port forwarding, no static IP
4
6
 
5
- ## Features
6
- - Host-based virtual routing (`api.example.com` → one backend, `app.example.com` → another)
7
- - Round-robin load balancing across multiple upstream targets per host
8
- - Wildcard `*` catch-all host for local dev
9
- - Static file fallback (serve a folder directly, no backend needed)
10
- - HTTPS support (bring your own cert/key)
11
- - Zero npm dependencies — pure Node.js `http`/`https` core modules
7
+ This README is a complete walkthrough from a fresh clone to "my project is live at my domain, and it survives crashes/reboots." Follow it top to bottom the first time; after that, day-to-day usage is one command (Step 8).
12
8
 
13
- ## One-command quick start
9
+ ---
10
+
11
+ ## What you'll end up with
12
+
13
+ ```
14
+ Internet --https--> Cloudflare edge --tunnel--> cloudflared (your machine) --> nodeproxy (8080) --> pm2-managed project (3000)
15
+ ```
16
+
17
+ pm2 keeps your project running (and restarts it if it crashes). nodeproxy forwards traffic to it. cloudflared carries traffic to your machine with no exposed ports. One `nodeproxy start --tunnel` brings all three layers up together.
18
+
19
+ ---
20
+
21
+ ## Prerequisites
22
+
23
+ - **Node.js 16+** — check with `node -v`.
24
+ - **npm** — used to auto-install `pm2` if it's missing (no manual install needed).
25
+ - **(Only for a public domain) A domain on a Cloudflare account**, nameservers already pointed at Cloudflare. Free plan is fine.
26
+ - **(Linux) `curl` and `sudo`** for cloudflared auto-install. **(macOS) Homebrew.**
27
+
28
+ ---
29
+
30
+
31
+
32
+ ## Step 1 — Register your project so nodeproxy starts it for you
33
+
34
+ This is the part that removes the "start your project manually first" step. Point it at your project's start command:
14
35
 
15
36
  ```bash
16
- npx nodeproxy start
37
+ nodeproxy app add myapp --cmd "npm start" --cwd /path/to/your/project
17
38
  ```
18
39
 
19
- That's it. On first run this:
20
- 1. Creates `nodeproxy.config.json` in the current directory with sensible defaults
21
- 2. Starts listening on port `8080`, forwarding everything to `http://localhost:3000`
40
+ - `myapp` any name you want, used for logs/status later
41
+ - `--cmd` — whatever normally boots your project (`npm start`, `node server.js`, `yarn dev`, etc.)
42
+ - `--cwd` the folder that command should run from
22
43
 
23
- Edit `nodeproxy.config.json` and restart to change routing, or use the CLI helper below.
44
+ This just registers it in `nodeproxy.config.json` nothing starts yet. Repeat this for each project you have (see multi-project section below).
45
+
46
+ **Already running your project some other way (Docker, a different process manager, always-on already)?** Skip this step entirely — nodeproxy will just proxy to whatever's listening on the port, same as before.
47
+
48
+ ## Step 2 — Point the proxy at it
49
+
50
+ By default nodeproxy forwards port 8080 → `http://localhost:3000`. If your project runs on a different port:
51
+
52
+ ```bash
53
+ nodeproxy add "*" http://localhost:YOUR_PORT
54
+ ```
55
+
56
+ ## Step 3 — First full local start
24
57
 
25
- ### Install globally instead
26
58
  ```bash
27
- npm install -g nodeproxy
28
59
  nodeproxy start
29
60
  ```
30
- (publish this folder to npm first, or just `npm link` it locally — see below)
31
61
 
32
- ### Run without installing (from this folder)
62
+ Expected output notice pm2 starting your app automatically, no manual `npm start` needed:
63
+ ```
64
+ [nodeproxy] Using config: /path/to/nodeproxy.config.json
65
+ [pm2] Starting "myapp" (npm start) in /path/to/your/project ...
66
+ [PM2] Done.
67
+ [nodeproxy] HTTP listening on port 8080
68
+ ```
69
+
70
+ Verify:
33
71
  ```bash
34
- node bin/cli.js start
72
+ curl http://localhost:8080
35
73
  ```
74
+ You should see your project's actual response — even though you never typed `npm start` yourself.
36
75
 
37
- ## Configuring routes
76
+ Check on your app anytime with:
77
+ ```bash
78
+ nodeproxy app list # status of every registered app
79
+ nodeproxy app logs myapp
80
+ nodeproxy app stop myapp
81
+ ```
82
+
83
+ Stop everything with `Ctrl+C` (nodeproxy) — your app keeps running under pm2 in the background even after that, exactly as pm2 is designed to. Use `nodeproxy app stop myapp` to actually stop it.
84
+
85
+ ## Step 4 — One-time Cloudflare login and tunnel creation
86
+
87
+ Only needed if you want a real public domain (skip to Step 9 if you're only using this locally).
38
88
 
39
89
  ```bash
40
- # Route traffic for a host to a backend
41
- nodeproxy add example.com http://localhost:4000
90
+ nodeproxy tunnel setup --domain app.yourdomain.com --port 8080
91
+ ```
92
+
93
+ What happens, in order:
94
+ 1. **cloudflared install check** — auto-installed if missing (Homebrew on macOS, direct binary on Linux; manual instructions printed if your platform needs it).
95
+ 2. **Browser login** — opens once so you can click **Authorize** in your Cloudflare account. This is the *only* manual step in the whole setup; it's how Cloudflare's OAuth login works and cannot be scripted by any tool, including Cloudflare's own CLI.
96
+ 3. **Tunnel creation** — a named tunnel is created for you.
97
+ 4. **Ingress config** — written to `~/.cloudflared/<name>.yml`, mapping your domain to `http://localhost:8080`.
98
+ 5. **DNS routing** — the CNAME record is added to Cloudflare DNS automatically.
42
99
 
43
- # Add a second target on the same host for load balancing
44
- # (just edit the "targets" array in nodeproxy.config.json directly)
100
+ Expected output:
101
+ ```
102
+ [tunnel] Done! "app.yourdomain.com" now points at your local port 8080.
103
+ [tunnel] From now on, just run: nodeproxy start --tunnel --name app-yourdomain-com
45
104
  ```
46
105
 
47
- Example `nodeproxy.config.json`:
106
+ ## Step 5 — Check DNS propagated
48
107
 
49
- ```json
50
- {
51
- "port": 8080,
52
- "https": { "enabled": false, "port": 8443, "key": "", "cert": "" },
53
- "logging": true,
54
- "routes": [
55
- { "host": "app.example.com", "targets": ["http://localhost:4000", "http://localhost:4001"], "staticDir": null },
56
- { "host": "static.example.com", "targets": [], "staticDir": "./public" },
57
- { "host": "*", "targets": ["http://localhost:3000"], "staticDir": null }
58
- ]
59
- }
108
+ ```bash
109
+ dig app.yourdomain.com
60
110
  ```
111
+ Should resolve to Cloudflare IPs within a minute or so.
61
112
 
62
- - `targets` with 2+ entries = automatic round-robin load balancing
63
- - `staticDir` set + empty `targets` = pure static file server for that host
64
- - `host: "*"` = catch-all, used when no other host matches (handy for local dev where you don't care about the `Host` header)
113
+ ## Step 6 **Go live: the one command you'll use from now on**
65
114
 
66
- ## HTTPS
115
+ ```bash
116
+ nodeproxy start --tunnel
117
+ ```
67
118
 
68
- ```json
69
- "https": {
70
- "enabled": true,
71
- "port": 8443,
72
- "key": "/path/to/privkey.pem",
73
- "cert": "/path/to/fullchain.pem"
74
- }
119
+ This starts, in order: your registered app(s) via pm2 → the local proxy → the Cloudflare tunnel. Visit `https://app.yourdomain.com` — it should serve your project. TLS is handled by Cloudflare's edge automatically.
120
+
121
+ ## Step 7 — Verify from more than one place
122
+
123
+ This matters — testing only from the machine running nodeproxy can hide DNS problems:
124
+
125
+ ```bash
126
+ # From the same machine — hits the local proxy directly, bypassing Cloudflare entirely
127
+ curl -H "Host: app.yourdomain.com" http://localhost:8080
128
+
129
+ # From the same machine — the real public path, through Cloudflare
130
+ curl https://app.yourdomain.com
131
+
132
+ # From a phone on cellular data, or ask a friend to open it
133
+ ```
134
+
135
+ If the first two work but a different device still can't reach it, jump straight to the "works on my machine, not on other devices" section below.
136
+
137
+ ---
138
+
139
+ ## "It works on my machine but not on another device/network"
140
+
141
+ This is almost always **one specific cause**, not a bug in the proxy: the DNS record Cloudflare created for your tunnel is set to **"DNS only" (grey cloud)** instead of **"Proxied" (orange cloud)**. Tunnel hostnames (`*.cfargotunnel.com`) only resolve *publicly* when proxied — when they're not, they can fail to resolve for everyone except a device that happens to have cached/local name resolution, which is exactly what makes it look like "works for me, not for anyone else."
142
+
143
+ Diagnose it directly instead of guessing:
144
+ ```bash
145
+ nodeproxy tunnel doctor --domain app.yourdomain.com
75
146
  ```
76
147
 
77
- Get free certs from Let's Encrypt (e.g. via `certbot`) and point `key`/`cert` at the files. HTTP and HTTPS run side-by-side.
148
+ This checks whether your domain actually resolves to a public IP address (not just whether a DNS record exists) and tells you exactly what's wrong. Example output when it's broken:
149
+ ```
150
+ ✗ Does NOT resolve to an IP address publicly — ENOTFOUND. This is almost always
151
+ because the DNS record is set to "DNS only" (grey cloud) in the Cloudflare
152
+ dashboard instead of "Proxied" (orange cloud). Fix: Cloudflare dashboard →
153
+ DNS → click the cloud icon next to this record so it turns orange, then
154
+ wait ~1 minute and retry.
155
+ ```
78
156
 
79
- ## Local install / publish
157
+ **The fix:** log into the Cloudflare dashboard → your domain → DNS → find the record for your subdomain → click the cloud icon next to it so it turns **orange** (Proxied) → wait about a minute → retest.
158
+
159
+ Other, less common causes if the doctor says DNS is fine but it still doesn't work elsewhere:
160
+ - `nodeproxy start --tunnel` isn't actually running at that moment — the tunnel only carries traffic while that process is alive.
161
+ - You're testing from a network that blocks outbound HTTPS or has its own DNS overrides (rare, mostly corporate networks).
162
+ - You created more than one tunnel for the same domain by accident — run `cloudflared tunnel list` and delete stale ones.
163
+
164
+ ---
165
+
166
+ ## Running more than one project
167
+
168
+ Each project needs: its own pm2 app registration, its own proxy port, and (if public) its own tunnel.
80
169
 
81
170
  ```bash
82
- npm link # makes the `nodeproxy` command available globally on this machine
83
- # or
84
- npm publish # to publish it as a real npm package
171
+ # Project A port 8080
172
+ nodeproxy app add appA --cmd "npm start" --cwd /path/to/project-a
173
+ nodeproxy add "*" http://localhost:3000 # project A's actual listen port
174
+ nodeproxy tunnel setup --domain appA.yourdomain.com --port 8080 --name appA
175
+
176
+ # Project B — new port 9090
177
+ nodeproxy app add appB --cmd "npm start" --cwd /path/to/project-b
178
+ nodeproxy listen 9090
179
+ nodeproxy add "*" http://localhost:4000 --port 9090 # project B's actual listen port
180
+ nodeproxy tunnel setup --domain appB.yourdomain.com --port 9090 --name appB
85
181
  ```
86
182
 
87
- ## Project layout
183
+ Run each tunnel in its own terminal (or background service — see below):
184
+ ```bash
185
+ nodeproxy start --tunnel --name appA
186
+ nodeproxy start --tunnel --name appB
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Making it survive a reboot / terminal close
192
+
193
+ `nodeproxy start --tunnel` runs in the foreground. Your pm2-managed app itself will actually survive that terminal closing (pm2 daemonizes it), but the proxy and tunnel won't unless you run one of these:
194
+
195
+ **pm2 boot persistence** (for your app surviving a full machine reboot):
196
+ ```bash
197
+ pm2 startup # prints a sudo command — copy/run it, once
198
+ pm2 save # nodeproxy already runs this for you after "start"
199
+ ```
200
+
201
+ **systemd (Linux)** for the proxy+tunnel — create `/etc/systemd/system/nodeproxy.service`:
202
+ ```ini
203
+ [Unit]
204
+ Description=nodeproxy with Cloudflare Tunnel
205
+ After=network.target
206
+
207
+ [Service]
208
+ WorkingDirectory=/path/to/nodeproxy
209
+ ExecStart=/usr/bin/env nodeproxy start --tunnel
210
+ Restart=always
211
+ User=YOUR_USERNAME
212
+
213
+ [Install]
214
+ WantedBy=multi-user.target
88
215
  ```
89
- bin/cli.js CLI entrypoint (start / init / add / help)
90
- lib/server.js Core proxy engine (routing, load balancing, static serving)
91
- lib/config.js Config file loading/creation
216
+ ```bash
217
+ sudo systemctl enable --now nodeproxy
92
218
  ```
93
219
 
94
- ## Limitations (intentional this is a learning-scale project, not production nginx)
95
- - No automatic Let's Encrypt / ACME renewal — bring your own certs
96
- - No SNI-based multi-cert HTTPS (one cert for all HTTPS hosts)
97
- - No rate limiting, caching, or WAF features
98
- - Round-robin only (no least-connections/weighted balancing)
220
+ macOS: use `launchd`, or just keep `nodeproxy start --tunnel` itself under pm2 too (`pm2 start "nodeproxy start --tunnel" --name gateway`).
221
+
222
+ ---
223
+
224
+ ## Troubleshooting
225
+
226
+ | Symptom | Likely cause | Fix |
227
+ |---|---|---|
228
+ | `502 Bad Gateway` locally | Your app isn't running, wrong port, or pm2 failed to start it | `nodeproxy app list` to check status; `nodeproxy app logs myapp` for errors |
229
+ | `404 Not Found (no matching route...)` | No `*` catch-all or matching host on that listener | `nodeproxy add "*" http://localhost:PORT --port PROXY_PORT` |
230
+ | pm2 app shows `errored` in `app list` | The `--cmd`/`--cwd` you registered doesn't actually start the app | Test the exact command manually in that folder first, then re-run `nodeproxy app add` |
231
+ | Works locally, not on another device | DNS record not proxied (grey cloud) | `nodeproxy tunnel doctor --domain yourdomain.com` — see dedicated section above |
232
+ | `tunnel setup` fails at install | Platform isn't auto-installable, or missing sudo/Homebrew | Run the manual command it prints, then retry |
233
+ | Browser doesn't open during login | Headless/SSH session | `cloudflared tunnel login` also prints a URL — open it on any device |
234
+ | Config changes don't take effect | Config is only read at startup | Restart `nodeproxy start` |
235
+
236
+ ---
237
+
238
+ ## Command reference
239
+
240
+ ```
241
+ nodeproxy start [--config <file>] [--tunnel] [--name <name>]
242
+ Starts registered apps (pm2), then
243
+ the proxy, then the tunnel if
244
+ --tunnel is given. The one command.
245
+ nodeproxy init [--config <file>] Just create the config file.
246
+ nodeproxy add <host> <target> [--port <n>] [--config <file>]
247
+ Add/update a proxy route.
248
+ nodeproxy listen <port> [--config <file>]
249
+ Open a new independent listener.
250
+
251
+ nodeproxy app add <name> --cmd "<cmd>" [--cwd <dir>]
252
+ Register a project to be started
253
+ and kept alive via pm2.
254
+ nodeproxy app list Show registered apps + live status.
255
+ nodeproxy app logs <name> Tail an app's logs.
256
+ nodeproxy app stop <name> Stop an app.
257
+
258
+ nodeproxy tunnel setup --domain <domain> [--name <name>] [--port <n>]
259
+ One-time: install, log in, create
260
+ tunnel, route DNS.
261
+ nodeproxy tunnel start [--name <name>] Run just the tunnel.
262
+ nodeproxy tunnel doctor --domain <domain>
263
+ Diagnose public-access DNS issues.
264
+
265
+ nodeproxy help Show all commands.
266
+ ```
267
+
268
+ ## Config file reference (`nodeproxy.config.json`)
269
+
270
+ ```json
271
+ {
272
+ "logging": true,
273
+ "apps": [
274
+ { "name": "myapp", "cmd": "npm start", "cwd": "/path/to/project" }
275
+ ],
276
+ "servers": [
277
+ {
278
+ "port": 8080,
279
+ "https": { "enabled": false, "key": "", "cert": "" },
280
+ "routes": [
281
+ { "host": "*", "targets": ["http://localhost:3000"], "staticDir": null }
282
+ ]
283
+ }
284
+ ]
285
+ }
286
+ ```
287
+
288
+ - `apps` — projects nodeproxy starts/keeps alive via pm2. Optional.
289
+ - `servers` — one entry per proxy port; each fully independent.
290
+ - `routes[].host` — real hostname or `"*"` catch-all.
291
+ - `routes[].targets` — 2+ enables round-robin load balancing.
292
+ - `routes[].staticDir` — serve static files instead of proxying.
293
+ - `https` — only for self-managed TLS; not needed with Cloudflare Tunnel.
294
+
295
+ ## Project layout
296
+ ```
297
+ bin/cli.js CLI entrypoint
298
+ lib/server.js Core proxy engine
299
+ lib/config.js Config loading/creation
300
+ lib/cloudflare.js Cloudflare Tunnel install/login/create/route/run/diagnose
301
+ lib/pm2.js pm2 install/start/stop/list helpers
302
+ ```
99
303
 
100
- These are all reasonable next steps if you want to extend it.
304
+ ## Known limitations
305
+ - No automatic Let's Encrypt renewal for self-managed HTTPS (not needed with Cloudflare Tunnel)
306
+ - Round-robin load balancing only
307
+ - The Cloudflare browser-login click is inherently manual and one-time per machine — Cloudflare's security design, not a gap here
308
+ - `tunnel doctor` diagnoses the most common public-access failure (unproxied DNS); it isn't an exhaustive network debugger
309
+