{"id":14563806,"url":"https://github.com/exposr/exposrd","last_synced_at":"2025-09-04T06:33:46.485Z","repository":{"id":41416344,"uuid":"368936612","full_name":"exposr/exposrd","owner":"exposr","description":"🚀 Horizontally scalable reverse tunnel relay server for exposing services behind NAT/Firewalls without port forwarding (Self-hosted cloudflared/ngrok alternative).","archived":false,"fork":false,"pushed_at":"2024-04-16T17:23:56.000Z","size":883,"stargazers_count":43,"open_issues_count":7,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-16T22:41:29.326Z","etag":null,"topics":["cloudflared","localhost","localtunnel","ngrok-alternative","proxy","reverse-proxy","reverse-tunnel","self-hosted","tunnel","tunnel-proxy","tunnel-server"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/exposr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-05-19T16:38:25.000Z","updated_at":"2024-04-10T10:20:47.000Z","dependencies_parsed_at":"2022-08-28T12:50:44.908Z","dependency_job_id":"b503f758-ec46-4ac0-8bb9-fae374fb682a","html_url":"https://github.com/exposr/exposrd","commit_stats":null,"previous_names":["exposr/exposrd"],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exposr%2Fexposrd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exposr%2Fexposrd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exposr%2Fexposrd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exposr%2Fexposrd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/exposr","download_url":"https://codeload.github.com/exposr/exposrd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":217902565,"owners_count":16248444,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflared","localhost","localtunnel","ngrok-alternative","proxy","reverse-proxy","reverse-tunnel","self-hosted","tunnel","tunnel-proxy","tunnel-server"],"created_at":"2024-09-07T02:04:42.991Z","updated_at":"2024-09-07T02:08:04.917Z","avatar_url":"https://github.com/exposr.png","language":"TypeScript","funding_links":[],"categories":["self-hosted"],"sub_categories":[],"readme":"# exposr - dynamic reverse tunnel relay server\n\nexposr is a self-hosted reverse tunnel relay server that allows you to securely expose devices and services\nbehind NATs or firewalls to the Internet through public URLs.\n\nexposr can for example be used for development and previews or for exposing services behind NAT/firewalls\nto the Internet without port-forwarding and risk of exposing your IP address.\n\nWhy another \"reverse tunnel server\"? exposr takes a slightly different approach than other servers\nof the same type. exposr is designed to run as a container with horizontal elastic scaling properties,\nand is well suited to run in container-orchestration systems like Kubernetes.\n\n# Features\n\n* Clustering support with horizontally scalability - more nodes can be added to increase capacity.\n* Multi-client connection - each client can maintain multiple persistent connection per tunnel.\n* No configuration files! - All configuration can be done as environment variables or command line options.\n* Designed to run behind a load balancer (HTTP/TCP) (ex. nginx or HAProxy).\n* Suitable to run in container-orchestration systems such as Kubernetes.\n* Multiple transports - multiplexed websocket with custom client or SSH client forwarding.\n* Multiple ingress methods - HTTP (with custom domain support) or SNI for TLS/TCP protocols.\n* Custom client can forward to any host, does not require root privileges and only requires outbound HTTP(s) connections.\n* Tunnel configuration through restful APIs.\n* No passwords or e-mails - but still secure. An account number together with the tunnel identifier serves as credentials.\n\nWhat it does *not* do\n* Certificate provisioning.\n* DNS provisioning.\n\nThis is on purpose as the server is designed to be stateless and to have elastic scaling\nproperties. Meaning these are more suitable to handle in other parts of the deployment stack, for\nexample at the load balancer.\n\n## Demo\n\n![](https://exposr.github.io/docs/img/demo/exposr-demo-20220301.svg)\n\n## Supported transports\nexposr supports two different transport methods. This is the way a tunnel client\nconnects and establishes the tunnel.\n\n| Type       | Method                     | Endpoint   | Client support        |\n| ---------- | -------------------------- |----------- | --------------------- |\n| Websocket  | Custom multiplex websocket | HTTP       | [`exposr`](https://github.com/exposr/exposr) |\n| SSH        | SSH TCP forwarding         | TCP        | Any SSH client        |\n\nThe Websocket transport endpoint can run behind a HTTP load balancer on the same port\nas the API. The SSH transport endpoint requires a dedicated TCP port and requires\na TCP load balancer in multi-node setups.\n\n## Supported ingress methods\nThe following ingress methods are supported. Ingress is the way clients (or users)\nconnect to the tunnel to connect to the exposed services.\n\n| Type  | Method                   | Protocol support | Requirements                | Load balancer req. |\n| ----- | ------------------------ | ---------------- | --------------------------- | ------------------ |\n| HTTP  | Virtual host (subdomain) | HTTP             | Wildcard domain             | HTTP               |\n| SNI   | SNI                      | TLS              | Wildcard certificate+domain | TCP                |\n\n## Supported storage options\n\nThe following storage options are supported. The default is no persistence,\nSQLite is recommended for single-node setups. Tunnel configuration and\naccounts are written to persistent storage.\n\n| Type       | Single/multi-node        | Note                 |\n| ---------- | ------------------------ | -------------------- |\n| Memory     | Single-node              | Data lost on restart |\n| SQLite     | Single node              |                      |\n| PostgreSQL | Multi-node               |                      |\n| Redis      | Multi-node               |                      |\n\n## Clustering support\nexposr can be run in a multi-node setup, ingress connections are re-routed to the node\nthat have the tunnel established. This allows for horizontal load balancing in round-robin\nfashion without the need for sticky sessions.\n\n| Type        | Discovery methods                  | Note                         |\n| ----------- | ---------------------------------- | ---------------------------- |\n| Single-node | Single-node                        | No clustering                |\n| UDP         | IP Multicast or Native Kubernetes  | K8S through headless service |\n| Redis       | Redis                              |                              |\n\nTo run exposr in clustering mode you need to select a cluster mode, the default\nis UDP with node discovery through IP multicast or through Kubernetes headless\nservice, exposr will try to auto-detect the best discovery method to use.\nTo use the UDP mode IP connectivity on UDP port 1025 (default) between nodes is required.\n\nIt is also possible to use a Redis cluster with pub/sub capabilities.\n\nNB: Multi-node storage is required for clustering setup.\n\n# Architecture\nexposr have three core concepts; transports, endpoints and ingress.\n\nA tunnel is composed of a transport and a connection endpoint.\nThe endpoint is used by the client to establish a tunnel connection.\nThe transport of the tunnel is the underlying data stream of the tunnel, it supports\nmultiple independent streams over one connection.\n\nThe ingress is for traffic destined for the tunnel target, an ingress supports one\nspecific protocol and have a distinct way of identifying which tunnel the request\nis bound for.\n\n```\n                          +-----------------------+\n      +----------------+  |  +-----------------+  |\n-----\u003e|    Ingress     +--|-\u003e|    Transport    +--|-----------+\n      +----------------+  |  +-----------------+  |           v\n                          |                       |   +----------------+     +--------------+\n                          |        Tunnel         |   |    Client      +----\u003e|    Target    |\n                          |                       |   +-------+--------+     +--------------+\n                          |  +-----------------+  |           |\n                          |  |    Endpoint     |\u003c-|-----------+\n                          |  +-----------------+  |           |\n                          +-----------------------+           |\n                             +-----------------+              |\n                             |      API        |\u003c-------------+\n                             +-----------------+\n```\n\n# Tunnels and accounts\nA tunnel is identified by a string consisting of alphanumeric `(a-z, 0-9)` characters and dashes `(-)`.\nMinimum 4 characters and maximum 63 characters. The tunnel identifier have to start with a alpha character.\nThis is chosen so that the tunnel identifier can be used as a DNS label.\n\nExample\n\n    my-tunnel-identifier-14\n\nAn account number is a 16 character string selected from the case-insensitive alphabet `CDEFHJKMNPRTVWXY2345689`.\nThe number is formatted into 4 groups of 4 characters separated by a separator.\nDashes and spaces are accepted as separator, as well as no separator.\n\nExample\n\n    MNF4-P6Y6-M2MR-RVCT\n    MNF4 P6Y6 M2MR RVCT\n    MNF4P6Y6M2MRRVCT\n\nA tunnel is owned by one account, one account can have multiple tunnels.\nThere is no password or key associated with an account.\n\nIt's _not_ possible for a user to list all tunnels belonging to an account.\nThis makes it possible to use the account number together with the tunnel identifier as credentials as both\nneeds to be known in order to perform privileged operations on a tunnel.\n# Running exposr\n\n## Runtime artifacts\n\n### Containers\nContainers are available for deployments in container runtime environments.\n\nLatest release is available with the `latest` tag, latest development (master branch) is available with the `unstable` tag.\n\n## Quick start\nYou can quickly try out exposr without installing anything.\n\nRun the server, the server will listen on port 8080 and the API will be exposed at `http://host.docker.internal:8080`.\nHTTP ingress sub-domains will be allocated from `http://localhost:8080`.\n\n    docker run --rm -ti -p 8080:8080 ghcr.io/exposr/exposrd:latest --allow-registration --ingress-http-url http://localhost:8080\n\nStart the client with, this will create a tunnel called `example` and connect it to `http://example.com`.\nThe tunnel will be available at `http://example.localhost:8080`.\n\n    docker run --rm -ti ghcr.io/exposr/exposr:latest -s http://host.docker.internal:8080/ tunnel connect example http://example.com\n\nTry the tunnel\n\n    curl --resolve example.localhost:8080:127.0.0.1 http://example.localhost:8080\n\n## Configuration\n\nexposr needs to have at least one ingress and one transport method enabled. The default option enables\nthe HTTP ingress and the WS transport.\n\n### Account creation\nAccount creation is disabled by default and needs to be enabled. It can be enabled in two ways, either through\nthe public API or by enabling the administration API. It's recommended to only use the admin API\nfor account creation.\n\nTo enable it through the public API start exposr with the flag `--allow-registration`.\n\n\u003e ⚠️ Warning: Enabling public account registration will allow anyone to register an account and to create tunnels on your server.\n\n### Administration\n\n#### Interface\nThe administration interface runs on a separate port from public API. By default it uses `8081`.\nThe interface can be enabled by passing the flag `--admin-enable true`.\n\nThe administration interface exposes a `/ping` endpoint for load balancer health checks.\n\n#### API\nThe administration API runs on a separate port from public API. By default it uses `8081`.\nThe API can be enabled by passing the flag `--admin-api-enable true`.\n\nTo further enable the administration API an API key must be configured.\n\n    exposrd --admin-api-enable true --admin-api-key \u003cinsert key\u003e\n\n\u003e ⚠️ Warning: The API key allows full privileged access to all accounts and tunnels.\n\n### Configuring HTTP ingress\nThe HTTP ingress can be enabled by passing the flag `--ingress http`.\nIt uses the same port as the API port, and fully supports HTTP(s) including upgrade requests (ex. websockets).\n\nThe HTTP ingress uses subdomains and virtual hosts to determine the tunnel id and requires a\nwildcard DNS entry to be configured and pointed to your server or load balancer.\n\n    *.example.com  IN A  10.0.0.1\n\nThe domain needs to be configured with `--ingress-http-url`.\n\n    exposrd --ingress http --ingress-http-url http://example.com\n\nEach tunnel will be allocated a subdomain, ex. `http://my-tunnel.example.com`.\n\nIf you have a proxy or load balancer in-front of exposr that terminates HTTPS, pass the domain with\nthe `https` protocol instead. (`--ingress-http-url https://example.com`).\n\n#### BYOD (Bring Your Own Domain)\n\nThe HTTP ingress supports custom domain names to be assigned to a tunnel outside of the automatic one\nallocated from the wildcard domain. Assigning a custom domain name to a tunnel will make exposr\nrecognize requests for the tunnel using this name.\n\nTo configure BYOD (altname) a CNAME for the domain must be created and pointing towards the FQDN\nof the tunnel. For example, to use the name `example.net` for the tunnel `my-tunnel.example.com`\na CNAME should be configured for `example.net` pointing to `my-tunnel.example.com`.\n\n    example.net  IN CNAME  my-tunnel.example.com\n\nFinally the altname needs to be enabled in exposr, this can be done through the cli.\n\n    exposr tunnel configure my-tunnel set ingress-http-altnames example.net\n\nThe request will be rejected unless the CNAME is properly configured.\n\nNote that if you have a load balancer or proxy in front of exposr that terminates HTTPS\nyou need have a certificate that covers the altname.\n\n### Configuring SNI ingress\n\nTo enable the SNI (Server Name Indication) ingress pass the flag `--ingress sni`.\nThe SNI ingress requires a dedicated TCP port, by default it uses 4430. The port can be changed with `--ingress-sni-port`.\n\nThe SNI ingress works by utilizing the SNI extension of TLS to get the tunnel from the hostname. Similar to\nthe HTTP ingress it requires a wildcard DNS entry (`*.example.com`), but also a wildcard certificate covering\nthe same domain name. It's compatible with any protocol that can run over TLS and a client that supports SNI.\n\nexposr will monitor the provided certificate and key for changes and re-load the certificate on-the fly.\n\n#### Certificate\n\nThe certificate must contain one wildcard entry, either as the common name (`CN`) or in the SAN list.\nIf there are multiple wildcard entries present, the first one will be used.\n\nFor production use, a real certificate should be used. Let's encrypt offers free wildcard certificates.\nFor testing a self-signed can be generated with openssl.\n\n    openssl req -x509 -newkey rsa:4096 -keyout private-key.pem -out certificate.pem -days 365 -nodes\n\n#### Example\n\n    exposrd --ingress sni --ingress-sni-cert certificate.pem --ingress-sni-key private-key.pem\n\n### Configuring SSH transport\n\nTo enable the SSH transport pass the flag `--transport ssh` to exposr.\nBy default it will use port 2200, it can be changed with `--transport-ssh-port`.\nThe base host name will by default use the API host, it can be overridden with `--transport-ssh-host`.\n\nA new SSH host key will be generated at startup. If you run in a clustered setup it's recommended to provide\na static key so that clients always receive the same host key. The key can be specified either as a path or string\ncontaining a SSH private key in PEM encoded OpenSSH format using `--transport-ssh-key`.\n\n#### Example\n\nStart the server with SSH transport enabled\n\n    \u003e docker run --rm -ti -p 8080:8080 -p 2200:2200 ghcr.io/exposr/exposrd:latest --allow-registration --ingress-http-url http://localhost:8080 --transport ssh\n\nCreate and account and configure a tunnel\n\n    \u003e docker run --rm -ti ghcr.io/exposr/exposr:latest -s http://host.docker.internal:8080/ account create\n     ✔ 2022-02-24 19:00:00 +0100 - Creating account...success\n    ✨ Created account DE94-JTNJ-FX5W-YWKY\n\n    \u003e docker run --rm -ti ghcr.io/exposr/exposr:latest -s http://host.docker.internal:8080/ -a DE94-JTNJ-FX5W-YWKY tunnel create my-tunnel transport-ssh on ingress-http on\n     ✔ 2022-02-24 19:00:10 +0100 - Creating tunnel...success (my-tunnel)\n     ✔ 2022-02-24 19:00:20 +0100 - Setting transport-ssh to 'true'...done\n     ✔ 2022-02-24 19:00:20 +0100 - Setting ingress-http to 'true'...done\n    ✨ Created tunnel my-tunnel\n\nFetch the SSH endpoint URL\n\n    \u003e docker run --rm -ti ghcr.io/exposr/exposr:latest -s http://host.docker.internal:8080/ -a MNF4-P6Y6-M2MR-RVC\" tunnel info my-tunnel\n    [...]\n      Transports\n        SSH: ssh://my-tunnel:kXBnFV6Z1YoZPhoVLmxn9UO-Cp2qh7R19CGRrA_ylYfiiZ32N-CR9LWyHtaHxXn8UXGPNSt5xXUxf-5DlZOvLg@localhost:2200\n\nEstablish the tunnel with SSH as normal\n\n    \u003e ssh -o \"StrictHostKeyChecking no\" -o \"UserKnownHostsFile /dev/null\" -R example.com:80:example.com:80 ssh://my-tunnel:nfeflVuKGick0rD2C7Mqne6d-MDWPGCX6At7ygj0U8FTkgbLFi-XckuEUQ9-ipkJ0aRPkrxziKit4wWDisONXg@localhost:2200\n    Warning: Permanently added '[localhost]:2200' (RSA) to the list of known hosts.\n    exposr/v0.5.1\n    Target URL: http://example.com/\n    HTTP ingress: http://my-tunnel.localhost:8080/\n\nThe target can be configured with the `bind_address` part of the `-R` argument to ssh. If a target\nhas already been configured the left-hand part of -R can be left out, example `-R 0:example.com:80`.\n\nNote that the connection token is only valid for one connection, and must be re-fetched for each connection.\n\n#### Permanent SSH key\nGenerate an SSH key with (only the private key is required)\n\n    ssh-keygen -b 2048 -t rsa -f sshkey -q -N \"\"\n\nThe content of the file can be passed through environment variables\n\n    EXPOSR_TRANSPORT_SSH_KEY=$(\u003csshkey) exposrd [...]\n\nYou can also specify it as a path\n\n    exposrd [...] --transport-ssh-key /path/to/sshkey\n\n### Storage setup\nexposr supports persistance through SQLite, PostgreSQL or Redis.\nTo enable storage, pass the `--storage-url` option together with a connection string.\n\nTo configure PostgreSQL use `postgres://\u003cconnection-string\u003e`.\n\n    exposrd --storage-url postgres://db_user:db_password@postgres-host/mydatabase\n\nTo configure Redis use `redis://\u003cconnection-string\u003e`.\n\n    exposrd --storage-url redis://:redis_password@redis-host\n\nTo configure SQLite use `sqlite://\u003cpath\u003e`.\n\n    exposrd --storage-url sqlite://exposr.sqlite\n\n### Clustering setup\nTo run exposr in a clustering setup, the following is required;\n\n* PostgreSQL or Redis for storage.\n* IP connectivity between all nodes.\n* Load balancer in-front of the nodes (ex. K8S, AWS ALB, GCP LB, nginx/haproxy/etc)\n\nTo run exposr in clustering mode the nodes needs IP connectivity between them, and\na pub/sub bus for messages. exposr supports pub/sub through a UDP or through Redis.\n\nThe clustering mode is configured using the `--cluster` option.\n\n    exposrd --cluster auto|udp|redis\n\nTo preserve the integrity of the message bus each message is signed using a per cluster signing key.\nThe message signature is validated by each node, and messages with invalid signatures are rejected.\n\nYou should configure a secret signing key using the `--cluster-key` option.\n\n#### UDP\nThe UDP pub/sub bus is using a custom UDP protocol running on port 1025 (can be changed with `--cluster-udp-port`).\nThe UDP clustering mode supports two modes of node discovery. IP multicast and Kubernetes headless service discovery.\n\nWhen using IP multicast, messages are sent to a IP multicast group, by default 239.0.0.1 (can be changed with `--cluster-udp-discovery-multicast-group`).\nWhen using Kubernetes discovery, each Pod IP is discovered through DNS and messages are sent using unicast directly to each node.\n\nexposr tries to auto-detect the environment and select the most appropriate discovery mode.\nYou can explicitly set the discovery mode using `--cluster-udp-discovery`.\n\n    exposrd --cluster udp --cluster-udp-discovery multicast|kubernetes\n\n#### Redis\nIf using Redis as a storage option it may be convenient to use Redis as the pub/sub bus as well.\nTo do so you must pass a Redis connection string to the Redis cluster to use for pub/sub using the `--cluster-redis-url` option.\n\n    exposrd --cluster redis --cluster-redis-url redis//:redis-password@redis-host\n\n### A note on scalability\nBecause of the persistent nature of the tunnel transport connections, the ingress of exposr does not\nscale linear, but rather exhibits a sub-linear scaling.\nWhen an ingress connection is made to a node that does not have a tunnel connected locally,\nthe connection is proxied internally by exposr to the node that have the tunnel connected.\nThis means that the ingress traffic will traverse two exposr nodes and with increased number of nodes\nthe probability of the ingress connection being mis-routed increases.\n\nYou can decrease the likelihood of miss-routing by allowing more client per-tunnel connections\nusing the `--transport-max-connections` option, at the expense of maintaining more connections per client.\n\n### Using environment variables\n\nEach option can be given as an environment variable instead of a command line option. The environment variable\nis named the same as the command line option in upper case with `-` replaced with `_`, and prefixed with `EXPOSR_`.\n\nFor example the command line option `--ingress-http-url http://example.com` would be specified as `EXPOSR_INGRESS_HTTP_URL=http://example.com`.\n\nMultiple value options are specified as comma separated values.\nFor example `--transport ws --transport ssh` would be specified as `EXPOSR_TRANSPORT=ws,ssh`\n\n## Production deployment\n\n### Kubernetes\n\nexposr can be deployed to Kubernetes with helm.\n\nAdd the repository\n\n\thelm repo add exposr https://exposr.github.io/helm-charts/\n\thelm repo update\n\nDeploy with\n\n    helm install my-exposr exposr/exposr\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexposr%2Fexposrd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexposr%2Fexposrd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexposr%2Fexposrd/lists"}