Simply put, NetProxy is a genuinely full-featured reverse-proxy for Windows and Linux. On the short list, provides TCP, UDP, HTTP, HTTPS, TLS termination, load balancing, failover, sticky sessions, HTTP header manipulation, and more.
📖 Read the NetProxy guide for what NetProxy can do, common use cases and step-by-step configuration recipes.
👀 If you like NetProxy, you may also be interested in NetTunnel, it has a some similar features but is designed to tunnel traffic through a single TCP connection allowing ingress without opening ports on the firewall.
There are more screenshots at the bottom of this page.
A proxy listens on a port and passes every connection it accepts to one of its endpoints: the machines it is proxying to. Each endpoint has its own address and port, and its own traffic type. A proxy has:
| Setting | |
|---|---|
| Traffic type | What the listener accepts: TCP (any TCP protocol), HTTP, HTTPS (the proxy terminates TLS with the certificate you give it), or UDP. |
| Bindings | Listen on all addresses (IPv4, IPv6 or both), or only on the addresses you list. |
| Endpoints | Where connections go. Each endpoint is TCP, HTTP or HTTPS (UDP proxies relay to UDP endpoints), and can be switched off without deleting it. |
| Connection pattern | Fail-over (the first endpoint that works, in the order listed), Round-robin, or Balanced (the endpoint with the fewest connections). |
| Sticky sessions | Send a client back to the endpoint it used before, until the session cache expiration (since it was last used) passes. |
| HTTP header rules | Insert, update, upsert or delete request and response headers in flight (HTTP, and HTTPS where the proxy terminates TLS). |
- Fail-over always tries the endpoints in the order listed, so the first one is used for as long as it works.
- Round-robin takes the endpoints in turn.
- Balanced starts with the endpoint that has the fewest connections.
- An endpoint that could not be connected to is tried last for ten seconds, so connections are not each made to wait for it to time out (it is still tried if nothing else works). Connecting to an endpoint is given ten seconds.
- With sticky sessions a client (by its IP address, so it is the same whichever port it connects from) goes to the endpoint it was last sent to, as long as that endpoint is enabled and is not failing. Otherwise it is sent to the next one that works.
- The listener terminates TLS when the traffic type is HTTPS. Give it a PFX certificate (the path is on the machine that the service runs on) and its password. HTTP header rules then work on HTTPS traffic.
- Each endpoint chooses its own traffic type. An HTTPS endpoint makes the proxy start TLS with it, checking its certificate against the endpoint address (use the host name, not an IP address; tick Ignore cert errors for a self signed certificate). A new endpoint starts with the traffic type of the listener, which is HTTPS to HTTPS (re-encrypt) when the listener is HTTPS, and plain to plain otherwise. Any combination is allowed, for example a plain HTTP listener in front of an HTTPS endpoint.
- To pass TLS through untouched (the proxy cannot read it, and header rules do not apply), use the TCP traffic type.
- Proxies saved by an earlier version as HTTPS without a certificate are passed through as TCP, and the service logs a warning.
- Browsing the service machine. Wherever the console asks for a certificate (the proxy and its host names), a plugin assembly or the log file, Browse lists the folders of the machine that the service runs on, so you can pick the file even when the console runs on another machine. It lists names, sizes and dates only; the content of a file is never sent. Only a logged in management user can browse, and the same listing is available to scripts as
NpServiceClient.Browse. - Renewed certificates are picked up by themselves. The PFX files (of the proxy and of its host names) are watched. When one is replaced, for example by a renewal with win-acme or certbot, new connections use the new certificate a few seconds later; connections that are open keep the one they started with, and nothing is restarted. A file that cannot be read (still being written, or broken) is retried for a minute and then left alone, and the previous certificate stays in use; the service logs it. Files on network shares are also checked every 30 seconds.
- Certificate passwords and the password hashes of the management users are stored encrypted in the configuration files (DPAPI on Windows, a key file next to the configuration on Linux).
win-acme (wacs.exe) can make the PFX file that NetProxy needs, without IIS. One command makes a wildcard certificate and writes it to a folder:
wacs.exe --accepttos --emailaddress info@YOURDOMAIN.com --target manual --host *.YOURDOMAIN.com --validation manual --store centralssl --centralsslstore C:\YOUR_NEW_CERT_PATH --pfxpassword "YOUR_NEW_PASSWORD" --installation none
--target manual --host *.YOURDOMAIN.comasks for a wildcard certificate. Use several names (--host *.YOURDOMAIN.com,YOURDOMAIN.com) if you also want the root domain on the same certificate: a wildcard does not cover the root domain, so without itYOURDOMAIN.comitself is not covered (see Sharing a port).--validation manualis how Let's Encrypt checks that you own the domain. A wildcard can only be validated with a DNS record, so win-acme prints a_acme-challenge.YOURDOMAIN.comTXT record for you to create at your DNS provider. Create it, wait for it to be visible, then press Enter. Because you create the record by hand, this certificate does not renew by itself: run the command (or Run renewal in the win-acme menu) again before it expires, about every 60 days, and create the new record when asked. To renew unattended, use a win-acme DNS validation plugin for your DNS provider instead.--store centralssl --centralsslstore C:\YOUR_NEW_CERT_PATH --pfxpassword "..."writes the certificate as a PFX file in that folder, protected by that password, and--installation noneskips installing it anywhere (no IIS needed). The file is named after the host, with the*of a wildcard replaced by_(for example_.YOURDOMAIN.com.pfx).- In NetProxy, set the PFX file of the proxy (or of a host name) to that file, and the password to the one you gave. When win-acme writes a renewed file there, NetProxy picks it up by itself (see above).
- The folder and the password are yours to choose. Keep the folder where only the service and administrators can read it.
To see or stop renewals that you no longer need, run wacs.exe, choose A: Manage renewals, type the number of the one renewal (for example 13) so that only it is selected, and choose C: Cancel renewal. Do not use Cancel while all renewals are selected. This only stops future renewals: it does not delete the PFX file or revoke the certificate.
Choose the UDP traffic type to relay datagrams. UDP has no connections, so the proxy keeps a session for each client (address and port): the first datagram creates it, it gets a socket of its own to its endpoint (so what the endpoint answers can be told from the answers to other clients), and it ends when the client has sent nothing for the idle timeout.
- Datagrams are relayed whole (up to 65,535 bytes) and in order. The endpoint answers the session's socket, and the answer goes back to the client from the port it sent to.
- Idle timeout: the setting of the proxy. For UDP
0means 60 seconds, because a UDP session has no end of its own. Use a few seconds for DNS and minutes for a VPN or a game. - Limits: max connections and max connections per client count sessions. Max datagrams/s per client drops what a client sends above that rate. It is off (0) by default because a VPN tunnel is one client sending tens of thousands of datagrams a second; set it for services where a client sends little (DNS, syslog). A client's queue is limited, so a flood from one address cannot use up memory, and a client that a plugin or the endpoints turned away is ignored for a few seconds instead of costing a lookup per datagram.
- Endpoints: round-robin, balanced and sticky sessions work per session (sticky is by client address). Fail-over is best effort: UDP cannot tell that an endpoint is down until it answers a datagram with "port unreachable", which ends the session, so that datagram is lost and the next client session goes to another endpoint. An endpoint given as a name that has both IPv4 and IPv6 addresses is sent to over IPv4.
- Ports: a UDP port belongs to one proxy (a datagram has no host name, so UDP proxies cannot share a port the way HTTP and HTTPS proxies do). A UDP proxy and a TCP proxy can use the same number.
- Not for UDP: host names, TLS, HTTP header rules, forwarded headers and HTTP/2. The endpoint sees the proxy's address as the client. QUIC and HTTP/3 pass through as opaque datagrams, but cannot be routed by name.
- Abuse: a spoofed source address can make a UDP service answer a victim. Keep the sessions per client and per proxy limited, and the datagram rate where the service allows it.
An endpoint sees the connection come from the proxy. Set X-Forwarded-For / -Proto on the HTTP Headers tab of an HTTP or HTTPS proxy to pass the client on:
- Set (for a proxy that clients connect to directly):
X-Forwarded-Foris the address of the client andX-Forwarded-Protoishttporhttps(what the client used to reach the proxy). What the client sent for these is dropped, because anybody can send them. - Append (for a proxy behind another proxy that sets them): the client is added to the end of
X-Forwarded-For, andX-Forwarded-Protois kept if it is there. - Done for every request on a keep-alive connection, for HTTP/1.x and HTTP/2. Your own header rules are applied afterwards, so a rule can still change them. In a proxy file it is
"ForwardedHeaders": "Off" | "Replace" | "Append".
Several HTTP or HTTPS proxies can listen on the same port, if each answers to different host names (the Host names tab of the proxy).
- A host name is exact (
www.example.com) or a wildcard for everything below a domain (*.example.com). A wildcard does not cover the root domain:*.example.commatcheswww.example.comanda.b.example.com, but notexample.comitself, so addexample.comas its own host name (and, for HTTPS, give it a certificate that covers it; a wildcard certificate for*.example.comdoes not cover the root domain either unless it listsexample.comas well). The most specific match wins: an exact name before a wildcard, a longer wildcard before a shorter one. - Example: a general proxy and an exception. Proxy A has the host names
*.example.comandexample.comand forwards to SERVER1. Proxy B has the host nametest.example.comand forwards to SERVER2. Both listen on the same port and are both HTTP or both HTTPS.test.example.comgoes to B, because an exact name beats a wildcard; every other name underexample.com, andexample.comitself, goes to A.*.example.comalso matches deeper names such asa.test.example.com; to send those to B as well, add*.test.example.comto B, which wins because it is the longer wildcard. For HTTPS, B needs a certificate that coverstest.example.com: a*.example.comwildcard certificate does, so B's host name can use the same PFX file and password as A. - A request goes to the proxy that has its host name: the
Hostheader for HTTP, or the server name (SNI) of the TLS handshake for HTTPS. - A proxy with no host names is the default for the port and gets whatever no other proxy claims. Only one proxy per port can be the default. With no default, a plain HTTP request for an unknown host gets a
404, and an unknown HTTPS server name fails the handshake. - HTTPS: each host name can have its own PFX certificate (and password), for example one for
www.example.comand another forexample.com. A host name without one uses the certificate of the proxy. Proxies that share a port must all be HTTP or all be HTTPS. - TCP (and UDP) proxies cannot share a port, because there is nothing to tell them apart by. The service refuses to save a proxy that conflicts with another one on the same port (and addresses).
- The proxy is chosen once per connection: the first request decides, so later requests on a keep-alive connection go to the same proxy. For the same reason HTTP/2 connections go to the proxy of the first host name (HTTPS) or to the default proxy (HTTP/2 without TLS, where the host is compressed), so clients that reuse one HTTP/2 connection for several host names can reach the wrong proxy.
- A proxy with host names answers only to them, even when it is the only one on its port (so when the proxy of another site is stopped, that site gets a 404 and its requests do not end up at a different site). A proxy without host names answers every host name.
netproxyctl is a cross-platform command line tool (Windows, Linux, macOS) that does what the console does, over the same encrypted management connection, so it works against a remote service too. NetProxy.ClientApi is the library underneath it, if you want to do the same from your own .NET code.
netproxyctl login --server myhost --user administrator # asks for the password
netproxyctl status
netproxyctl proxy list
netproxyctl proxy get web > web.json # edit it...
netproxyctl proxy apply -f web.json --dry-run # ...see what would change...
netproxyctl proxy apply -f web.json # ...and apply it
netproxyctl proxy restart web
netproxyctl user add bob --password-stdin
netproxyctl help # everything else
- Login:
loginkeeps the server, user and the hash of the password (not the password) in~/.netproxy/credentials.jsonfor the user that ran it; on Linux and macOS only that user can read the file.NETPROXY_SERVER,NETPROXY_PORT,NETPROXY_USERandNETPROXY_PASSWORD, or--server,--port,--userand--password, take precedence, which is how scripts log in. - Apply by name:
proxy applyandplugin applyadd the proxy or plugin, or replace the one with the same name (not case sensitive), the waykubectl applydoes. One that is the same as what the service has is left alone, so it is not restarted. A file can hold one proxy, a list, or an export. Inproxy getsecrets are shown as********, and applying that keeps the secret the proxy has. - Editing the files by hand: the files are in the configuration folder (see below). Edit them, run
netproxyctl validateto check them and see what would change, thennetproxyctl reload(orkill -HUPthe service on Linux and macOS). Reload applies only what changed: proxies you did not touch keep running and keep their connections, and nothing is changed if the files have a problem (including a file with no proxies, which is what a file that could not be read looks like). Plain text passwords you typed into the files are encrypted the next time the service saves. The management port only changes when the service restarts. Do not edit the files while a console is changing the same things: the service rewrites them on every change. - Scripts:
--jsonprints JSON, and the exit code is 0 when it was done, 1 when it was not, 2 for a wrong command line and 3 when it could not connect or log in.exportandimportmove the proxies, plugins and settings between services (users are not included). - Also:
reload,logs,certs(when the certificates expire),stats,connections(andconnections close),user ...,plugin ...,settings ....
NetProxy can be extended with .NET plugins. A plugin is an assembly with a class that derives from NpPlugin (the NetProxy.PluginApi project, which has no dependencies). NetProxy.SamplePlugin is a small working example: it logs connections, can block clients, and can find and replace text in the traffic.
- Setting up: plugins belong to the service. Add them in the console under Settings, Plugins (assembly path on the service machine, optional class name, settings as
name=valuelines, a time limit per event), and put them in the order they are called in. A proxy then opts in on its Plugins tab: a proxy uses no plugins unless it ticks them. - Writing one: derive from
NpPlugin, mark the class with[NpPlugin("Name")], and override the events you want. Events you do not override cost nothing. ReferenceNetProxy.PluginApiwithout copying it next to your assembly (Private=false), because the service supplies it. Plugins are loaded in their own load context, so a new build can be loaded with Reload without restarting the service. - Events:
- Service:
OnServiceStarting,OnServiceStopping,OnTick(whenTickIntervalis set),OnManagementLogin,OnError. - Proxy:
OnProxyStarting(can say no),OnProxyStarted,OnProxyStartFailed,OnProxyStopping,OnProxyStopped,OnProxyConfigurationChanged. - Connection:
OnConnectionRefused,OnClientTlsEstablished,OnConnecting(can say no),OnEndpointConnecting(can say no, and the next endpoint is tried),OnEndpointConnected,OnEndpointConnectFailed,OnConnected,OnIdleTimeout,OnDisconnecting,OnDisconnected. - Data:
OnDataReceived,OnDataSendingandOnDataSent.
- Service:
- Data events sit between the two sides of a connection, so for an HTTPS proxy that terminates TLS they see the decrypted data (
ReceivedEncryptedsays it was encrypted on the wire) before it is encrypted again for the endpoint (WillBeSentEncrypted).OnDataReceivedis called with what was read, before the HTTP header rules;OnDataSendingis called after them, right before the write. Both canReplacethe data (any length),Dropit, orCloseConnection. A read is whatever arrived together, not a message. A TCP proxy that passes TLS through only has encrypted bytes to give to a plugin. - UDP: on a UDP proxy a client's session is a connection (it is "connecting" when its first datagram arrives and "disconnected" when the session ends), and every datagram is a data event, so
OnDataReceivedandOnDataSendingget whole datagrams: a datagram is a message, unlike a read. Nothing is encrypted by the proxy, soReceivedEncryptedandWillBeSentEncryptedare false. - Order and safety: the plugins of a proxy are called in order, and each gets the data as the one before left it. An event that can say no stops at the first plugin that does. A plugin that throws, or takes longer than its time limit, is logged and skipped for that event; the connection carries on.
- Not in this version: events for individual HTTP requests and responses (use the data events), and for certificates that are about to expire.
Proxies listen on IPv4, IPv6, or both (one dual-stack listener) when they listen on all addresses, and a binding can be an IPv4 or IPv6 address (::1 or [::1]). An endpoint can be an IPv6 address or a host name that has one. The management port accepts IPv6 too (Settings > Configuration > Service). Check the firewall for both IP versions: IPv6 addresses are often directly reachable from the internet.
Tick Allow HTTP/2 on the General tab to offer HTTP/2 to clients; gRPC is HTTP/2, so this is also what gRPC needs. The HTTP header rules apply to HTTP/2 requests and responses too (names are lower case in HTTP/2, a rule for Host changes :authority, and the connection specific headers such as Connection cannot be added). Without header rules the data is passed through untouched.
- HTTPS listener: HTTP/2 is negotiated with the client (ALPN). The endpoint has to speak what the client speaks, so a client that uses HTTP/2 needs an endpoint that agrees to HTTP/2 over TLS (traffic type HTTPS) or one that accepts plain HTTP/2 (h2c, traffic type TCP or HTTP). Clients that only know HTTP/1.1 keep working.
- HTTP listener: a client that starts with the HTTP/2 preface (h2c with prior knowledge, which is what gRPC clients without TLS use) is detected, and the endpoint has to accept h2c. The
Upgrade: h2crequest is not supported. - HTTP/3 is not offered.
On the Advanced tab of a proxy (0 is no limit, which is how existing proxies behave):
- Max. connections is the most connections the proxy has open at once.
- Max. connections per client is the most that one client address has open at once. Clients are told apart by IP address, so many clients behind one NAT share a limit.
- Idle timeout (s) closes a connection that has carried no data in either direction for that long. Keep it above the keep-alive interval of long lived connections (a gRPC stream, a database or SSH session).
A connection over a limit is closed as soon as it is accepted. The service logs a warning when that starts, and then at most once a minute.
The service writes to a rolling log file (Logs/NetProxy.log in its installation folder by default, rolled over at a size you choose), to the Windows event log (warnings and exceptions), and to the console when it is run by hand. The level, the file and the roll-over can be changed in Settings > Configuration > Service and take effect immediately.
The configuration is stored in the common application data folder: C:\ProgramData\NetProxy on Windows, /usr/share/NetProxy on Linux (see the Linux install notes in @Installers). The NETPROXY_CONFIG_NAME environment variable names a different folder, so a test instance does not touch the real configuration.
- A new installation has one management user,
administrator, with an empty password. Set a password first. The service logs a warning while any user has none. - The management connection between the console and the service is encrypted: on connecting, the two agree on a key (NTDLS.SecureKeyExchange) and everything after that, including the login and every setting, is encrypted (NTDLS.Permafrost). The service refuses a login over a connection that is not encrypted, so the console and the service have to be the same version. The connection is encrypted but the service is not authenticated to the console, so on an untrusted network still limit who can reach the management port (5854), for example with a VPN or NetTunnel.