simple HTTP | HTTPS | WS | WSS reverse proxy in node.js. currently supports:
- reverse proxy using incoming request's
x-forwarded-hostorhostheader to pre-configured origin servers. see Host and X-Forwarded-Host - proxy incoming
http:orhttps:request to originhttp:orhttps:servers - proxy incoming
ws:orwss:request to originws:orwss:servers - supports graceful shutdowns
- supports round-robin origin server selection (optional TCP health checks)
- supports force upgrade
http:tohttps:andws:towss: - supports forced redirects for host migration
- supports URL parts preservation during redirects
- multi-core via
node:cluster(workersconfig; default = CPU count) - keep-alive outbound agents, stream backpressure, and configurable timeouts
- async structured JSON logging (
logLevel, optionalaccessLog) - optional metrics (
metricsEnabled/ localhostadminPortβGET /metrics)
Requires Node.js >= 20.
- for global installation
npm i fimiproxy -g - for local installation
npm i fimiproxy - for local dev-dependency installation
npm i fimiproxy -D
replace npm with yarn or any other package manager of choice.
{
"exposeHttpProxy": false,
"httpPort": "",
"exposeHttpsProxy": false,
"httpsPort": "",
"exposeWsProxyForHttp": false,
"exposeWsProxyForHttps": false,
"httpsPublicKeyFilepath": "",
"httpsPrivateKeyFilepath": "",
"httpsPublicKey": "",
"httpsPrivateKey": "",
"debug": false,
"workers": 1,
"originTimeoutMs": 30000,
"headersTimeoutMs": 60000,
"requestTimeoutMs": 0,
"keepAliveTimeoutMs": 5000,
"maxSockets": 256,
"maxFreeSockets": 256,
"maxConnections": 0,
"accessLog": false,
"logLevel": "info",
"originHealthEnabled": false,
"originHealthIntervalMs": 10000,
"metricsEnabled": false,
"metricsLogIntervalMs": 60000,
"adminPort": "",
"routes": [
{
"origin": [
{
"originHost": "",
"originPort": "",
"originProtocol": "http:"
},
{
"originHost": "",
"originPort": "",
"originProtocol": "ws:"
}
],
"incomingHostAndPort": "",
"forceUpgradeHttpToHttps": false,
"forceUpgradeWsToWss": false,
"forceRedirect": false,
"usePermanentRedirect": false,
"redirectHost": "",
"redirectURLParts": false,
"overrideHost": ""
}
],
"forceUpgradeHttpToHttps": false,
"forceUpgradeWsToWss": false,
"usePermanentRedirect": false,
"redirectHost": "",
"redirectURLParts": false
}exposeHttpProxyβ set totrueto expose an HTTP server, requireshttpPortto be set iftrueexposeHttpsProxyβ set totrueto expose an HTTPS server, requireshttpsPort,httpsPublicKeyORhttpsPublicKeyFilepath,httpsPrivateKeyORhttpsPrivateKeyFilepathto be set iftrueexposeWsProxyForHttpβ set totrueto expose a WebSocket server for HTTP requests, requireshttpPortandexposeHttpProxyto be set iftrueexposeWsProxyForHttpsβ set totrueto expose a WebSocket server for HTTPS requests, requireshttpsPortandexposeHttpsProxyto be set iftruehttpPortβ port HTTP server should listen on, whenexposeHttpProxyistruehttpsPortβ port HTTPS server should listen on, whenexposeHttpsProxyistruehttpsPublicKeyFilepathβ filepath to TLS certificate (public key) used with HTTPS serverhttpsPrivateKeyFilepathβ filepath to TLS private key used with HTTPS serverhttpsPublicKeyβ TLS certificate (public key) string used with HTTPS server. takes precedence overhttpsPublicKeyFilepathhttpsPrivateKeyβ TLS private key string used with HTTPS server. takes precedence overhttpsPrivateKeyFilepathdebugβ set totrueto enable debug logging for troubleshooting (also forceslogLeveltodebug)workersβ number of cluster worker processes. Default:os.availableParallelism(). Set to1for a single process (recommended for tests and simple local use). Whenworkers > 1, a primary process forks workers that share the listen ports; keep your process manager / deploycountat 1 (do not run multiple independent masters on :80/:443)originTimeoutMsβ timeout for origin HTTP requests and WebSocket opens (default30000)headersTimeoutMsβ inbound headers timeout (default60000)requestTimeoutMsβ inbound request timeout;0disables (default0)keepAliveTimeoutMsβ inbound keep-alive timeout (default5000)maxSockets/maxFreeSocketsβ outbound keep-alivehttp/httpsagent pool sizes (default256)maxConnectionsβ max concurrent inbound connections;0= unlimited (default0). Excess connections are dropped by NodeaccessLogβ whentrue, emit structured per-request access lines (defaultfalse)logLevelβerror|warn|info|debug(defaultinfo)originHealthEnabledβ whentrue, periodically TCP-check origins and skip unhealthy ones in round-robin (defaultfalse)originHealthIntervalMsβ health check interval (default10000)metricsEnabledβ whentrue, periodically log a metrics snapshot (defaultfalse)metricsLogIntervalMsβ metrics log interval (default60000)adminPortβ if set, listen on127.0.0.1:adminPortand serveGET /metricsas JSONforceUpgradeHttpToHttpsβ set totrueto force upgrade allhttp:requests tohttps:requests globallyforceUpgradeWsToWssβ set totrueto force upgrade allws:requests towss:requests globallyusePermanentRedirectβ set totrueto use permanent redirect globally. The proxy server will return a308redirect response to the client instead of the default307temporary redirect responseredirectHostβ default host to redirect to globally, e.g. when upgrading to HTTPS or WSS, or if the incoming host is no longer supported and all requests to it should be redirected somewhere else. if not set, the proxy server will redirect to the incomingx-forwarded-hostorhostheader fieldredirectURLPartsβ controls which URL parts are preserved during redirects. Can betrue(preserve all parts),false(preserve only host), or an object specifying which parts to preserve (see Route-level Configuration below)
Logs are structured JSON lines written asynchronously to stdout/stderr. Typical fields: level, msg, time, pid, optional workerId, plus event-specific fields (host, origin, status, durationMs, errors).
- Default: startup/route configuration + errors
accessLog: true: one access line per completed HTTP/WS requestdebug: true/logLevel: "debug": routing and origin option details
routesβ array of incoming host to origin protocol, host, and port mappingsoriginβ array of origin server host, port, and protocol (supports round-robin load balancing)originHostβ origin host or IP addressoriginPortβ origin port numberoriginProtocolβ origin protocol. one ofhttp:,https:,ws:, orwss:. don't forget the:at the end
incomingHostAndPortβ incominghost:portpattern to match for proxying to origin server. picked from HTTPhostheader field. Examples:example.com:80,api.example.com,*.example.com(wildcards supported)forceUpgradeHttpToHttpsβ set totrueto force upgradehttp:requests tohttps:requests for this routeforceUpgradeWsToWssβ set totrueto force upgradews:requests towss:requests for this routeforceRedirectβ set totrueto force redirect all requests to this route to theredirectHost. useful for permanent host migrationsusePermanentRedirectβ set totrueto use permanent redirect for this route. The proxy server will return a308redirect response to the client instead of the default307temporary redirect responseredirectHostβ host to redirect to for this route, e.g. when upgrading to HTTPS or WSS, or whenforceRedirectis enabled. if not set, the proxy server will redirect to the incomingx-forwarded-hostorhostheader fieldredirectURLPartsβ controls which URL parts are preserved during redirects for this route. Can be:trueβ preserve all URL parts (protocol, pathname, search, username, password)falseβ preserve only the host- An object with specific parts:
{ "protocol": true, "pathname": true, "search": false, "username": false, "password": false }
overrideHostβ if set, the proxy will override thehostandx-forwarded-hostheader fields in requests sent to the origin server. useful for testing or when a specific host is required (e.g., for OAuth callbacks)
{
"exposeHttpProxy": true,
"httpPort": "80",
"exposeHttpsProxy": true,
"httpsPort": "443",
"httpsPublicKeyFilepath": "/path/to/cert.pem",
"httpsPrivateKeyFilepath": "/path/to/key.pem",
"routes": [
{
"origin": [
{
"originHost": "localhost",
"originPort": 3000,
"originProtocol": "http:"
}
],
"incomingHostAndPort": "example.com",
"forceUpgradeHttpToHttps": true
}
]
}{
"exposeHttpsProxy": true,
"httpsPort": "443",
"httpsPublicKey": "-----BEGIN CERTIFICATE-----\n...",
"httpsPrivateKey": "-----BEGIN PRIVATE KEY-----\n...",
"routes": [
{
"origin": [
{
"originHost": "backend1.internal",
"originPort": 8080,
"originProtocol": "http:"
},
{
"originHost": "backend2.internal",
"originPort": 8080,
"originProtocol": "http:"
}
],
"incomingHostAndPort": "api.example.com"
}
]
}{
"exposeHttpProxy": true,
"httpPort": "80",
"routes": [
{
"origin": [],
"incomingHostAndPort": "old-domain.com",
"forceRedirect": true,
"redirectHost": "new-domain.com",
"usePermanentRedirect": true,
"redirectURLParts": {
"pathname": true,
"search": true
}
}
]
}{
"exposeHttpsProxy": true,
"httpsPort": "443",
"exposeWsProxyForHttps": true,
"httpsPublicKeyFilepath": "/path/to/cert.pem",
"httpsPrivateKeyFilepath": "/path/to/key.pem",
"routes": [
{
"origin": [
{
"originHost": "websocket-server.internal",
"originPort": 8080,
"originProtocol": "ws:"
}
],
"incomingHostAndPort": "ws.example.com",
"forceUpgradeWsToWss": true
}
]
}- if installed globally, run
fimiproxy ./path/to/config.json - if installed locally, run
npm exec fimiproxy ./path/to/config.json - for one-time run, run
npx -y fimiproxy ./path/to/config.json
Alternatively, you can start fimiproxy without passing a config filepath argument by setting the FIMIPROXY_CONFIG_FILEPATH environment variable:
# Set the environment variable
export FIMIPROXY_CONFIG_FILEPATH=./path/to/config.json
# Then run fimiproxy without arguments
fimiproxyOr in a single command:
FIMIPROXY_CONFIG_FILEPATH=./path/to/config.json fimiproxyNote: The command line argument takes precedence over the environment variable. If both are provided, the command line argument will be used.
import fimiproxy from "fimiproxy"
// start fimiproxy
await fimiproxy.startFimiproxyUsingConfig({
/** config */ {
exposeHttpProxy: true,
exposeHttpsProxy: true,
httpPort: "80",
httpsPort: "443",
workers: 1,
debug: false,
routes: [{
origin: [{
originHost: "localhost",
originPort: 3000,
originProtocol: "https:",
}],
incomingHostAndPort: "www.example.com",
forceUpgradeHttpToHttps: true,
overrideHost: "localhost:3000"
}],
httpsPublicKey: "-----BEGIN CERTIFICATE-----\n...",
httpsPrivateKey: "-----BEGIN PRIVATE KEY-----\n...",
},
/** shouldHandleGracefulShutdown */ true,
/** exitProcessOnShutdown */ true,
});
// end fimiproxy
await fimiproxy.endFimiproxy(/** exitProcessOnShutdown */ true);startFimiproxyUsingConfigβ start fimiproxy using configconfig: FimiproxyRuntimeConfigβ see configuration aboveshouldHandleGracefulShutdownβ defaults totrue. iftrue, will listen forSIGINTandSIGTERM, and attempt to gracefully shut down the proxy server. Whenfalse, forces single-process mode (useful for tests)exitProcessOnShutdownβ defaults totrue. ifshouldHandleGracefulShutdownistrue, will callprocess.exit()after graceful shutdown. your process may not shut down afterSIGINTandSIGTERMif nottrue. currently untested behaviour (if process will shutdown or not) when set tofalseandshouldHandleGracefulShutdownistrue
startFimiproxyUsingConfigFileβ start fimiproxy using config read from filepathfilepath: stringβ file at filepath should be a json file, see configuration section above
startFimiproxyUsingProcessArgsβ start fimiproxy using filepath picked fromprocess.argv[2]see https://nodejs.org/docs/latest/api/process.html#processargv. example,node your-script.js ./path/to/config.jsonstartFimiproxyUsingEnvVarβ start fimiproxy using filepath from environment variable (defaults toFIMIPROXY_CONFIG_FILEPATH)endFimiproxyβ gracefully end fimiproxy (returns a Promise)exitProcessβ defaults totrue. callsprocess.exit()iftrue
setupGracefulShutdownβ register SIGINT/SIGTERM handlers without shutting down immediately (used internally whenshouldHandleGracefulShutdownis true)
Use fimiproxy to proxy local development servers with SSL termination:
fimiproxy dev-config.jsonRoute different subdomains to different microservices:
api.example.comβ backend API servicews.example.comβ WebSocket servicecdn.example.comβ static file server
Gradually migrate from old domain to new domain while preserving SEO:
- Use
forceRedirectwithusePermanentRedirect: true - Preserve URL paths and query parameters with
redirectURLParts
Distribute traffic across multiple backend servers using round-robin selection. Enable originHealthEnabled to skip origins that fail periodic TCP checks (if all are unhealthy, fimiproxy falls back to the full origin list so traffic is not hard-stopped).
- Prefer
workersequal to CPU count on production hosts that terminate TLS (HTTPS benefits most from multi-core). - Keep deploy/process-manager instance count at 1; multi-core is internal cluster, not multiple masters binding the same privileged ports.
- Leave
accessLogoff unless you need it; errors still log aterrorlevel. - Tune
maxSocketsfor busy localhost fan-out; setmaxConnectionsif you need overload protection. - Run a quick load check with
npm run bench(see benchmarks).
npm run bench
npm run bench -- --duration 20 --connections 100The in-process harness uses workers: 1. To compare multi-worker RPS, start fimiproxy via the CLI with "workers": N in config and point autocannon (or similar) at that process.
Set debug: true in your configuration or use the FIMIPROXY_DEBUG=true environment variable to see detailed logs. Alternatively set "logLevel": "debug".
-
EADDRINUSE Error: Port already in use
- Check if another process is using the port:
lsof -i :PORT - Use different ports in your configuration
- Do not raise external process
countabove 1 for the same :80/:443 β useworkersinstead
- Check if another process is using the port:
-
SSL Certificate Issues:
- Ensure certificate files exist and are readable
- Verify certificate format (PEM)
- Check certificate expiration
-
WebSocket Connection Issues:
- Ensure
exposeWsProxyForHttporexposeWsProxyForHttpsis enabled - Verify origin server supports WebSocket protocol
- Check for protocol mismatch (ws vs wss)
- Origin open failures time out after
originTimeoutMs
- Ensure
-
Host Header Issues:
- Use
overrideHostif the origin server expects specific host headers - Check that
incomingHostAndPortmatches the actual request host
- Use
- Call
endFimiproxy()before anotherstartFimiproxy*in the same process. State is held on a per-processFimiproxyInstance; overlapping starts without teardown are unsupported. - Round-robin is simple rotation (not weighted or sticky). Optional TCP health checks can skip unhealthy origins when
originHealthEnabledis true. - Wildcard hosts (
*.example.com) are documented historically; matching is exactincomingHostAndPortlookup today.